Skip to content

Commit

Permalink
Add methods for minimum amount out and maximum amount in for easy cal…
Browse files Browse the repository at this point in the history
…culation of transaction parameters (Uniswap#23)
  • Loading branch information
moodysalem authored May 11, 2020
1 parent e5b779f commit 3f22d17
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 7 deletions.
41 changes: 35 additions & 6 deletions src/entities/trade.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Token } from 'entities/token'
import invariant from 'tiny-invariant'

import { TradeType } from '../constants'
import { ONE, TradeType, ZERO } from '../constants'
import { sortedInsert } from '../utils'
import { Fraction, TokenAmount } from './fractions'
import { Percent } from './fractions/percent'
import { Price } from './fractions/price'
import { Pair } from './pair'
import { Route } from './route'
import { TokenAmount } from './fractions'
import { Price } from './fractions/price'
import { Percent } from './fractions/percent'
import { Token } from 'entities/token'
import { sortedInsert } from '../utils'

function getSlippage(midPrice: Price, inputAmount: TokenAmount, outputAmount: TokenAmount): Percent {
const exactQuote = midPrice.raw.multiply(inputAmount.raw)
Expand Down Expand Up @@ -115,6 +115,35 @@ export class Trade {
this.slippage = getSlippage(route.midPrice, inputAmount, outputAmount)
}

// get the minimum amount that must be received from this trade for the given allowed slippage
public minimumAmountOut(additionalSlippageTolerance: Percent): TokenAmount {
invariant(!additionalSlippageTolerance.lessThan(ZERO), 'ADDITIONAL_SLIPPAGE_TOLERANCE')
if (this.tradeType === TradeType.EXACT_OUTPUT) {
return this.outputAmount
} else {
return new TokenAmount(
this.outputAmount.token,
new Fraction(ONE)
.add(additionalSlippageTolerance)
.invert()
.multiply(this.outputAmount.raw).quotient
)
}
}

// get the maximum amount in that can be spent via this trade for the given allowed slippage
public maximumAmountIn(additionalSlippageTolerance: Percent): TokenAmount {
invariant(!additionalSlippageTolerance.lessThan(ZERO), 'ADDITIONAL_SLIPPAGE_TOLERANCE')
if (this.tradeType === TradeType.EXACT_INPUT) {
return this.inputAmount
} else {
return new TokenAmount(
this.inputAmount.token,
new Fraction(ONE).add(additionalSlippageTolerance).multiply(this.inputAmount.raw).quotient
)
}
}

// given a list of pairs, and a fixed amount in, returns the top `maxNumResults` trades that go from an input token
// amount to an output token, making at most `maxHops` hops
// note this does not consider aggregation, as routes are linear. it's possible a better route exists by splitting
Expand Down
114 changes: 113 additions & 1 deletion test/trade.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ChainId, Token, TokenAmount, Pair, Trade } from '../src'
import JSBI from 'jsbi'
import { ChainId, Pair, Percent, Route, Token, TokenAmount, Trade, TradeType } from '../src'

describe('Trade', () => {
const token0 = new Token(ChainId.MAINNET, '0x0000000000000000000000000000000000000001', 18, 't0')
Expand Down Expand Up @@ -85,6 +85,118 @@ describe('Trade', () => {
})
})

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(
'ADDITIONAL_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(
'ADDITIONAL_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(
'ADDITIONAL_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(
'ADDITIONAL_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')
Expand Down

0 comments on commit 3f22d17

Please sign in to comment.