Skip to content

Commit

Permalink
refactor ticklist into a utility
Browse files Browse the repository at this point in the history
  • Loading branch information
moodysalem committed Apr 9, 2021
1 parent cfa3150 commit 514a639
Show file tree
Hide file tree
Showing 14 changed files with 376 additions and 322 deletions.
1 change: 0 additions & 1 deletion src/entities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@ export * from './pool'
export * from './position'
export * from './route'
export * from './tick'
export * from './tickList'
export * from './trade'
79 changes: 33 additions & 46 deletions src/entities/pool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { nearestUsableTick } from '../utils/nearestUsableTick'
import { TickMath } from '../utils/tickMath'
import { Pool } from './pool'
import { Tick } from './tick'
import { TickList } from './tickList'
import { encodeSqrtRatioX96 } from '../utils/encodeSqrtRatioX96'
import JSBI from 'jsbi'
import { NEGATIVE_ONE } from '../internalConstants'
Expand All @@ -14,39 +13,35 @@ const ONE_ETHER = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18))
describe('Pool', () => {
const USDC = new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD Coin')
const DAI = new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'DAI Stablecoin')
const tickMapDefault: TickList = new TickList([
new Tick({ index: -2, liquidityNet: 0, liquidityGross: 0 }),
new Tick({ index: 2, liquidityNet: 0, liquidityGross: 0 })
])

describe('constructor', () => {
it('cannot be used for tokens on different chains', () => {
expect(() => {
new Pool(USDC, WETH9[ChainId.RINKEBY], FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 0, new TickList([]))
new Pool(USDC, WETH9[ChainId.RINKEBY], FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 0, [])
}).toThrow('CHAIN_IDS')
})

it('fee must be integer', () => {
expect(() => {
new Pool(USDC, WETH9[ChainId.MAINNET], FeeAmount.MEDIUM + 0.5, encodeSqrtRatioX96(1, 1), 0, 0, new TickList([]))
new Pool(USDC, WETH9[ChainId.MAINNET], FeeAmount.MEDIUM + 0.5, encodeSqrtRatioX96(1, 1), 0, 0, [])
}).toThrow('FEE')
})

it('fee cannot be more than 1e6', () => {
expect(() => {
new Pool(USDC, WETH9[ChainId.MAINNET], 1e6, encodeSqrtRatioX96(1, 1), 0, 0, new TickList([]))
new Pool(USDC, WETH9[ChainId.MAINNET], 1e6, encodeSqrtRatioX96(1, 1), 0, 0, [])
}).toThrow('FEE')
})

it('cannot be given two of the same token', () => {
expect(() => {
new Pool(USDC, USDC, FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 0, new TickList([]))
new Pool(USDC, USDC, FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 0, [])
}).toThrow('ADDRESSES')
})

it('price must be within tick price bounds', () => {
expect(() => {
new Pool(USDC, WETH9[ChainId.MAINNET], FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 1, new TickList([]))
new Pool(USDC, WETH9[ChainId.MAINNET], FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 1, [])
}).toThrow('PRICE_BOUNDS')
expect(() => {
new Pool(
Expand All @@ -56,21 +51,21 @@ describe('Pool', () => {
JSBI.add(encodeSqrtRatioX96(1, 1), JSBI.BigInt(1)),
0,
-1,
new TickList([])
[]
)
}).toThrow('PRICE_BOUNDS')
})

it('works with valid arguments for empty pool medium fee', () => {
new Pool(USDC, WETH9[ChainId.MAINNET], FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 0, new TickList([]))
new Pool(USDC, WETH9[ChainId.MAINNET], FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 0, [])
})

it('works with valid arguments for empty pool low fee', () => {
new Pool(USDC, WETH9[ChainId.MAINNET], FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, new TickList([]))
new Pool(USDC, WETH9[ChainId.MAINNET], FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, [])
})

it('works with valid arguments for empty pool high fee', () => {
new Pool(USDC, WETH9[ChainId.MAINNET], FeeAmount.HIGH, encodeSqrtRatioX96(1, 1), 0, 0, new TickList([]))
new Pool(USDC, WETH9[ChainId.MAINNET], FeeAmount.HIGH, encodeSqrtRatioX96(1, 1), 0, 0, [])
})
})

Expand All @@ -83,17 +78,17 @@ describe('Pool', () => {

describe('#token0', () => {
it('always is the token that sorts before', () => {
let pool = new Pool(USDC, DAI, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, tickMapDefault)
let pool = new Pool(USDC, DAI, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, [])
expect(pool.token0).toEqual(DAI)
pool = new Pool(DAI, USDC, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, tickMapDefault)
pool = new Pool(DAI, USDC, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, [])
expect(pool.token0).toEqual(DAI)
})
})
describe('#token1', () => {
it('always is the token that sorts after', () => {
let pool = new Pool(USDC, DAI, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, tickMapDefault)
let pool = new Pool(USDC, DAI, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, [])
expect(pool.token1).toEqual(USDC)
pool = new Pool(DAI, USDC, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, tickMapDefault)
pool = new Pool(DAI, USDC, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, [])
expect(pool.token1).toEqual(USDC)
})
})
Expand All @@ -108,7 +103,7 @@ describe('Pool', () => {
encodeSqrtRatioX96(101e6, 100e18),
0,
TickMath.getTickAtSqrtRatio(encodeSqrtRatioX96(101e6, 100e18)),
tickMapDefault
[]
).token0Price.toSignificant(5)
).toEqual('1.01')
expect(
Expand All @@ -119,7 +114,7 @@ describe('Pool', () => {
encodeSqrtRatioX96(101e6, 100e18),
0,
TickMath.getTickAtSqrtRatio(encodeSqrtRatioX96(101e6, 100e18)),
tickMapDefault
[]
).token0Price.toSignificant(5)
).toEqual('1.01')
})
Expand All @@ -135,7 +130,7 @@ describe('Pool', () => {
encodeSqrtRatioX96(101e6, 100e18),
0,
TickMath.getTickAtSqrtRatio(encodeSqrtRatioX96(101e6, 100e18)),
tickMapDefault
[]
).token1Price.toSignificant(5)
).toEqual('0.9901')
expect(
Expand All @@ -146,14 +141,14 @@ describe('Pool', () => {
encodeSqrtRatioX96(101e6, 100e18),
0,
TickMath.getTickAtSqrtRatio(encodeSqrtRatioX96(101e6, 100e18)),
tickMapDefault
[]
).token1Price.toSignificant(5)
).toEqual('0.9901')
})
})

describe('#priceOf', () => {
const pool = new Pool(USDC, DAI, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, tickMapDefault)
const pool = new Pool(USDC, DAI, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, [])
it('returns price of token in terms of other token', () => {
expect(pool.priceOf(DAI)).toEqual(pool.token0Price)
expect(pool.priceOf(USDC)).toEqual(pool.token1Price)
Expand All @@ -166,15 +161,15 @@ describe('Pool', () => {

describe('#chainId', () => {
it('returns the token0 chainId', () => {
let pool = new Pool(USDC, DAI, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, tickMapDefault)
let pool = new Pool(USDC, DAI, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, [])
expect(pool.chainId).toEqual(ChainId.MAINNET)
pool = new Pool(DAI, USDC, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, tickMapDefault)
pool = new Pool(DAI, USDC, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, [])
expect(pool.chainId).toEqual(ChainId.MAINNET)
})
})

describe('#involvesToken', () => {
const pool = new Pool(USDC, DAI, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, tickMapDefault)
const pool = new Pool(USDC, DAI, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), 0, 0, [])
expect(pool.involvesToken(USDC)).toEqual(true)
expect(pool.involvesToken(DAI)).toEqual(true)
expect(pool.involvesToken(WETH9[ChainId.MAINNET])).toEqual(false)
Expand All @@ -184,26 +179,18 @@ describe('Pool', () => {
let pool: Pool

beforeEach(() => {
pool = new Pool(
USDC,
DAI,
FeeAmount.LOW,
encodeSqrtRatioX96(1, 1),
ONE_ETHER,
0,
new TickList([
new Tick({
index: nearestUsableTick(TickMath.MIN_TICK, TICK_SPACINGS[FeeAmount.LOW]),
liquidityNet: ONE_ETHER,
liquidityGross: ONE_ETHER
}),
new Tick({
index: nearestUsableTick(TickMath.MAX_TICK, TICK_SPACINGS[FeeAmount.LOW]),
liquidityNet: JSBI.multiply(ONE_ETHER, NEGATIVE_ONE),
liquidityGross: ONE_ETHER
})
])
)
pool = new Pool(USDC, DAI, FeeAmount.LOW, encodeSqrtRatioX96(1, 1), ONE_ETHER, 0, [
new Tick({
index: nearestUsableTick(TickMath.MIN_TICK, TICK_SPACINGS[FeeAmount.LOW]),
liquidityNet: ONE_ETHER,
liquidityGross: ONE_ETHER
}),
new Tick({
index: nearestUsableTick(TickMath.MAX_TICK, TICK_SPACINGS[FeeAmount.LOW]),
liquidityNet: JSBI.multiply(ONE_ETHER, NEGATIVE_ONE),
liquidityGross: ONE_ETHER
})
])
})

describe('#getOutputAmount', () => {
Expand Down
19 changes: 13 additions & 6 deletions src/entities/pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { computePoolAddress } from '../utils/computePoolAddress'
import { LiquidityMath } from '../utils/liquidityMath'
import { SwapMath } from '../utils/swapMath'
import { TickMath } from '../utils/tickMath'
import { TickList } from './tickList'
import { Tick } from './tick'
import { TickList } from '../utils/tickList'

interface StepComputations {
sqrtPriceStartX96: JSBI
Expand All @@ -26,7 +27,7 @@ export class Pool {
public readonly sqrtRatioX96: JSBI
public readonly liquidity: JSBI
public readonly tickCurrent: number
public readonly ticks: TickList
public readonly ticks: Tick[]

private _token0Price?: Price
private _token1Price?: Price
Expand All @@ -52,7 +53,7 @@ export class Pool {
sqrtRatioX96: BigintIsh,
liquidity: BigintIsh,
tickCurrent: number,
ticks: TickList
ticks: Tick[]
) {
invariant(Number.isInteger(fee) && fee < 1_000_000, 'FEE')

Expand All @@ -63,12 +64,13 @@ export class Pool {
JSBI.lessThanOrEqual(JSBI.BigInt(sqrtRatioX96), nextTickSqrtRatioX96),
'PRICE_BOUNDS'
)
TickList.validate(ticks, TICK_SPACINGS[fee])
;[this.token0, this.token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]
this.fee = fee
this.sqrtRatioX96 = JSBI.BigInt(sqrtRatioX96)
this.liquidity = JSBI.BigInt(liquidity)
this.tickCurrent = tickCurrent
this.ticks = ticks
this.ticks = ticks.slice() // create a copy since we want the pool's list to be immutable
}

/**
Expand Down Expand Up @@ -202,7 +204,12 @@ export class Pool {
// because each iteration of the while loop rounds, we can't optimize this code (relative to the smart contract)
// by simply traversing to the next available tick, we instead need to exactly replicate
// tickBitmap.nextInitializedTickWithinOneWord
;[step.tickNext, step.initialized] = this.ticks.nextInitializedTickWithinOneWord(state.tick, zeroForOne)
;[step.tickNext, step.initialized] = TickList.nextInitializedTickWithinOneWord(
this.ticks,
state.tick,
zeroForOne,
this.tickSpacing
)

if (step.tickNext < TickMath.MIN_TICK) {
step.tickNext = TickMath.MIN_TICK
Expand Down Expand Up @@ -238,7 +245,7 @@ export class Pool {
if (JSBI.equal(state.sqrtPriceX96, step.sqrtPriceNextX96)) {
// if the tick is initialized, run the tick transition
if (step.initialized) {
let liquidityNet = this.ticks.getTick(step.tickNext).liquidityNet
let liquidityNet = TickList.getTick(this.ticks, step.tickNext).liquidityNet
// if we're moving leftward, we interpret liquidityNet as the opposite sign
// safe because liquidityNet cannot be type(int128).min
if (zeroForOne) liquidityNet = JSBI.multiply(liquidityNet, NEGATIVE_ONE)
Expand Down
11 changes: 1 addition & 10 deletions src/entities/position.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,14 @@ import { nearestUsableTick } from '../utils/nearestUsableTick'
import { TickMath } from '../utils/tickMath'
import { Pool } from './pool'
import { Position } from './position'
import { TickList } from './tickList'

describe('Position', () => {
const USDC = new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD Coin')
const DAI = new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'DAI Stablecoin')
const POOL_SQRT_RATIO_START = encodeSqrtRatioX96(100e6, 100e18)
const POOL_TICK_CURRENT = TickMath.getTickAtSqrtRatio(POOL_SQRT_RATIO_START)
const TICK_SPACING = TICK_SPACINGS[FeeAmount.LOW]
const DAI_USDC_POOL = new Pool(
DAI,
USDC,
FeeAmount.LOW,
POOL_SQRT_RATIO_START,
0,
POOL_TICK_CURRENT,
new TickList([])
)
const DAI_USDC_POOL = new Pool(DAI, USDC, FeeAmount.LOW, POOL_SQRT_RATIO_START, 0, POOL_TICK_CURRENT, [])

it('can be constructed around 0 tick', () => {
const position = new Position({
Expand Down
7 changes: 3 additions & 4 deletions src/entities/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ import { FeeAmount } from '../constants'
import { encodeSqrtRatioX96 } from '../utils/encodeSqrtRatioX96'
import { Pool } from './pool'
import { Route } from './route'
import { TickList } from './tickList'

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 pool_0_1 = new Pool(token0, token1, FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 0, new TickList([]))
const pool_0_weth = new Pool(token0, weth, FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 0, new TickList([]))
const pool_1_weth = new Pool(token1, weth, FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 0, new TickList([]))
const pool_0_1 = new Pool(token0, token1, FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 0, [])
const pool_0_weth = new Pool(token0, weth, FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 0, [])
const pool_1_weth = new Pool(token1, weth, FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 0, [])

describe('path', () => {
it('constructs a path from the tokens', () => {
Expand Down
6 changes: 5 additions & 1 deletion src/entities/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import invariant from 'tiny-invariant'

import { ChainId, Currency, ETHER, Token, WETH9 } from '@uniswap/sdk-core'
import { ChainId, Currency, ETHER, Price, Token, WETH9 } from '@uniswap/sdk-core'
import { Pool } from './pool'

export class Route {
Expand Down Expand Up @@ -53,4 +53,8 @@ export class Route {
public get chainId(): ChainId | number {
return this.pools[0].chainId
}

public get midPrice(): Price {
throw new Error('todo')
}
}
Loading

0 comments on commit 514a639

Please sign in to comment.