Commit 6e86d3c7 authored by Moody Salem's avatar Moody Salem

use the new sdk-core version

parent 6440270c
import JSBI from 'jsbi'
import invariant from 'tiny-invariant'
import { ChainId, WETH9 as _WETH9, TradeType, Rounding, Token, CurrencyAmount } from '@uniswap/sdk-core'
import { ChainId, WETH9 as _WETH9, TradeType, Token, CurrencyAmount } from '@uniswap/sdk-core'
import { Pair, Route, Trade } from '../index'
const ADDRESSES = [
......@@ -24,95 +24,70 @@ describe('entities', () => {
DECIMAL_PERMUTATIONS.forEach(decimals => {
describe(`decimals permutation: ${decimals}`, () => {
let tokens: Token[]
it('Token', () => {
beforeAll(() => {
tokens = ADDRESSES.map((address, i) => new Token(CHAIN_ID, address, decimals[i]))
tokens.forEach((token, i) => {
expect(token.chainId).toEqual(CHAIN_ID)
expect(token.address).toEqual(ADDRESSES[i])
expect(token.decimals).toEqual(decimals[i])
})
})
let pairs: Pair[]
it('Pair', () => {
pairs = [
new Pair(
new CurrencyAmount(tokens[0], decimalize(1, tokens[0].decimals)),
new CurrencyAmount(tokens[1], decimalize(1, tokens[1].decimals))
CurrencyAmount.fromRawAmount(tokens[0], decimalize(1, tokens[0].decimals)),
CurrencyAmount.fromRawAmount(tokens[1], decimalize(1, tokens[1].decimals))
),
new Pair(
new CurrencyAmount(tokens[1], decimalize(1, tokens[1].decimals)),
new CurrencyAmount(tokens[2], decimalize(1, tokens[2].decimals))
CurrencyAmount.fromRawAmount(tokens[1], decimalize(1, tokens[1].decimals)),
CurrencyAmount.fromRawAmount(tokens[2], decimalize(1, tokens[2].decimals))
),
new Pair(
new CurrencyAmount(tokens[2], decimalize(1, tokens[2].decimals)),
new CurrencyAmount(WETH9, decimalize(1234, WETH9.decimals))
CurrencyAmount.fromRawAmount(tokens[2], decimalize(1, tokens[2].decimals)),
CurrencyAmount.fromRawAmount(WETH9, decimalize(1234, WETH9.decimals))
)
]
})
let route: Route
let route: Route<Token, Token>
it('Route', () => {
route = new Route(pairs, tokens[0])
route = new Route(pairs, tokens[0], WETH9)
expect(route.pairs).toEqual(pairs)
expect(route.path).toEqual(tokens.concat([WETH9]))
expect(route.input).toEqual(tokens[0])
expect(route.output).toEqual(WETH9)
})
it('Price:Route.midPrice', () => {
it('#midPrice', () => {
invariant(route.input.isToken)
invariant(route.output.isToken)
expect(route.midPrice.quote(new CurrencyAmount(route.input, decimalize(1, route.input.decimals)))).toEqual(
new CurrencyAmount(route.output, decimalize(1234, route.output.decimals))
)
expect(
route.midPrice.invert().quote(new CurrencyAmount(route.output, decimalize(1234, route.output.decimals)))
).toEqual(new CurrencyAmount(route.input, decimalize(1, route.input.decimals)))
route.midPrice.quote(CurrencyAmount.fromRawAmount(route.input, decimalize(1, route.input.decimals))).toExact()
).toEqual(CurrencyAmount.fromRawAmount(route.output, decimalize(1234, route.output.decimals)).toExact())
expect(
route.midPrice
.invert()
.quote(CurrencyAmount.fromRawAmount(route.output, decimalize(1234, route.output.decimals)))
.toExact()
).toEqual(CurrencyAmount.fromRawAmount(route.input, decimalize(1, route.input.decimals)).toExact())
expect(route.midPrice.toSignificant(1)).toEqual('1000')
expect(route.midPrice.toSignificant(2)).toEqual('1200')
expect(route.midPrice.toSignificant(3)).toEqual('1230')
expect(route.midPrice.toSignificant(4)).toEqual('1234')
expect(route.midPrice.toSignificant(5)).toEqual('1234')
expect(route.midPrice.toSignificant(5, { groupSeparator: ',' })).toEqual('1,234')
expect(route.midPrice.invert().toSignificant(1)).toEqual('0.0008')
expect(route.midPrice.invert().toSignificant(2)).toEqual('0.00081')
expect(route.midPrice.invert().toSignificant(3)).toEqual('0.00081')
expect(route.midPrice.invert().toSignificant(4)).toEqual('0.0008104')
expect(route.midPrice.invert().toSignificant(4, undefined, Rounding.ROUND_DOWN)).toEqual('0.0008103')
expect(route.midPrice.invert().toSignificant(5)).toEqual('0.00081037')
expect(route.midPrice.toFixed(0)).toEqual('1234')
expect(route.midPrice.toFixed(1)).toEqual('1234.0')
expect(route.midPrice.toFixed(2)).toEqual('1234.00')
expect(route.midPrice.toFixed(2, { groupSeparator: ',' })).toEqual('1,234.00')
expect(route.midPrice.invert().toFixed(0)).toEqual('0')
expect(route.midPrice.invert().toFixed(1)).toEqual('0.0')
expect(route.midPrice.invert().toFixed(2)).toEqual('0.00')
expect(route.midPrice.invert().toFixed(3)).toEqual('0.001')
expect(route.midPrice.invert().toFixed(4)).toEqual('0.0008')
expect(route.midPrice.invert().toFixed(5)).toEqual('0.00081')
expect(route.midPrice.invert().toFixed(6)).toEqual('0.000810')
expect(route.midPrice.invert().toFixed(7)).toEqual('0.0008104')
expect(route.midPrice.invert().toFixed(7, undefined, Rounding.ROUND_DOWN)).toEqual('0.0008103')
expect(route.midPrice.invert().toFixed(8)).toEqual('0.00081037')
})
describe('Trade', () => {
let route: Route
let route: Route<Token, Token>
it('TradeType.EXACT_INPUT', () => {
route = new Route(
[
new Pair(
new CurrencyAmount(tokens[1], decimalize(5, tokens[1].decimals)),
new CurrencyAmount(WETH9, decimalize(10, WETH9.decimals))
CurrencyAmount.fromRawAmount(tokens[1], decimalize(5, tokens[1].decimals)),
CurrencyAmount.fromRawAmount(WETH9, decimalize(10, WETH9.decimals))
)
],
tokens[1]
tokens[1],
WETH9
)
const inputAmount = new CurrencyAmount(tokens[1], decimalize(1, tokens[1].decimals))
const expectedOutputAmount = new CurrencyAmount(WETH9, '1662497915624478906')
const inputAmount = CurrencyAmount.fromRawAmount(tokens[1], decimalize(1, tokens[1].decimals))
const expectedOutputAmount = CurrencyAmount.fromRawAmount(WETH9, '1662497915624478906')
const trade = new Trade(route, inputAmount, TradeType.EXACT_INPUT)
expect(trade.route).toEqual(route)
expect(trade.tradeType).toEqual(TradeType.EXACT_INPUT)
......@@ -121,18 +96,15 @@ describe('entities', () => {
expect(trade.executionPrice.toSignificant(18)).toEqual('1.66249791562447891')
expect(trade.executionPrice.invert().toSignificant(18)).toEqual('0.601504513540621866')
expect(trade.executionPrice.quote(inputAmount)).toEqual(expectedOutputAmount)
expect(trade.executionPrice.invert().quote(expectedOutputAmount)).toEqual(inputAmount)
expect(trade.nextMidPrice.toSignificant(18)).toEqual('1.38958368072925352')
expect(trade.nextMidPrice.invert().toSignificant(18)).toEqual('0.71964')
expect(trade.executionPrice.quote(inputAmount).quotient).toEqual(expectedOutputAmount.quotient)
expect(trade.executionPrice.invert().quote(expectedOutputAmount).quotient).toEqual(inputAmount.quotient)
expect(trade.priceImpact.toSignificant(18)).toEqual('16.8751042187760547')
})
it('TradeType.EXACT_OUTPUT', () => {
const outputAmount = new CurrencyAmount(WETH9, '1662497915624478906')
const expectedInputAmount = new CurrencyAmount(tokens[1], decimalize(1, tokens[1].decimals))
const outputAmount = CurrencyAmount.fromRawAmount(WETH9, '1662497915624478906')
const expectedInputAmount = CurrencyAmount.fromRawAmount(tokens[1], decimalize(1, tokens[1].decimals))
const trade = new Trade(route, outputAmount, TradeType.EXACT_OUTPUT)
expect(trade.route).toEqual(route)
expect(trade.tradeType).toEqual(TradeType.EXACT_OUTPUT)
......@@ -141,11 +113,8 @@ describe('entities', () => {
expect(trade.executionPrice.toSignificant(18)).toEqual('1.66249791562447891')
expect(trade.executionPrice.invert().toSignificant(18)).toEqual('0.601504513540621866')
expect(trade.executionPrice.quote(expectedInputAmount)).toEqual(outputAmount)
expect(trade.executionPrice.invert().quote(outputAmount)).toEqual(expectedInputAmount)
expect(trade.nextMidPrice.toSignificant(18)).toEqual('1.38958368072925352')
expect(trade.nextMidPrice.invert().toSignificant(18)).toEqual('0.71964')
expect(trade.executionPrice.quote(expectedInputAmount).quotient).toEqual(outputAmount.quotient)
expect(trade.executionPrice.invert().quote(outputAmount).quotient).toEqual(expectedInputAmount.quotient)
expect(trade.priceImpact.toSignificant(18)).toEqual('16.8751042187760547')
})
......@@ -155,8 +124,8 @@ describe('entities', () => {
const route = new Route(
[
new Pair(
new CurrencyAmount(tokens[1], decimalize(1, tokens[1].decimals)),
new CurrencyAmount(
CurrencyAmount.fromRawAmount(tokens[1], decimalize(1, tokens[1].decimals)),
CurrencyAmount.fromRawAmount(
WETH9,
JSBI.add(
decimalize(10, WETH9.decimals),
......@@ -165,9 +134,10 @@ describe('entities', () => {
)
)
],
tokens[1]
tokens[1],
WETH9
)
const outputAmount = new CurrencyAmount(tokens[1], '1')
const outputAmount = CurrencyAmount.fromRawAmount(tokens[1], '1')
const trade = new Trade(route, outputAmount, TradeType.EXACT_INPUT)
expect(trade.priceImpact.toSignificant(18)).toEqual(
......@@ -176,12 +146,6 @@ describe('entities', () => {
}
})
})
it('CurrencyAmount', () => {
const amount = new CurrencyAmount(WETH9, '1234567000000000000000')
expect(amount.toExact()).toEqual('1234.567')
expect(amount.toExact({ groupSeparator: ',' })).toEqual('1,234.567')
})
})
})
})
......@@ -44,7 +44,11 @@ describe('Pair', () => {
describe('constructor', () => {
it('cannot be used for tokens on different chains', () => {
expect(
() => new Pair(new CurrencyAmount(USDC, '100'), new CurrencyAmount(WETH9[ChainId.RINKEBY], '100'))
() =>
new Pair(
CurrencyAmount.fromRawAmount(USDC, '100'),
CurrencyAmount.fromRawAmount(WETH9[ChainId.RINKEBY], '100')
)
).toThrow('CHAIN_IDS')
})
})
......@@ -57,61 +61,69 @@ describe('Pair', () => {
describe('#token0', () => {
it('always is the token that sorts before', () => {
expect(new Pair(new CurrencyAmount(USDC, '100'), new CurrencyAmount(DAI, '100')).token0).toEqual(DAI)
expect(new Pair(new CurrencyAmount(DAI, '100'), new CurrencyAmount(USDC, '100')).token0).toEqual(DAI)
expect(
new Pair(CurrencyAmount.fromRawAmount(USDC, '100'), CurrencyAmount.fromRawAmount(DAI, '100')).token0
).toEqual(DAI)
expect(
new Pair(CurrencyAmount.fromRawAmount(DAI, '100'), CurrencyAmount.fromRawAmount(USDC, '100')).token0
).toEqual(DAI)
})
})
describe('#token1', () => {
it('always is the token that sorts after', () => {
expect(new Pair(new CurrencyAmount(USDC, '100'), new CurrencyAmount(DAI, '100')).token1).toEqual(USDC)
expect(new Pair(new CurrencyAmount(DAI, '100'), new CurrencyAmount(USDC, '100')).token1).toEqual(USDC)
expect(
new Pair(CurrencyAmount.fromRawAmount(USDC, '100'), CurrencyAmount.fromRawAmount(DAI, '100')).token1
).toEqual(USDC)
expect(
new Pair(CurrencyAmount.fromRawAmount(DAI, '100'), CurrencyAmount.fromRawAmount(USDC, '100')).token1
).toEqual(USDC)
})
})
describe('#reserve0', () => {
it('always comes from the token that sorts before', () => {
expect(new Pair(new CurrencyAmount(USDC, '100'), new CurrencyAmount(DAI, '101')).reserve0).toEqual(
new CurrencyAmount(DAI, '101')
)
expect(new Pair(new CurrencyAmount(DAI, '101'), new CurrencyAmount(USDC, '100')).reserve0).toEqual(
new CurrencyAmount(DAI, '101')
)
expect(
new Pair(CurrencyAmount.fromRawAmount(USDC, '100'), CurrencyAmount.fromRawAmount(DAI, '101')).reserve0
).toEqual(CurrencyAmount.fromRawAmount(DAI, '101'))
expect(
new Pair(CurrencyAmount.fromRawAmount(DAI, '101'), CurrencyAmount.fromRawAmount(USDC, '100')).reserve0
).toEqual(CurrencyAmount.fromRawAmount(DAI, '101'))
})
})
describe('#reserve1', () => {
it('always comes from the token that sorts after', () => {
expect(new Pair(new CurrencyAmount(USDC, '100'), new CurrencyAmount(DAI, '101')).reserve1).toEqual(
new CurrencyAmount(USDC, '100')
)
expect(new Pair(new CurrencyAmount(DAI, '101'), new CurrencyAmount(USDC, '100')).reserve1).toEqual(
new CurrencyAmount(USDC, '100')
)
expect(
new Pair(CurrencyAmount.fromRawAmount(USDC, '100'), CurrencyAmount.fromRawAmount(DAI, '101')).reserve1
).toEqual(CurrencyAmount.fromRawAmount(USDC, '100'))
expect(
new Pair(CurrencyAmount.fromRawAmount(DAI, '101'), CurrencyAmount.fromRawAmount(USDC, '100')).reserve1
).toEqual(CurrencyAmount.fromRawAmount(USDC, '100'))
})
})
describe('#token0Price', () => {
it('returns price of token0 in terms of token1', () => {
expect(new Pair(new CurrencyAmount(USDC, '101'), new CurrencyAmount(DAI, '100')).token0Price).toEqual(
new Price(DAI, USDC, '100', '101')
)
expect(new Pair(new CurrencyAmount(DAI, '100'), new CurrencyAmount(USDC, '101')).token0Price).toEqual(
new Price(DAI, USDC, '100', '101')
)
expect(
new Pair(CurrencyAmount.fromRawAmount(USDC, '101'), CurrencyAmount.fromRawAmount(DAI, '100')).token0Price
).toEqual(new Price(DAI, USDC, '100', '101'))
expect(
new Pair(CurrencyAmount.fromRawAmount(DAI, '100'), CurrencyAmount.fromRawAmount(USDC, '101')).token0Price
).toEqual(new Price(DAI, USDC, '100', '101'))
})
})
describe('#token1Price', () => {
it('returns price of token1 in terms of token0', () => {
expect(new Pair(new CurrencyAmount(USDC, '101'), new CurrencyAmount(DAI, '100')).token1Price).toEqual(
new Price(USDC, DAI, '101', '100')
)
expect(new Pair(new CurrencyAmount(DAI, '100'), new CurrencyAmount(USDC, '101')).token1Price).toEqual(
new Price(USDC, DAI, '101', '100')
)
expect(
new Pair(CurrencyAmount.fromRawAmount(USDC, '101'), CurrencyAmount.fromRawAmount(DAI, '100')).token1Price
).toEqual(new Price(USDC, DAI, '101', '100'))
expect(
new Pair(CurrencyAmount.fromRawAmount(DAI, '100'), CurrencyAmount.fromRawAmount(USDC, '101')).token1Price
).toEqual(new Price(USDC, DAI, '101', '100'))
})
})
describe('#priceOf', () => {
const pair = new Pair(new CurrencyAmount(USDC, '101'), new CurrencyAmount(DAI, '100'))
const pair = new Pair(CurrencyAmount.fromRawAmount(USDC, '101'), CurrencyAmount.fromRawAmount(DAI, '100'))
it('returns price of token in terms of other token', () => {
expect(pair.priceOf(DAI)).toEqual(pair.token0Price)
expect(pair.priceOf(USDC)).toEqual(pair.token1Price)
......@@ -124,136 +136,151 @@ describe('Pair', () => {
describe('#reserveOf', () => {
it('returns reserves of the given token', () => {
expect(new Pair(new CurrencyAmount(USDC, '100'), new CurrencyAmount(DAI, '101')).reserveOf(USDC)).toEqual(
new CurrencyAmount(USDC, '100')
)
expect(new Pair(new CurrencyAmount(DAI, '101'), new CurrencyAmount(USDC, '100')).reserveOf(USDC)).toEqual(
new CurrencyAmount(USDC, '100')
)
expect(
new Pair(CurrencyAmount.fromRawAmount(USDC, '100'), CurrencyAmount.fromRawAmount(DAI, '101')).reserveOf(USDC)
).toEqual(CurrencyAmount.fromRawAmount(USDC, '100'))
expect(
new Pair(CurrencyAmount.fromRawAmount(DAI, '101'), CurrencyAmount.fromRawAmount(USDC, '100')).reserveOf(USDC)
).toEqual(CurrencyAmount.fromRawAmount(USDC, '100'))
})
it('throws if not in the pair', () => {
expect(() =>
new Pair(new CurrencyAmount(DAI, '101'), new CurrencyAmount(USDC, '100')).reserveOf(WETH9[ChainId.MAINNET])
new Pair(CurrencyAmount.fromRawAmount(DAI, '101'), CurrencyAmount.fromRawAmount(USDC, '100')).reserveOf(
WETH9[ChainId.MAINNET]
)
).toThrow('TOKEN')
})
})
describe('#chainId', () => {
it('returns the token0 chainId', () => {
expect(new Pair(new CurrencyAmount(USDC, '100'), new CurrencyAmount(DAI, '100')).chainId).toEqual(ChainId.MAINNET)
expect(new Pair(new CurrencyAmount(DAI, '100'), new CurrencyAmount(USDC, '100')).chainId).toEqual(ChainId.MAINNET)
expect(
new Pair(CurrencyAmount.fromRawAmount(USDC, '100'), CurrencyAmount.fromRawAmount(DAI, '100')).chainId
).toEqual(ChainId.MAINNET)
expect(
new Pair(CurrencyAmount.fromRawAmount(DAI, '100'), CurrencyAmount.fromRawAmount(USDC, '100')).chainId
).toEqual(ChainId.MAINNET)
})
})
describe('#involvesToken', () => {
expect(new Pair(new CurrencyAmount(USDC, '100'), new CurrencyAmount(DAI, '100')).involvesToken(USDC)).toEqual(true)
expect(new Pair(new CurrencyAmount(USDC, '100'), new CurrencyAmount(DAI, '100')).involvesToken(DAI)).toEqual(true)
expect(
new Pair(new CurrencyAmount(USDC, '100'), new CurrencyAmount(DAI, '100')).involvesToken(WETH9[ChainId.MAINNET])
new Pair(CurrencyAmount.fromRawAmount(USDC, '100'), CurrencyAmount.fromRawAmount(DAI, '100')).involvesToken(USDC)
).toEqual(true)
expect(
new Pair(CurrencyAmount.fromRawAmount(USDC, '100'), CurrencyAmount.fromRawAmount(DAI, '100')).involvesToken(DAI)
).toEqual(true)
expect(
new Pair(CurrencyAmount.fromRawAmount(USDC, '100'), CurrencyAmount.fromRawAmount(DAI, '100')).involvesToken(
WETH9[ChainId.MAINNET]
)
).toEqual(false)
})
describe('miscellaneous', () => {
it('getLiquidityMinted:0', async () => {
const tokenA = new Token(ChainId.RINKEBY, '0x0000000000000000000000000000000000000001', 18)
const tokenB = new Token(ChainId.RINKEBY, '0x0000000000000000000000000000000000000002', 18)
const pair = new Pair(new CurrencyAmount(tokenA, '0'), new CurrencyAmount(tokenB, '0'))
const pair = new Pair(CurrencyAmount.fromRawAmount(tokenA, '0'), CurrencyAmount.fromRawAmount(tokenB, '0'))
expect(() => {
pair.getLiquidityMinted(
new CurrencyAmount(pair.liquidityToken, '0'),
new CurrencyAmount(tokenA, '1000'),
new CurrencyAmount(tokenB, '1000')
CurrencyAmount.fromRawAmount(pair.liquidityToken, '0'),
CurrencyAmount.fromRawAmount(tokenA, '1000'),
CurrencyAmount.fromRawAmount(tokenB, '1000')
)
}).toThrow(InsufficientInputAmountError)
expect(() => {
pair.getLiquidityMinted(
new CurrencyAmount(pair.liquidityToken, '0'),
new CurrencyAmount(tokenA, '1000000'),
new CurrencyAmount(tokenB, '1')
CurrencyAmount.fromRawAmount(pair.liquidityToken, '0'),
CurrencyAmount.fromRawAmount(tokenA, '1000000'),
CurrencyAmount.fromRawAmount(tokenB, '1')
)
}).toThrow(InsufficientInputAmountError)
const liquidity = pair.getLiquidityMinted(
new CurrencyAmount(pair.liquidityToken, '0'),
new CurrencyAmount(tokenA, '1001'),
new CurrencyAmount(tokenB, '1001')
CurrencyAmount.fromRawAmount(pair.liquidityToken, '0'),
CurrencyAmount.fromRawAmount(tokenA, '1001'),
CurrencyAmount.fromRawAmount(tokenB, '1001')
)
expect(liquidity.raw.toString()).toEqual('1')
expect(liquidity.quotient.toString()).toEqual('1')
})
it('getLiquidityMinted:!0', async () => {
const tokenA = new Token(ChainId.RINKEBY, '0x0000000000000000000000000000000000000001', 18)
const tokenB = new Token(ChainId.RINKEBY, '0x0000000000000000000000000000000000000002', 18)
const pair = new Pair(new CurrencyAmount(tokenA, '10000'), new CurrencyAmount(tokenB, '10000'))
const pair = new Pair(
CurrencyAmount.fromRawAmount(tokenA, '10000'),
CurrencyAmount.fromRawAmount(tokenB, '10000')
)
expect(
pair
.getLiquidityMinted(
new CurrencyAmount(pair.liquidityToken, '10000'),
new CurrencyAmount(tokenA, '2000'),
new CurrencyAmount(tokenB, '2000')
CurrencyAmount.fromRawAmount(pair.liquidityToken, '10000'),
CurrencyAmount.fromRawAmount(tokenA, '2000'),
CurrencyAmount.fromRawAmount(tokenB, '2000')
)
.raw.toString()
.quotient.toString()
).toEqual('2000')
})
it('getLiquidityValue:!feeOn', async () => {
const tokenA = new Token(ChainId.RINKEBY, '0x0000000000000000000000000000000000000001', 18)
const tokenB = new Token(ChainId.RINKEBY, '0x0000000000000000000000000000000000000002', 18)
const pair = new Pair(new CurrencyAmount(tokenA, '1000'), new CurrencyAmount(tokenB, '1000'))
const pair = new Pair(CurrencyAmount.fromRawAmount(tokenA, '1000'), CurrencyAmount.fromRawAmount(tokenB, '1000'))
{
const liquidityValue = pair.getLiquidityValue(
tokenA,
new CurrencyAmount(pair.liquidityToken, '1000'),
new CurrencyAmount(pair.liquidityToken, '1000'),
CurrencyAmount.fromRawAmount(pair.liquidityToken, '1000'),
CurrencyAmount.fromRawAmount(pair.liquidityToken, '1000'),
false
)
expect(currencyEquals(liquidityValue.currency, tokenA)).toBe(true)
expect(liquidityValue.raw.toString()).toBe('1000')
expect(liquidityValue.quotient.toString()).toBe('1000')
}
// 500
{
const liquidityValue = pair.getLiquidityValue(
tokenA,
new CurrencyAmount(pair.liquidityToken, '1000'),
new CurrencyAmount(pair.liquidityToken, '500'),
CurrencyAmount.fromRawAmount(pair.liquidityToken, '1000'),
CurrencyAmount.fromRawAmount(pair.liquidityToken, '500'),
false
)
expect(currencyEquals(liquidityValue.currency, tokenA)).toBe(true)
expect(liquidityValue.raw.toString()).toBe('500')
expect(liquidityValue.quotient.toString()).toBe('500')
}
// tokenB
{
const liquidityValue = pair.getLiquidityValue(
tokenB,
new CurrencyAmount(pair.liquidityToken, '1000'),
new CurrencyAmount(pair.liquidityToken, '1000'),
CurrencyAmount.fromRawAmount(pair.liquidityToken, '1000'),
CurrencyAmount.fromRawAmount(pair.liquidityToken, '1000'),
false
)
expect(currencyEquals(liquidityValue.currency, tokenB)).toBe(true)
expect(liquidityValue.raw.toString()).toBe('1000')
expect(liquidityValue.quotient.toString()).toBe('1000')
}
})
it('getLiquidityValue:feeOn', async () => {
const tokenA = new Token(ChainId.RINKEBY, '0x0000000000000000000000000000000000000001', 18)
const tokenB = new Token(ChainId.RINKEBY, '0x0000000000000000000000000000000000000002', 18)
const pair = new Pair(new CurrencyAmount(tokenA, '1000'), new CurrencyAmount(tokenB, '1000'))
const pair = new Pair(CurrencyAmount.fromRawAmount(tokenA, '1000'), CurrencyAmount.fromRawAmount(tokenB, '1000'))
const liquidityValue = pair.getLiquidityValue(
tokenA,
new CurrencyAmount(pair.liquidityToken, '500'),
new CurrencyAmount(pair.liquidityToken, '500'),
CurrencyAmount.fromRawAmount(pair.liquidityToken, '500'),
CurrencyAmount.fromRawAmount(pair.liquidityToken, '500'),
true,
'250000' // 500 ** 2
)
expect(currencyEquals(liquidityValue.currency, tokenA)).toBe(true)
expect(liquidityValue.raw.toString()).toBe('917') // ceiling(1000 - (500 * (1 / 6)))
expect(liquidityValue.quotient.toString()).toBe('917') // ceiling(1000 - (500 * (1 / 6)))
})
})
})
......@@ -25,18 +25,16 @@ export const computePairAddress = ({
}
export class Pair {
public readonly liquidityToken: Token
private readonly tokenAmounts: [CurrencyAmount, CurrencyAmount]
private readonly tokenAmounts: [CurrencyAmount<Token>, CurrencyAmount<Token>]
public static getAddress(tokenA: Token, tokenB: Token): string {
return computePairAddress({ factoryAddress: FACTORY_ADDRESS, tokenA, tokenB })
}
public constructor(currencyAmountA: CurrencyAmount, tokenAmountB: CurrencyAmount) {
invariant(currencyAmountA.currency.isToken && tokenAmountB.currency.isToken, 'TOKEN')
public constructor(currencyAmountA: CurrencyAmount<Token>, tokenAmountB: CurrencyAmount<Token>) {
const tokenAmounts = currencyAmountA.currency.sortsBefore(tokenAmountB.currency) // does safety checks
? [currencyAmountA, tokenAmountB]
: [tokenAmountB, currencyAmountA]
invariant(tokenAmounts[0].currency.isToken && tokenAmounts[1].currency.isToken, 'TOKEN')
this.liquidityToken = new Token(
tokenAmounts[0].currency.chainId,
Pair.getAddress(tokenAmounts[0].currency, tokenAmounts[1].currency),
......@@ -44,7 +42,7 @@ export class Pair {
'UNI-V2',
'Uniswap V2'
)
this.tokenAmounts = tokenAmounts as [CurrencyAmount, CurrencyAmount]
this.tokenAmounts = tokenAmounts as [CurrencyAmount<Token>, CurrencyAmount<Token>]
}
/**
......@@ -58,22 +56,24 @@ export class Pair {
/**
* Returns the current mid price of the pair in terms of token0, i.e. the ratio of reserve1 to reserve0
*/
public get token0Price(): Price {
return new Price(this.token0, this.token1, this.tokenAmounts[0].raw, this.tokenAmounts[1].raw)
public get token0Price(): Price<Token, Token> {
const result = this.tokenAmounts[1].divide(this.tokenAmounts[0])
return new Price(this.token0, this.token1, result.denominator, result.numerator)
}
/**
* Returns the current mid price of the pair in terms of token1, i.e. the ratio of reserve0 to reserve1
*/
public get token1Price(): Price {
return new Price(this.token1, this.token0, this.tokenAmounts[1].raw, this.tokenAmounts[0].raw)
public get token1Price(): Price<Token, Token> {
const result = this.tokenAmounts[0].divide(this.tokenAmounts[1])
return new Price(this.token1, this.token0, result.denominator, result.numerator)
}
/**
* Return the price of the given token in terms of the other token in the pair.
* @param token token to return price of
*/
public priceOf(token: Token): Price {
public priceOf(token: Token): Price<Token, Token> {
invariant(this.involvesToken(token), 'TOKEN')
return token.equals(this.token0) ? this.token0Price : this.token1Price
}
......@@ -86,63 +86,61 @@ export class Pair {
}
public get token0(): Token {
invariant(this.tokenAmounts[0].currency.isToken)
return this.tokenAmounts[0].currency
}
public get token1(): Token {
invariant(this.tokenAmounts[1].currency.isToken)
return this.tokenAmounts[1].currency
}
public get reserve0(): CurrencyAmount {
public get reserve0(): CurrencyAmount<Token> {
return this.tokenAmounts[0]
}
public get reserve1(): CurrencyAmount {
public get reserve1(): CurrencyAmount<Token> {
return this.tokenAmounts[1]
}
public reserveOf(token: Token): CurrencyAmount {
public reserveOf(token: Token): CurrencyAmount<Token> {
invariant(this.involvesToken(token), 'TOKEN')
return token.equals(this.token0) ? this.reserve0 : this.reserve1
}
public getOutputAmount(inputAmount: CurrencyAmount): [CurrencyAmount, Pair] {
invariant(inputAmount.currency.isToken && this.involvesToken(inputAmount.currency), 'TOKEN')
if (JSBI.equal(this.reserve0.raw, ZERO) || JSBI.equal(this.reserve1.raw, ZERO)) {
public getOutputAmount(inputAmount: CurrencyAmount<Token>): [CurrencyAmount<Token>, Pair] {
invariant(this.involvesToken(inputAmount.currency), 'TOKEN')
if (JSBI.equal(this.reserve0.quotient, ZERO) || JSBI.equal(this.reserve1.quotient, ZERO)) {
throw new InsufficientReservesError()
}
const inputReserve = this.reserveOf(inputAmount.currency)
const outputReserve = this.reserveOf(inputAmount.currency.equals(this.token0) ? this.token1 : this.token0)
const inputAmountWithFee = JSBI.multiply(inputAmount.raw, _997)
const numerator = JSBI.multiply(inputAmountWithFee, outputReserve.raw)
const denominator = JSBI.add(JSBI.multiply(inputReserve.raw, _1000), inputAmountWithFee)
const outputAmount = new CurrencyAmount(
const inputAmountWithFee = JSBI.multiply(inputAmount.quotient, _997)
const numerator = JSBI.multiply(inputAmountWithFee, outputReserve.quotient)
const denominator = JSBI.add(JSBI.multiply(inputReserve.quotient, _1000), inputAmountWithFee)
const outputAmount = CurrencyAmount.fromRawAmount(
inputAmount.currency.equals(this.token0) ? this.token1 : this.token0,
JSBI.divide(numerator, denominator)
)
if (JSBI.equal(outputAmount.raw, ZERO)) {
if (JSBI.equal(outputAmount.quotient, ZERO)) {
throw new InsufficientInputAmountError()
}
return [outputAmount, new Pair(inputReserve.add(inputAmount), outputReserve.subtract(outputAmount))]
}
public getInputAmount(outputAmount: CurrencyAmount): [CurrencyAmount, Pair] {
invariant(outputAmount.currency.isToken && this.involvesToken(outputAmount.currency), 'TOKEN')
public getInputAmount(outputAmount: CurrencyAmount<Token>): [CurrencyAmount<Token>, Pair] {
invariant(this.involvesToken(outputAmount.currency), 'TOKEN')
if (
JSBI.equal(this.reserve0.raw, ZERO) ||
JSBI.equal(this.reserve1.raw, ZERO) ||
JSBI.greaterThanOrEqual(outputAmount.raw, this.reserveOf(outputAmount.currency).raw)
JSBI.equal(this.reserve0.quotient, ZERO) ||
JSBI.equal(this.reserve1.quotient, ZERO) ||
JSBI.greaterThanOrEqual(outputAmount.quotient, this.reserveOf(outputAmount.currency).quotient)
) {
throw new InsufficientReservesError()
}
const outputReserve = this.reserveOf(outputAmount.currency)
const inputReserve = this.reserveOf(outputAmount.currency.equals(this.token0) ? this.token1 : this.token0)
const numerator = JSBI.multiply(JSBI.multiply(inputReserve.raw, outputAmount.raw), _1000)
const denominator = JSBI.multiply(JSBI.subtract(outputReserve.raw, outputAmount.raw), _997)
const inputAmount = new CurrencyAmount(
const numerator = JSBI.multiply(JSBI.multiply(inputReserve.quotient, outputAmount.quotient), _1000)
const denominator = JSBI.multiply(JSBI.subtract(outputReserve.quotient, outputAmount.quotient), _997)
const inputAmount = CurrencyAmount.fromRawAmount(
outputAmount.currency.equals(this.token0) ? this.token1 : this.token0,
JSBI.add(JSBI.divide(numerator, denominator), ONE)
)
......@@ -150,60 +148,59 @@ export class Pair {
}
public getLiquidityMinted(
totalSupply: CurrencyAmount,
tokenAmountA: CurrencyAmount,
tokenAmountB: CurrencyAmount
): CurrencyAmount {
invariant(totalSupply.currency.isToken && totalSupply.currency.equals(this.liquidityToken), 'LIQUIDITY')
const tokenAmounts =
tokenAmountA.currency.isToken &&
tokenAmountB.currency.isToken &&
tokenAmountA.currency.sortsBefore(tokenAmountB.currency) // does safety checks
totalSupply: CurrencyAmount<Token>,
tokenAmountA: CurrencyAmount<Token>,
tokenAmountB: CurrencyAmount<Token>
): CurrencyAmount<Token> {
invariant(totalSupply.currency.equals(this.liquidityToken), 'LIQUIDITY')
const tokenAmounts = tokenAmountA.currency.sortsBefore(tokenAmountB.currency) // does safety checks
? [tokenAmountA, tokenAmountB]
: [tokenAmountB, tokenAmountA]
invariant(tokenAmounts[0].currency.isToken && tokenAmounts[1].currency.isToken)
invariant(tokenAmounts[0].currency.equals(this.token0) && tokenAmounts[1].currency.equals(this.token1), 'TOKEN')
let liquidity: JSBI
if (JSBI.equal(totalSupply.raw, ZERO)) {
liquidity = JSBI.subtract(sqrt(JSBI.multiply(tokenAmounts[0].raw, tokenAmounts[1].raw)), MINIMUM_LIQUIDITY)
if (JSBI.equal(totalSupply.quotient, ZERO)) {
liquidity = JSBI.subtract(
sqrt(JSBI.multiply(tokenAmounts[0].quotient, tokenAmounts[1].quotient)),
MINIMUM_LIQUIDITY
)
} else {
const amount0 = JSBI.divide(JSBI.multiply(tokenAmounts[0].raw, totalSupply.raw), this.reserve0.raw)
const amount1 = JSBI.divide(JSBI.multiply(tokenAmounts[1].raw, totalSupply.raw), this.reserve1.raw)
const amount0 = JSBI.divide(JSBI.multiply(tokenAmounts[0].quotient, totalSupply.quotient), this.reserve0.quotient)
const amount1 = JSBI.divide(JSBI.multiply(tokenAmounts[1].quotient, totalSupply.quotient), this.reserve1.quotient)
liquidity = JSBI.lessThanOrEqual(amount0, amount1) ? amount0 : amount1
}
if (!JSBI.greaterThan(liquidity, ZERO)) {
throw new InsufficientInputAmountError()
}
return new CurrencyAmount(this.liquidityToken, liquidity)
return CurrencyAmount.fromRawAmount(this.liquidityToken, liquidity)
}
public getLiquidityValue(
token: Token,
totalSupply: CurrencyAmount,
liquidity: CurrencyAmount,
totalSupply: CurrencyAmount<Token>,
liquidity: CurrencyAmount<Token>,
feeOn: boolean = false,
kLast?: BigintIsh
): CurrencyAmount {
): CurrencyAmount<Token> {
invariant(this.involvesToken(token), 'TOKEN')
invariant(totalSupply.currency.isToken && totalSupply.currency.equals(this.liquidityToken), 'TOTAL_SUPPLY')
invariant(liquidity.currency.isToken && liquidity.currency.equals(this.liquidityToken), 'LIQUIDITY')
invariant(JSBI.lessThanOrEqual(liquidity.raw, totalSupply.raw), 'LIQUIDITY')
invariant(totalSupply.currency.equals(this.liquidityToken), 'TOTAL_SUPPLY')
invariant(liquidity.currency.equals(this.liquidityToken), 'LIQUIDITY')
invariant(JSBI.lessThanOrEqual(liquidity.quotient, totalSupply.quotient), 'LIQUIDITY')
let totalSupplyAdjusted: CurrencyAmount
let totalSupplyAdjusted: CurrencyAmount<Token>
if (!feeOn) {
totalSupplyAdjusted = totalSupply
} else {
invariant(!!kLast, 'K_LAST')
const kLastParsed = JSBI.BigInt(kLast)
if (!JSBI.equal(kLastParsed, ZERO)) {
const rootK = sqrt(JSBI.multiply(this.reserve0.raw, this.reserve1.raw))
const rootK = sqrt(JSBI.multiply(this.reserve0.quotient, this.reserve1.quotient))
const rootKLast = sqrt(kLastParsed)
if (JSBI.greaterThan(rootK, rootKLast)) {
const numerator = JSBI.multiply(totalSupply.raw, JSBI.subtract(rootK, rootKLast))
const numerator = JSBI.multiply(totalSupply.quotient, JSBI.subtract(rootK, rootKLast))
const denominator = JSBI.add(JSBI.multiply(rootK, FIVE), rootKLast)
const feeLiquidity = JSBI.divide(numerator, denominator)
totalSupplyAdjusted = totalSupply.add(new CurrencyAmount(this.liquidityToken, feeLiquidity))
totalSupplyAdjusted = totalSupply.add(CurrencyAmount.fromRawAmount(this.liquidityToken, feeLiquidity))
} else {
totalSupplyAdjusted = totalSupply
}
......@@ -212,9 +209,9 @@ export class Pair {
}
}
return new CurrencyAmount(
return CurrencyAmount.fromRawAmount(
token,
JSBI.divide(JSBI.multiply(liquidity.raw, this.reserveOf(token).raw), totalSupplyAdjusted.raw)
JSBI.divide(JSBI.multiply(liquidity.quotient, this.reserveOf(token).quotient), totalSupplyAdjusted.quotient)
)
}
}
......@@ -5,12 +5,12 @@ describe('Route', () => {
const token0 = new Token(ChainId.MAINNET, '0x0000000000000000000000000000000000000001', 18, 't0')
const token1 = new Token(ChainId.MAINNET, '0x0000000000000000000000000000000000000002', 18, 't1')
const weth = WETH9[ChainId.MAINNET]
const pair_0_1 = new Pair(new CurrencyAmount(token0, '100'), new CurrencyAmount(token1, '200'))
const pair_0_weth = new Pair(new CurrencyAmount(token0, '100'), new CurrencyAmount(weth, '100'))
const pair_1_weth = new Pair(new CurrencyAmount(token1, '175'), new CurrencyAmount(weth, '100'))
const pair_0_1 = new Pair(CurrencyAmount.fromRawAmount(token0, '100'), CurrencyAmount.fromRawAmount(token1, '200'))
const pair_0_weth = new Pair(CurrencyAmount.fromRawAmount(token0, '100'), CurrencyAmount.fromRawAmount(weth, '100'))
const pair_1_weth = new Pair(CurrencyAmount.fromRawAmount(token1, '175'), CurrencyAmount.fromRawAmount(weth, '100'))
it('constructs a path from the tokens', () => {
const route = new Route([pair_0_1], token0)
const route = new Route([pair_0_1], token0, token1)
expect(route.pairs).toEqual([pair_0_1])
expect(route.path).toEqual([token0, token1])
expect(route.input).toEqual(token0)
......@@ -19,14 +19,14 @@ describe('Route', () => {
})
it('can have a token as both input and output', () => {
const route = new Route([pair_0_weth, pair_0_1, pair_1_weth], weth)
const route = new Route([pair_0_weth, pair_0_1, pair_1_weth], weth, weth)
expect(route.pairs).toEqual([pair_0_weth, pair_0_1, pair_1_weth])
expect(route.input).toEqual(weth)
expect(route.output).toEqual(weth)
})
it('supports ether input', () => {
const route = new Route([pair_0_weth], ETHER)
const route = new Route([pair_0_weth], ETHER, token0)
expect(route.pairs).toEqual([pair_0_weth])
expect(route.input).toEqual(ETHER)
expect(route.output).toEqual(token0)
......
import { ChainId, Currency, ETHER, Price, Token, WETH9 } from '@uniswap/sdk-core'
import invariant from 'tiny-invariant'
import { ChainId, Currency, Price, Token, wrappedCurrency } from '@uniswap/sdk-core'
import { Pair } from './pair'
export class Route {
export class Route<TInput extends Currency, TOutput extends Currency> {
public readonly pairs: Pair[]
public readonly path: Token[]
public readonly input: Currency
public readonly output: Currency
public readonly input: TInput
public readonly output: TOutput
public get midPrice(): Price {
const prices: Price[] = []
for (const [i, pair] of this.pairs.entries()) {
prices.push(
this.path[i].equals(pair.token0)
? new Price(pair.reserve0.currency, pair.reserve1.currency, pair.reserve0.raw, pair.reserve1.raw)
: new Price(pair.reserve1.currency, pair.reserve0.currency, pair.reserve1.raw, pair.reserve0.raw)
)
}
return prices.slice(1).reduce((accumulator, currentValue) => accumulator.multiply(currentValue), prices[0])
}
public constructor(pairs: Pair[], input: Currency, output?: Currency) {
public constructor(pairs: Pair[], input: TInput, output: TOutput) {
invariant(pairs.length > 0, 'PAIRS')
const chainId: ChainId | number = pairs[0].chainId
invariant(
......@@ -29,20 +17,14 @@ export class Route {
'CHAIN_IDS'
)
const weth: Token | undefined = WETH9[chainId as ChainId]
invariant(
(input.isToken && pairs[0].involvesToken(input)) || (input === ETHER && weth && pairs[0].involvesToken(weth)),
'INPUT'
)
const wrappedInput = wrappedCurrency(input, chainId)
invariant(pairs[0].involvesToken(wrappedInput), 'INPUT')
invariant(
typeof output === 'undefined' ||
(output.isToken && pairs[pairs.length - 1].involvesToken(output)) ||
(output === ETHER && weth && pairs[pairs.length - 1].involvesToken(weth)),
typeof output === 'undefined' || pairs[pairs.length - 1].involvesToken(wrappedCurrency(output, chainId)),
'OUTPUT'
)
const path: Token[] = [input.isToken ? input : weth]
const path: Token[] = [wrappedInput]
for (const [i, pair] of pairs.entries()) {
const currentInput = path[i]
invariant(currentInput.equals(pair.token0) || currentInput.equals(pair.token1), 'PATH')
......@@ -53,7 +35,23 @@ export class Route {
this.pairs = pairs
this.path = path
this.input = input
this.output = output ?? path[path.length - 1]
this.output = output
}
private _midPrice: Price<TInput, TOutput> | null = null
public get midPrice(): Price<TInput, TOutput> {
if (this._midPrice !== null) return this._midPrice
const prices: Price<Currency, Currency>[] = []
for (const [i, pair] of this.pairs.entries()) {
prices.push(
this.path[i].equals(pair.token0)
? new Price(pair.reserve0.currency, pair.reserve1.currency, pair.reserve0.quotient, pair.reserve1.quotient)
: new Price(pair.reserve1.currency, pair.reserve0.currency, pair.reserve1.quotient, pair.reserve0.quotient)
)
}
const reduced = prices.slice(1).reduce((accumulator, currentValue) => accumulator.multiply(currentValue), prices[0])
return (this._midPrice = new Price(this.input, this.output, reduced.denominator, reduced.numerator))
}
public get chainId(): ChainId | number {
......
......@@ -11,36 +11,39 @@ describe('Trade', () => {
const token3 = new Token(ChainId.MAINNET, '0x0000000000000000000000000000000000000004', 18, 't3')
const pair_0_1 = new Pair(
new CurrencyAmount(token0, JSBI.BigInt(1000)),
new CurrencyAmount(token1, JSBI.BigInt(1000))
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(1000)),
CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(1000))
)
const pair_0_2 = new Pair(
new CurrencyAmount(token0, JSBI.BigInt(1000)),
new CurrencyAmount(token2, JSBI.BigInt(1100))
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(1000)),
CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(1100))
)
const pair_0_3 = new Pair(
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(1000)),
CurrencyAmount.fromRawAmount(token3, JSBI.BigInt(900))
)
const pair_0_3 = new Pair(new CurrencyAmount(token0, JSBI.BigInt(1000)), new CurrencyAmount(token3, JSBI.BigInt(900)))
const pair_1_2 = new Pair(
new CurrencyAmount(token1, JSBI.BigInt(1200)),
new CurrencyAmount(token2, JSBI.BigInt(1000))
CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(1200)),
CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(1000))
)
const pair_1_3 = new Pair(
new CurrencyAmount(token1, JSBI.BigInt(1200)),
new CurrencyAmount(token3, JSBI.BigInt(1300))
CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(1200)),
CurrencyAmount.fromRawAmount(token3, JSBI.BigInt(1300))
)
const pair_weth_0 = new Pair(
new CurrencyAmount(WETH9[ChainId.MAINNET], JSBI.BigInt(1000)),
new CurrencyAmount(token0, JSBI.BigInt(1000))
CurrencyAmount.fromRawAmount(WETH9[ChainId.MAINNET], JSBI.BigInt(1000)),
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(1000))
)
const empty_pair_0_1 = new Pair(
new CurrencyAmount(token0, JSBI.BigInt(0)),
new CurrencyAmount(token1, JSBI.BigInt(0))
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(0)),
CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(0))
)
it('can be constructed with ETHER as input', () => {
const trade = new Trade(
new Route([pair_weth_0], ETHER),
new Route([pair_weth_0], ETHER, token0),
CurrencyAmount.ether(JSBI.BigInt(100)),
TradeType.EXACT_INPUT
)
......@@ -50,7 +53,7 @@ describe('Trade', () => {
it('can be constructed with ETHER as input for exact output', () => {
const trade = new Trade(
new Route([pair_weth_0], ETHER, token0),
new CurrencyAmount(token0, JSBI.BigInt(100)),
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100)),
TradeType.EXACT_OUTPUT
)
expect(trade.inputAmount.currency).toEqual(ETHER)
......@@ -69,7 +72,7 @@ describe('Trade', () => {
it('can be constructed with ETHER as output for exact input', () => {
const trade = new Trade(
new Route([pair_weth_0], token0, ETHER),
new CurrencyAmount(token0, JSBI.BigInt(100)),
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100)),
TradeType.EXACT_INPUT
)
expect(trade.inputAmount.currency).toEqual(token0)
......@@ -78,41 +81,45 @@ describe('Trade', () => {
describe('#bestTradeExactIn', () => {
it('throws with empty pairs', () => {
expect(() => Trade.bestTradeExactIn([], new CurrencyAmount(token0, JSBI.BigInt(100)), token2)).toThrow('PAIRS')
expect(() => Trade.bestTradeExactIn([], CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100)), token2)).toThrow(
'PAIRS'
)
})
it('throws with max hops of 0', () => {
expect(() =>
Trade.bestTradeExactIn([pair_0_2], new CurrencyAmount(token0, JSBI.BigInt(100)), token2, { maxHops: 0 })
Trade.bestTradeExactIn([pair_0_2], CurrencyAmount.fromRawAmount(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 CurrencyAmount(token0, JSBI.BigInt(100)),
CurrencyAmount.fromRawAmount(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 CurrencyAmount(token0, JSBI.BigInt(100)))
expect(result[0].outputAmount).toEqual(new CurrencyAmount(token2, JSBI.BigInt(99)))
expect(result[0].inputAmount).toEqual(CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100)))
expect(result[0].outputAmount).toEqual(CurrencyAmount.fromRawAmount(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 CurrencyAmount(token0, JSBI.BigInt(100)))
expect(result[1].outputAmount).toEqual(new CurrencyAmount(token2, JSBI.BigInt(69)))
expect(result[1].inputAmount).toEqual(CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100)))
expect(result[1].outputAmount).toEqual(CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(69)))
})
it('doesnt throw for zero liquidity pairs', () => {
expect(
Trade.bestTradeExactIn([empty_pair_0_1], new CurrencyAmount(token0, JSBI.BigInt(100)), token1)
Trade.bestTradeExactIn([empty_pair_0_1], CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100)), token1)
).toHaveLength(0)
})
it('respects maxHops', () => {
const result = Trade.bestTradeExactIn(
[pair_0_1, pair_0_2, pair_1_2],
new CurrencyAmount(token0, JSBI.BigInt(10)),
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(10)),
token2,
{ maxHops: 1 }
)
......@@ -124,19 +131,19 @@ describe('Trade', () => {
it('insufficient input for one pair', () => {
const result = Trade.bestTradeExactIn(
[pair_0_1, pair_0_2, pair_1_2],
new CurrencyAmount(token0, JSBI.BigInt(1)),
CurrencyAmount.fromRawAmount(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 CurrencyAmount(token2, JSBI.BigInt(1)))
expect(result[0].outputAmount).toEqual(CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(1)))
})
it('respects n', () => {
const result = Trade.bestTradeExactIn(
[pair_0_1, pair_0_2, pair_1_2],
new CurrencyAmount(token0, JSBI.BigInt(10)),
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(10)),
token2,
{ maxNumResults: 1 }
)
......@@ -147,7 +154,7 @@ describe('Trade', () => {
it('no path', () => {
const result = Trade.bestTradeExactIn(
[pair_0_1, pair_0_3, pair_1_3],
new CurrencyAmount(token0, JSBI.BigInt(10)),
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(10)),
token2
)
expect(result).toHaveLength(0)
......@@ -170,7 +177,7 @@ describe('Trade', () => {
it('works for ETHER currency output', () => {
const result = Trade.bestTradeExactIn(
[pair_weth_0, pair_0_1, pair_0_3, pair_1_3],
new CurrencyAmount(token3, JSBI.BigInt(100)),
CurrencyAmount.fromRawAmount(token3, JSBI.BigInt(100)),
ETHER
)
expect(result).toHaveLength(2)
......@@ -186,8 +193,8 @@ describe('Trade', () => {
describe('#maximumAmountIn', () => {
describe('tradeType = EXACT_INPUT', () => {
const exactIn = new Trade(
new Route([pair_0_1, pair_1_2], token0),
new CurrencyAmount(token0, JSBI.BigInt(100)),
new Route([pair_0_1, pair_1_2], token0, token2),
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100)),
TradeType.EXACT_INPUT
)
it('throws if less than 0', () => {
......@@ -200,20 +207,20 @@ describe('Trade', () => {
})
it('returns exact if nonzero', () => {
expect(exactIn.maximumAmountIn(new Percent(JSBI.BigInt(0), JSBI.BigInt(100)))).toEqual(
new CurrencyAmount(token0, JSBI.BigInt(100))
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100))
)
expect(exactIn.maximumAmountIn(new Percent(JSBI.BigInt(5), JSBI.BigInt(100)))).toEqual(
new CurrencyAmount(token0, JSBI.BigInt(100))
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100))
)
expect(exactIn.maximumAmountIn(new Percent(JSBI.BigInt(200), JSBI.BigInt(100)))).toEqual(
new CurrencyAmount(token0, JSBI.BigInt(100))
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100))
)
})
})
describe('tradeType = EXACT_OUTPUT', () => {
const exactOut = new Trade(
new Route([pair_0_1, pair_1_2], token0),
new CurrencyAmount(token2, JSBI.BigInt(100)),
new Route([pair_0_1, pair_1_2], token0, token2),
CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(100)),
TradeType.EXACT_OUTPUT
)
......@@ -227,13 +234,13 @@ describe('Trade', () => {
})
it('returns slippage amount if nonzero', () => {
expect(exactOut.maximumAmountIn(new Percent(JSBI.BigInt(0), JSBI.BigInt(100)))).toEqual(
new CurrencyAmount(token0, JSBI.BigInt(156))
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(156))
)
expect(exactOut.maximumAmountIn(new Percent(JSBI.BigInt(5), JSBI.BigInt(100)))).toEqual(
new CurrencyAmount(token0, JSBI.BigInt(163))
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(163))
)
expect(exactOut.maximumAmountIn(new Percent(JSBI.BigInt(200), JSBI.BigInt(100)))).toEqual(
new CurrencyAmount(token0, JSBI.BigInt(468))
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(468))
)
})
})
......@@ -242,8 +249,8 @@ describe('Trade', () => {
describe('#minimumAmountOut', () => {
describe('tradeType = EXACT_INPUT', () => {
const exactIn = new Trade(
new Route([pair_0_1, pair_1_2], token0),
new CurrencyAmount(token0, JSBI.BigInt(100)),
new Route([pair_0_1, pair_1_2], token0, token2),
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100)),
TradeType.EXACT_INPUT
)
it('throws if less than 0', () => {
......@@ -256,20 +263,20 @@ describe('Trade', () => {
})
it('returns exact if nonzero', () => {
expect(exactIn.minimumAmountOut(new Percent(JSBI.BigInt(0), JSBI.BigInt(100)))).toEqual(
new CurrencyAmount(token2, JSBI.BigInt(69))
CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(69))
)
expect(exactIn.minimumAmountOut(new Percent(JSBI.BigInt(5), JSBI.BigInt(100)))).toEqual(
new CurrencyAmount(token2, JSBI.BigInt(65))
CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(65))
)
expect(exactIn.minimumAmountOut(new Percent(JSBI.BigInt(200), JSBI.BigInt(100)))).toEqual(
new CurrencyAmount(token2, JSBI.BigInt(23))
CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(23))
)
})
})
describe('tradeType = EXACT_OUTPUT', () => {
const exactOut = new Trade(
new Route([pair_0_1, pair_1_2], token0),
new CurrencyAmount(token2, JSBI.BigInt(100)),
new Route([pair_0_1, pair_1_2], token0, token2),
CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(100)),
TradeType.EXACT_OUTPUT
)
......@@ -283,13 +290,13 @@ describe('Trade', () => {
})
it('returns slippage amount if nonzero', () => {
expect(exactOut.minimumAmountOut(new Percent(JSBI.BigInt(0), JSBI.BigInt(100)))).toEqual(
new CurrencyAmount(token2, JSBI.BigInt(100))
CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(100))
)
expect(exactOut.minimumAmountOut(new Percent(JSBI.BigInt(5), JSBI.BigInt(100)))).toEqual(
new CurrencyAmount(token2, JSBI.BigInt(100))
CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(100))
)
expect(exactOut.minimumAmountOut(new Percent(JSBI.BigInt(200), JSBI.BigInt(100)))).toEqual(
new CurrencyAmount(token2, JSBI.BigInt(100))
CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(100))
)
})
})
......@@ -298,8 +305,8 @@ describe('Trade', () => {
describe('#worstExecutionPrice', () => {
describe('tradeType = EXACT_INPUT', () => {
const exactIn = new Trade(
new Route([pair_0_1, pair_1_2], token0),
new CurrencyAmount(token0, 100),
new Route([pair_0_1, pair_1_2], token0, token2),
CurrencyAmount.fromRawAmount(token0, 100),
TradeType.EXACT_INPUT
)
it('throws if less than 0', () => {
......@@ -316,8 +323,8 @@ describe('Trade', () => {
})
describe('tradeType = EXACT_OUTPUT', () => {
const exactOut = new Trade(
new Route([pair_0_1, pair_1_2], token0),
new CurrencyAmount(token2, 100),
new Route([pair_0_1, pair_1_2], token0, token2),
CurrencyAmount.fromRawAmount(token2, 100),
TradeType.EXACT_OUTPUT
)
......@@ -337,11 +344,15 @@ describe('Trade', () => {
describe('#bestTradeExactOut', () => {
it('throws with empty pairs', () => {
expect(() => Trade.bestTradeExactOut([], token0, new CurrencyAmount(token2, JSBI.BigInt(100)))).toThrow('PAIRS')
expect(() => Trade.bestTradeExactOut([], token0, CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(100)))).toThrow(
'PAIRS'
)
})
it('throws with max hops of 0', () => {
expect(() =>
Trade.bestTradeExactOut([pair_0_2], token0, new CurrencyAmount(token2, JSBI.BigInt(100)), { maxHops: 0 })
Trade.bestTradeExactOut([pair_0_2], token0, CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(100)), {
maxHops: 0
})
).toThrow('MAX_HOPS')
})
......@@ -349,22 +360,22 @@ describe('Trade', () => {
const result = Trade.bestTradeExactOut(
[pair_0_1, pair_0_2, pair_1_2],
token0,
new CurrencyAmount(token2, JSBI.BigInt(100))
CurrencyAmount.fromRawAmount(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 CurrencyAmount(token0, JSBI.BigInt(101)))
expect(result[0].outputAmount).toEqual(new CurrencyAmount(token2, JSBI.BigInt(100)))
expect(result[0].inputAmount).toEqual(CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(101)))
expect(result[0].outputAmount).toEqual(CurrencyAmount.fromRawAmount(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 CurrencyAmount(token0, JSBI.BigInt(156)))
expect(result[1].outputAmount).toEqual(new CurrencyAmount(token2, JSBI.BigInt(100)))
expect(result[1].inputAmount).toEqual(CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(156)))
expect(result[1].outputAmount).toEqual(CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(100)))
})
it('doesnt throw for zero liquidity pairs', () => {
expect(
Trade.bestTradeExactOut([empty_pair_0_1], token1, new CurrencyAmount(token1, JSBI.BigInt(100)))
Trade.bestTradeExactOut([empty_pair_0_1], token1, CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(100)))
).toHaveLength(0)
})
......@@ -372,7 +383,7 @@ describe('Trade', () => {
const result = Trade.bestTradeExactOut(
[pair_0_1, pair_0_2, pair_1_2],
token0,
new CurrencyAmount(token2, JSBI.BigInt(10)),
CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(10)),
{ maxHops: 1 }
)
expect(result).toHaveLength(1)
......@@ -384,7 +395,7 @@ describe('Trade', () => {
const result = Trade.bestTradeExactOut(
[pair_0_1, pair_0_2, pair_1_2],
token0,
new CurrencyAmount(token2, JSBI.BigInt(1200))
CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(1200))
)
expect(result).toHaveLength(0)
})
......@@ -393,7 +404,7 @@ describe('Trade', () => {
const result = Trade.bestTradeExactOut(
[pair_0_1, pair_0_2, pair_1_2],
token0,
new CurrencyAmount(token2, JSBI.BigInt(1050))
CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(1050))
)
expect(result).toHaveLength(1)
})
......@@ -402,7 +413,7 @@ describe('Trade', () => {
const result = Trade.bestTradeExactOut(
[pair_0_1, pair_0_2, pair_1_2],
token0,
new CurrencyAmount(token2, JSBI.BigInt(10)),
CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(10)),
{ maxNumResults: 1 }
)
......@@ -413,7 +424,7 @@ describe('Trade', () => {
const result = Trade.bestTradeExactOut(
[pair_0_1, pair_0_3, pair_1_3],
token0,
new CurrencyAmount(token2, JSBI.BigInt(10))
CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(10))
)
expect(result).toHaveLength(0)
})
......@@ -422,7 +433,7 @@ describe('Trade', () => {
const result = Trade.bestTradeExactOut(
[pair_weth_0, pair_0_1, pair_0_3, pair_1_3],
ETHER,
new CurrencyAmount(token3, JSBI.BigInt(100))
CurrencyAmount.fromRawAmount(token3, JSBI.BigInt(100))
)
expect(result).toHaveLength(2)
expect(result[0].inputAmount.currency).toEqual(ETHER)
......
......@@ -3,43 +3,33 @@ import {
Currency,
CurrencyAmount,
currencyEquals,
ETHER,
Fraction,
Percent,
Price,
sortedInsert,
Token,
wrappedCurrency,
TradeType,
WETH9
wrappedCurrencyAmount
} from '@uniswap/sdk-core'
import { computePriceImpact, Token } from '../../../../sdk-core'
import { ONE, ZERO } from '../constants'
import invariant from 'tiny-invariant'
import { Pair } from './pair'
import { Route } from './route'
/**
* Returns the percent difference between the mid price and the execution price, i.e. price impact.
* @param midPrice mid price before the trade
* @param inputAmount the input amount of the trade
* @param outputAmount the output amount of the trade
*/
function computePriceImpact(midPrice: Price, inputAmount: CurrencyAmount, outputAmount: CurrencyAmount): Percent {
const exactQuote = midPrice.raw.multiply(inputAmount.raw)
// calculate slippage := (exactQuote - outputAmount) / exactQuote
const slippage = exactQuote.subtract(outputAmount.raw).divide(exactQuote)
return new Percent(slippage.numerator, slippage.denominator)
}
// minimal interface so the input output comparator may be shared across types
interface InputOutput {
readonly inputAmount: CurrencyAmount
readonly outputAmount: CurrencyAmount
interface InputOutput<TInput extends Currency, TOutput extends Currency> {
readonly inputAmount: CurrencyAmount<TInput>
readonly outputAmount: CurrencyAmount<TOutput>
}
// 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(a: InputOutput, b: InputOutput): number {
export function inputOutputComparator<TInput extends Currency, TOutput extends Currency>(
a: InputOutput<TInput, TOutput>,
b: InputOutput<TInput, TOutput>
): number {
// must have same input and output token for comparison
invariant(currencyEquals(a.inputAmount.currency, b.inputAmount.currency), 'INPUT_CURRENCY')
invariant(currencyEquals(a.outputAmount.currency, b.outputAmount.currency), 'OUTPUT_CURRENCY')
......@@ -64,7 +54,10 @@ export function inputOutputComparator(a: InputOutput, b: InputOutput): number {
}
// extension of the input output comparator that also considers other dimensions of the trade in ranking them
export function tradeComparator(a: Trade, b: Trade) {
export function tradeComparator<TInput extends Currency, TOutput extends Currency, TTradeType extends TradeType>(
a: Trade<TInput, TOutput, TTradeType>,
b: Trade<TInput, TOutput, TTradeType>
) {
const ioComp = inputOutputComparator(a, b)
if (ioComp !== 0) {
return ioComp
......@@ -88,52 +81,31 @@ export interface BestTradeOptions {
maxHops?: number
}
/**
* Given a currency amount and a chain ID, returns the equivalent representation as the token amount.
* In other words, if the currency is ETHER, returns the WETH9 token amount for the given chain. Otherwise, returns
* the input currency amount.
*/
function wrappedAmount(currencyAmount: CurrencyAmount, chainId: ChainId): CurrencyAmount {
if (currencyAmount.currency.isToken) return currencyAmount
if (currencyAmount.currency.isEther) return new CurrencyAmount(WETH9[chainId], currencyAmount.raw)
throw new Error('CURRENCY')
}
function wrappedCurrency(currency: Currency, chainId: ChainId): Token {
if (currency.isToken) return currency
if (currency === ETHER) return WETH9[chainId]
throw new Error('CURRENCY')
}
/**
* Represents a trade executed against a list of pairs.
* Does not account for slippage, i.e. trades that front run this trade and move the price.
*/
export class Trade {
export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType extends TradeType> {
/**
* The route of the trade, i.e. which pairs the trade goes through.
* The route of the trade, i.e. which pairs the trade goes through and the input/output currencies.
*/
public readonly route: Route
public readonly route: Route<TInput, TOutput>
/**
* The type of the trade, either exact in or exact out.
*/
public readonly tradeType: TradeType
public readonly tradeType: TTradeType
/**
* The input amount for the trade assuming no slippage.
*/
public readonly inputAmount: CurrencyAmount
public readonly inputAmount: CurrencyAmount<TInput>
/**
* The output amount for the trade assuming no slippage.
*/
public readonly outputAmount: CurrencyAmount
public readonly outputAmount: CurrencyAmount<TOutput>
/**
* The price expressed in terms of output amount/input amount.
*/
public readonly executionPrice: Price
/**
* The mid price after the trade executes assuming no slippage.
*/
public readonly nextMidPrice: Price
public readonly executionPrice: Price<TInput, TOutput>
/**
* The percent difference between the mid price before the trade and the trade execution price.
*/
......@@ -144,7 +116,10 @@ export class Trade {
* @param route route of the exact in trade
* @param amountIn the amount being passed in
*/
public static exactIn(route: Route, amountIn: CurrencyAmount): Trade {
public static exactIn<TInput extends Currency, TOutput extends Currency>(
route: Route<TInput, TOutput>,
amountIn: CurrencyAmount<TInput>
): Trade<TInput, TOutput, TradeType.EXACT_INPUT> {
return new Trade(route, amountIn, TradeType.EXACT_INPUT)
}
......@@ -153,54 +128,57 @@ export class Trade {
* @param route route of the exact out trade
* @param amountOut the amount returned by the trade
*/
public static exactOut(route: Route, amountOut: CurrencyAmount): Trade {
public static exactOut<TInput extends Currency, TOutput extends Currency>(
route: Route<TInput, TOutput>,
amountOut: CurrencyAmount<TOutput>
): Trade<TInput, TOutput, TradeType.EXACT_OUTPUT> {
return new Trade(route, amountOut, TradeType.EXACT_OUTPUT)
}
public constructor(route: Route, amount: CurrencyAmount, tradeType: TradeType) {
const amounts: CurrencyAmount[] = new Array(route.path.length)
const nextPairs: Pair[] = new Array(route.pairs.length)
public constructor(
route: Route<TInput, TOutput>,
amount: TTradeType extends TradeType.EXACT_INPUT ? CurrencyAmount<TInput> : CurrencyAmount<TOutput>,
tradeType: TTradeType
) {
this.route = route
this.tradeType = tradeType
const tokenAmounts: CurrencyAmount<Token>[] = new Array(route.path.length)
if (tradeType === TradeType.EXACT_INPUT) {
invariant(currencyEquals(amount.currency, route.input), 'INPUT')
amounts[0] = wrappedAmount(amount, route.chainId)
tokenAmounts[0] = wrappedCurrencyAmount(amount, route.chainId)
for (let i = 0; i < route.path.length - 1; i++) {
const pair = route.pairs[i]
const [outputAmount, nextPair] = pair.getOutputAmount(amounts[i])
amounts[i + 1] = outputAmount
nextPairs[i] = nextPair
const [outputAmount] = pair.getOutputAmount(tokenAmounts[i])
tokenAmounts[i + 1] = outputAmount
}
this.inputAmount = CurrencyAmount.fromFractionalAmount(route.input, amount.numerator, amount.denominator)
this.outputAmount = CurrencyAmount.fromFractionalAmount(
route.output,
tokenAmounts[tokenAmounts.length - 1].numerator,
tokenAmounts[tokenAmounts.length - 1].denominator
)
} else {
invariant(currencyEquals(amount.currency, route.output), 'OUTPUT')
amounts[amounts.length - 1] = wrappedAmount(amount, route.chainId)
tokenAmounts[tokenAmounts.length - 1] = wrappedCurrencyAmount(amount, route.chainId)
for (let i = route.path.length - 1; i > 0; i--) {
const pair = route.pairs[i - 1]
const [inputAmount, nextPair] = pair.getInputAmount(amounts[i])
amounts[i - 1] = inputAmount
nextPairs[i - 1] = nextPair
const [inputAmount] = pair.getInputAmount(tokenAmounts[i])
tokenAmounts[i - 1] = inputAmount
}
this.inputAmount = CurrencyAmount.fromFractionalAmount(
route.input,
tokenAmounts[0].numerator,
tokenAmounts[0].denominator
)
this.outputAmount = CurrencyAmount.fromFractionalAmount(route.output, amount.numerator, amount.denominator)
}
this.route = route
this.tradeType = tradeType
this.inputAmount =
tradeType === TradeType.EXACT_INPUT
? amount
: route.input === ETHER
? CurrencyAmount.ether(amounts[0].raw)
: amounts[0]
this.outputAmount =
tradeType === TradeType.EXACT_OUTPUT
? amount
: route.output === ETHER
? CurrencyAmount.ether(amounts[amounts.length - 1].raw)
: amounts[amounts.length - 1]
this.executionPrice = new Price(
this.inputAmount.currency,
this.outputAmount.currency,
this.inputAmount.raw,
this.outputAmount.raw
this.inputAmount.quotient,
this.outputAmount.quotient
)
this.nextMidPrice = new Route(nextPairs, route.input).midPrice
this.priceImpact = computePriceImpact(route.midPrice, this.inputAmount, this.outputAmount)
}
......@@ -208,7 +186,7 @@ export class Trade {
* Get the minimum amount that must be received from this trade for the given slippage tolerance
* @param slippageTolerance tolerance of unfavorable slippage from the execution price of this trade
*/
public minimumAmountOut(slippageTolerance: Percent): CurrencyAmount {
public minimumAmountOut(slippageTolerance: Percent): CurrencyAmount<TOutput> {
invariant(!slippageTolerance.lessThan(ZERO), 'SLIPPAGE_TOLERANCE')
if (this.tradeType === TradeType.EXACT_OUTPUT) {
return this.outputAmount
......@@ -216,8 +194,8 @@ export class Trade {
const slippageAdjustedAmountOut = new Fraction(ONE)
.add(slippageTolerance)
.invert()
.multiply(this.outputAmount.raw).quotient
return new CurrencyAmount(this.outputAmount.currency, slippageAdjustedAmountOut)
.multiply(this.outputAmount.quotient).quotient
return CurrencyAmount.fromRawAmount(this.outputAmount.currency, slippageAdjustedAmountOut)
}
}
......@@ -225,13 +203,14 @@ export class Trade {
* Get the maximum amount in that can be spent via this trade for the given slippage tolerance
* @param slippageTolerance tolerance of unfavorable slippage from the execution price of this trade
*/
public maximumAmountIn(slippageTolerance: Percent): CurrencyAmount {
public maximumAmountIn(slippageTolerance: Percent): CurrencyAmount<TInput> {
invariant(!slippageTolerance.lessThan(ZERO), 'SLIPPAGE_TOLERANCE')
if (this.tradeType === TradeType.EXACT_INPUT) {
return this.inputAmount
} else {
const slippageAdjustedAmountIn = new Fraction(ONE).add(slippageTolerance).multiply(this.inputAmount.raw).quotient
return new CurrencyAmount(this.inputAmount.currency, slippageAdjustedAmountIn)
const slippageAdjustedAmountIn = new Fraction(ONE).add(slippageTolerance).multiply(this.inputAmount.quotient)
.quotient
return CurrencyAmount.fromRawAmount(this.inputAmount.currency, slippageAdjustedAmountIn)
}
}
......@@ -241,35 +220,36 @@ export class Trade {
* 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.
* @param pairs the pairs to consider in finding the best trade
* @param currencyAmountIn exact amount of input currency to spend
* @param nextAmountIn exact amount of input currency to spend
* @param currencyOut the desired currency out
* @param maxNumResults maximum number of results to return
* @param maxHops maximum number of hops a returned trade can make, e.g. 1 hop goes through a single pair
* @param currentPairs used in recursion; the current list of pairs
* @param originalAmountIn used in recursion; the original value of the currencyAmountIn parameter
* @param currencyAmountIn used in recursion; the original value of the currencyAmountIn parameter
* @param bestTrades used in recursion; the current list of best trades
*/
public static bestTradeExactIn(
public static bestTradeExactIn<TInput extends Currency, TOutput extends Currency>(
pairs: Pair[],
currencyAmountIn: CurrencyAmount,
currencyOut: Currency,
currencyAmountIn: CurrencyAmount<TInput>,
currencyOut: TOutput,
{ maxNumResults = 3, maxHops = 3 }: BestTradeOptions = {},
// used in recursion.
currentPairs: Pair[] = [],
originalAmountIn: CurrencyAmount = currencyAmountIn,
bestTrades: Trade[] = []
): Trade[] {
nextAmountIn: CurrencyAmount<Currency> = currencyAmountIn,
bestTrades: Trade<TInput, TOutput, TradeType.EXACT_INPUT>[] = []
): Trade<TInput, TOutput, TradeType.EXACT_INPUT>[] {
invariant(pairs.length > 0, 'PAIRS')
invariant(maxHops > 0, 'MAX_HOPS')
invariant(originalAmountIn === currencyAmountIn || currentPairs.length > 0, 'INVALID_RECURSION')
const chainId: ChainId | undefined = currencyAmountIn.currency.isToken
? currencyAmountIn.currency.chainId
invariant(currencyAmountIn === nextAmountIn || currentPairs.length > 0, 'INVALID_RECURSION')
const chainId: ChainId | undefined = nextAmountIn.currency.isToken
? nextAmountIn.currency.chainId
: currencyOut.isToken
? currencyOut.chainId
? (currencyOut as Token).chainId
: undefined
invariant(chainId !== undefined, 'CHAIN_ID')
const amountIn = wrappedAmount(currencyAmountIn, chainId)
const amountIn = wrappedCurrencyAmount(nextAmountIn, chainId)
const tokenOut = wrappedCurrency(currencyOut, chainId)
for (let i = 0; i < pairs.length; i++) {
const pair = pairs[i]
......@@ -277,7 +257,7 @@ export class Trade {
if (!currencyEquals(pair.token0, amountIn.currency) && !currencyEquals(pair.token1, amountIn.currency)) continue
if (pair.reserve0.equalTo(ZERO) || pair.reserve1.equalTo(ZERO)) continue
let amountOut: CurrencyAmount
let amountOut: CurrencyAmount<Token>
try {
;[amountOut] = pair.getOutputAmount(amountIn)
} catch (error) {
......@@ -292,8 +272,8 @@ export class Trade {
sortedInsert(
bestTrades,
new Trade(
new Route([...currentPairs, pair], originalAmountIn.currency, currencyOut),
originalAmountIn,
new Route([...currentPairs, pair], currencyAmountIn.currency, currencyOut),
currencyAmountIn,
TradeType.EXACT_INPUT
),
maxNumResults,
......@@ -305,14 +285,14 @@ export class Trade {
// otherwise, consider all the other paths that lead from this token as long as we have not exceeded maxHops
Trade.bestTradeExactIn(
pairsExcludingThisPair,
amountOut,
currencyAmountIn,
currencyOut,
{
maxNumResults,
maxHops: maxHops - 1
},
[...currentPairs, pair],
originalAmountIn,
amountOut,
bestTrades
)
}
......@@ -325,12 +305,12 @@ export class Trade {
* Return the execution price after accounting for slippage tolerance
* @param slippageTolerance the allowed tolerated slippage
*/
public worstExecutionPrice(slippageTolerance: Percent): Price {
public worstExecutionPrice(slippageTolerance: Percent): Price<TInput, TOutput> {
return new Price(
this.inputAmount.currency,
this.outputAmount.currency,
this.maximumAmountIn(slippageTolerance).raw,
this.minimumAmountOut(slippageTolerance).raw
this.maximumAmountIn(slippageTolerance).quotient,
this.minimumAmountOut(slippageTolerance).quotient
)
}
......@@ -342,34 +322,34 @@ export class Trade {
* the amount in among multiple routes.
* @param pairs the pairs to consider in finding the best trade
* @param currencyIn the currency to spend
* @param currencyAmountOut the exact amount of currency out
* @param nextAmountOut the exact amount of currency out
* @param maxNumResults maximum number of results to return
* @param maxHops maximum number of hops a returned trade can make, e.g. 1 hop goes through a single pair
* @param currentPairs used in recursion; the current list of pairs
* @param originalAmountOut used in recursion; the original value of the currencyAmountOut parameter
* @param currencyAmountOut used in recursion; the original value of the currencyAmountOut parameter
* @param bestTrades used in recursion; the current list of best trades
*/
public static bestTradeExactOut(
public static bestTradeExactOut<TInput extends Currency, TOutput extends Currency>(
pairs: Pair[],
currencyIn: Currency,
currencyAmountOut: CurrencyAmount,
currencyIn: TInput,
currencyAmountOut: CurrencyAmount<TOutput>,
{ maxNumResults = 3, maxHops = 3 }: BestTradeOptions = {},
// used in recursion.
currentPairs: Pair[] = [],
originalAmountOut: CurrencyAmount = currencyAmountOut,
bestTrades: Trade[] = []
): Trade[] {
nextAmountOut: CurrencyAmount<Currency> = currencyAmountOut,
bestTrades: Trade<TInput, TOutput, TradeType.EXACT_OUTPUT>[] = []
): Trade<TInput, TOutput, TradeType.EXACT_OUTPUT>[] {
invariant(pairs.length > 0, 'PAIRS')
invariant(maxHops > 0, 'MAX_HOPS')
invariant(originalAmountOut === currencyAmountOut || currentPairs.length > 0, 'INVALID_RECURSION')
const chainId: ChainId | undefined = currencyAmountOut.currency.isToken
? currencyAmountOut.currency.chainId
invariant(currencyAmountOut === nextAmountOut || currentPairs.length > 0, 'INVALID_RECURSION')
const chainId: ChainId | undefined = nextAmountOut.currency.isToken
? nextAmountOut.currency.chainId
: currencyIn.isToken
? currencyIn.chainId
? (currencyIn as Token).chainId
: undefined
invariant(chainId !== undefined, 'CHAIN_ID')
const amountOut = wrappedAmount(currencyAmountOut, chainId)
const amountOut = wrappedCurrencyAmount(nextAmountOut, chainId)
const tokenIn = wrappedCurrency(currencyIn, chainId)
for (let i = 0; i < pairs.length; i++) {
const pair = pairs[i]
......@@ -377,7 +357,7 @@ export class Trade {
if (!currencyEquals(pair.token0, amountOut.currency) && !currencyEquals(pair.token1, amountOut.currency)) continue
if (pair.reserve0.equalTo(ZERO) || pair.reserve1.equalTo(ZERO)) continue
let amountIn: CurrencyAmount
let amountIn: CurrencyAmount<Token>
try {
;[amountIn] = pair.getInputAmount(amountOut)
} catch (error) {
......@@ -392,8 +372,8 @@ export class Trade {
sortedInsert(
bestTrades,
new Trade(
new Route([pair, ...currentPairs], currencyIn, originalAmountOut.currency),
originalAmountOut,
new Route([pair, ...currentPairs], currencyIn, currencyAmountOut.currency),
currencyAmountOut,
TradeType.EXACT_OUTPUT
),
maxNumResults,
......@@ -406,13 +386,13 @@ export class Trade {
Trade.bestTradeExactOut(
pairsExcludingThisPair,
currencyIn,
amountIn,
currencyAmountOut,
{
maxNumResults,
maxHops: maxHops - 1
},
[pair, ...currentPairs],
originalAmountOut,
amountIn,
bestTrades
)
}
......
import JSBI from 'jsbi'
export { JSBI }
export { FACTORY_ADDRESS, INIT_CODE_HASH, MINIMUM_LIQUIDITY } from './constants'
export * from './errors'
......
......@@ -16,15 +16,18 @@ describe('Router', () => {
const token1 = new Token(ChainId.MAINNET, '0x0000000000000000000000000000000000000002', 18, 't1')
const pair_0_1 = new Pair(
new CurrencyAmount(token0, JSBI.BigInt(1000)),
new CurrencyAmount(token1, JSBI.BigInt(1000))
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(1000)),
CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(1000))
)
const pair_weth_0 = new Pair(new CurrencyAmount(WETH9[ChainId.MAINNET], '1000'), new CurrencyAmount(token0, '1000'))
const pair_weth_0 = new Pair(
CurrencyAmount.fromRawAmount(WETH9[ChainId.MAINNET], '1000'),
CurrencyAmount.fromRawAmount(token0, '1000')
)
describe('#swapCallParameters', () => {
describe('exact in', () => {
it('ether to token1', () => {
it.only('ether to token1', () => {
const result = Router.swapCallParameters(
Trade.exactIn(new Route([pair_weth_0, pair_0_1], ETHER, token1), CurrencyAmount.ether(JSBI.BigInt(100))),
{ ttl: 50, recipient: '0x0000000000000000000000000000000000000004', allowedSlippage: new Percent('1', '100') }
......@@ -62,7 +65,7 @@ describe('Router', () => {
const result = Router.swapCallParameters(
Trade.exactIn(
new Route([pair_0_1, pair_weth_0], token1, ETHER),
new CurrencyAmount(token1, JSBI.BigInt(100))
CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(100))
),
{ ttl: 50, recipient: '0x0000000000000000000000000000000000000004', allowedSlippage: new Percent('1', '100') }
)
......@@ -78,7 +81,7 @@ describe('Router', () => {
})
it('token0 to token1', () => {
const result = Router.swapCallParameters(
Trade.exactIn(new Route([pair_0_1], token0, token1), new CurrencyAmount(token0, JSBI.BigInt(100))),
Trade.exactIn(new Route([pair_0_1], token0, token1), CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100))),
{ ttl: 50, recipient: '0x0000000000000000000000000000000000000004', allowedSlippage: new Percent('1', '100') }
)
expect(result.methodName).toEqual('swapExactTokensForTokens')
......@@ -97,7 +100,7 @@ describe('Router', () => {
const result = Router.swapCallParameters(
Trade.exactOut(
new Route([pair_weth_0, pair_0_1], ETHER, token1),
new CurrencyAmount(token1, JSBI.BigInt(100))
CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(100))
),
{ ttl: 50, recipient: '0x0000000000000000000000000000000000000004', allowedSlippage: new Percent('1', '100') }
)
......@@ -127,7 +130,7 @@ describe('Router', () => {
})
it('token0 to token1', () => {
const result = Router.swapCallParameters(
Trade.exactOut(new Route([pair_0_1], token0, token1), new CurrencyAmount(token1, JSBI.BigInt(100))),
Trade.exactOut(new Route([pair_0_1], token0, token1), CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(100))),
{ ttl: 50, recipient: '0x0000000000000000000000000000000000000004', allowedSlippage: new Percent('1', '100') }
)
expect(result.methodName).toEqual('swapTokensForExactTokens')
......@@ -166,7 +169,7 @@ describe('Router', () => {
const result = Router.swapCallParameters(
Trade.exactIn(
new Route([pair_0_1, pair_weth_0], token1, ETHER),
new CurrencyAmount(token1, JSBI.BigInt(100))
CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(100))
),
{
ttl: 50,
......@@ -187,7 +190,10 @@ describe('Router', () => {
})
it('token0 to token1', () => {
const result = Router.swapCallParameters(
Trade.exactIn(new Route([pair_0_1], token0, token1), new CurrencyAmount(token0, JSBI.BigInt(100))),
Trade.exactIn(
new Route([pair_0_1], token0, token1),
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100))
),
{
ttl: 50,
recipient: '0x0000000000000000000000000000000000000004',
......@@ -212,7 +218,7 @@ describe('Router', () => {
Router.swapCallParameters(
Trade.exactOut(
new Route([pair_weth_0, pair_0_1], ETHER, token1),
new CurrencyAmount(token1, JSBI.BigInt(100))
CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(100))
),
{
ttl: 50,
......@@ -239,7 +245,10 @@ describe('Router', () => {
it('token0 to token1', () => {
expect(() =>
Router.swapCallParameters(
Trade.exactOut(new Route([pair_0_1], token0, token1), new CurrencyAmount(token1, JSBI.BigInt(100))),
Trade.exactOut(
new Route([pair_0_1], token0, token1),
CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(100))
),
{
ttl: 50,
recipient: '0x0000000000000000000000000000000000000004',
......
import { CurrencyAmount, ETHER, Percent, TradeType, validateAndParseAddress } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, Percent, TradeType, validateAndParseAddress } from '@uniswap/sdk-core'
import { Trade } from 'entities'
import invariant from 'tiny-invariant'
import { Token } from '../../../sdk-core'
/**
* Options for producing the arguments to send call to the router.
......@@ -53,8 +54,8 @@ export interface SwapParameters {
value: string
}
function toHex(currencyAmount: CurrencyAmount) {
return `0x${currencyAmount.raw.toString(16)}`
function toHex(currencyAmount: CurrencyAmount<Currency>) {
return `0x${currencyAmount.quotient.toString(16)}`
}
const ZERO_HEX = '0x0'
......@@ -72,9 +73,12 @@ export abstract class Router {
* @param trade to produce call parameters for
* @param options options for the call parameters
*/
public static swapCallParameters(trade: Trade, options: TradeOptions | TradeOptionsDeadline): SwapParameters {
const etherIn = trade.inputAmount.currency === ETHER
const etherOut = trade.outputAmount.currency === ETHER
public static swapCallParameters(
trade: Trade<Currency, Currency, TradeType>,
options: TradeOptions | TradeOptionsDeadline
): SwapParameters {
const etherIn = trade.inputAmount.currency.isEther
const etherOut = trade.outputAmount.currency.isEther
// the router does not support both ether in and out
invariant(!(etherIn && etherOut), 'ETHER_IN_OUT')
invariant(!('ttl' in options) || options.ttl > 0, 'TTL')
......@@ -82,7 +86,7 @@ export abstract class Router {
const to: string = validateAndParseAddress(options.recipient)
const amountIn: string = toHex(trade.maximumAmountIn(options.allowedSlippage))
const amountOut: string = toHex(trade.minimumAmountOut(options.allowedSlippage))
const path: string[] = trade.route.path.map(token => token.address)
const path: string[] = trade.route.path.map((token: Token) => token.address)
const deadline =
'ttl' in options
? `0x${(Math.floor(new Date().getTime() / 1000) + options.ttl).toString(16)}`
......
......@@ -1706,10 +1706,10 @@
semver "^7.3.2"
tsutils "^3.17.1"
"@uniswap/sdk-core@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-2.0.2.tgz#748d1d189503d20d3027ef69927ad13cbf2d224a"
integrity sha512-Cx6epJgXE/b9ZP8GAes3LiYFXuxfd7UDZtn8Wvxr6xEMh8T21HHbsoEs9sB8iCBYfYuw2PT+Pza4GJqQNvnddg==
"@uniswap/sdk-core@^3.0.0-alpha.0":
version "3.0.0-alpha.0"
resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-3.0.0-alpha.0.tgz#03f3515bc4b735c4d04b4a49e9518b2ea023a707"
integrity sha512-42K3bYBYVdf45E5ek4GcaIkbW3eTkcpGYAufN7rw+LRsvnb5HlbF3FF0NxUP2WwLcyTe1DImHupIROUmCrC61A==
dependencies:
"@ethersproject/address" "^5.0.2"
big.js "^5.2.2"
......
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