Skip to content

Commit

Permalink
feat(v4-sdk): Add V4Planner to encode v4 routes and positions (#87)
Browse files Browse the repository at this point in the history
Co-authored-by: Eric Zhong <[email protected]>
  • Loading branch information
ewilz and zhongeric authored Sep 12, 2024
1 parent a226b32 commit f8edf01
Show file tree
Hide file tree
Showing 6 changed files with 416 additions and 6 deletions.
1 change: 1 addition & 0 deletions sdks/v4-sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './entities'
export * from './utils'
108 changes: 108 additions & 0 deletions sdks/v4-sdk/src/utils/encodeRouteToPath.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Ether, Token } from '@uniswap/sdk-core'
import { encodeSqrtRatioX96 } from '@uniswap/v3-sdk'
import { Route } from '../entities/route'
import { Pool } from '../entities/pool'
import { encodeRouteToPath } from './encodeRouteToPath'
import { ADDRESS_ZERO, FEE_AMOUNT_MEDIUM, TICK_SPACING_TEN } from '../internalConstants'

const eth = Ether.onChain(1)
const currency1 = new Token(1, '0x0000000000000000000000000000000000000001', 18, 't1')
const currency2 = new Token(1, '0x0000000000000000000000000000000000000002', 18, 't2')
const currency3 = new Token(1, '0x0000000000000000000000000000000000000003', 18, 't3')

const pool_eth_1 = new Pool(
eth,
currency1,
FEE_AMOUNT_MEDIUM,
TICK_SPACING_TEN,
ADDRESS_ZERO,
encodeSqrtRatioX96(1, 1),
0,
0,
[]
)

const pool_1_2 = new Pool(
currency1,
currency2,
FEE_AMOUNT_MEDIUM,
TICK_SPACING_TEN,
ADDRESS_ZERO,
encodeSqrtRatioX96(1, 1),
0,
0,
[]
)

const pool_2_3 = new Pool(
currency2,
currency3,
FEE_AMOUNT_MEDIUM,
TICK_SPACING_TEN,
ADDRESS_ZERO,
encodeSqrtRatioX96(1, 1),
0,
0,
[]
)

const route = new Route([pool_eth_1, pool_1_2, pool_2_3], eth, currency3)

describe('RouterPlanner', () => {
it('encodes the correct route for exactIn', async () => {
const expected = [
{
intermediateCurrency: '0x0000000000000000000000000000000000000001',
fee: 3000,
tickSpacing: 10,
hooks: '0x0000000000000000000000000000000000000000',
hookData: '0x',
},
{
intermediateCurrency: '0x0000000000000000000000000000000000000002',
fee: 3000,
tickSpacing: 10,
hooks: '0x0000000000000000000000000000000000000000',
hookData: '0x',
},
{
intermediateCurrency: '0x0000000000000000000000000000000000000003',
fee: 3000,
tickSpacing: 10,
hooks: '0x0000000000000000000000000000000000000000',
hookData: '0x',
},
]

expect(encodeRouteToPath(route)).toEqual(expected)
})

it('encodes the correct route for exactOut', async () => {
const exactOutput = true
const expected = [
{
intermediateCurrency: '0x0000000000000000000000000000000000000000',
fee: 3000,
tickSpacing: 10,
hooks: '0x0000000000000000000000000000000000000000',
hookData: '0x',
},
{
intermediateCurrency: '0x0000000000000000000000000000000000000001',
fee: 3000,
tickSpacing: 10,
hooks: '0x0000000000000000000000000000000000000000',
hookData: '0x',
},
{
intermediateCurrency: '0x0000000000000000000000000000000000000002',
fee: 3000,
tickSpacing: 10,
hooks: '0x0000000000000000000000000000000000000000',
hookData: '0x',
},
]

expect(encodeRouteToPath(route, exactOutput)).toEqual(expected)
})
})
33 changes: 33 additions & 0 deletions sdks/v4-sdk/src/utils/encodeRouteToPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Currency } from '@uniswap/sdk-core'
import { Route } from '../entities/route'
import { ADDRESS_ZERO } from '../internalConstants'

export type PathKey = {
intermediateCurrency: string // address
fee: number
tickSpacing: number
hooks: string // address
hookData: string // bytes
}

export const encodeRouteToPath = (route: Route<Currency, Currency>, exactOutput?: boolean): PathKey[] => {
let startingCurrency = exactOutput ? route.output : route.input
let pools = exactOutput ? route.pools.reverse() : route.pools
let pathKeys: PathKey[] = []

for (let pool of pools) {
const nextCurrency = startingCurrency.equals(pool.currency0) ? pool.currency1 : pool.currency0

pathKeys.push({
intermediateCurrency: nextCurrency.isNative ? ADDRESS_ZERO : nextCurrency.address,
fee: pool.fee,
tickSpacing: pool.tickSpacing,
hooks: pool.hooks,
hookData: '0x',
})

startingCurrency = nextCurrency
}

return exactOutput ? pathKeys.reverse() : pathKeys
}
8 changes: 2 additions & 6 deletions sdks/v4-sdk/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
export * from './encodeRouteToPath'
export * from './v4Planner'
export * from './calldata'
export * from './encodeSqrtRatioX96'
export * from './maxLiquidityForAmounts'
export * from './priceTickConversions'
export * from './sqrtPriceMath'
export * from './swapMath'
export * from './tickMath'
116 changes: 116 additions & 0 deletions sdks/v4-sdk/src/utils/v4Planner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { BigNumber } from 'ethers'
import JSBI from 'jsbi'
import { CurrencyAmount, TradeType, Token, WETH9 } from '@uniswap/sdk-core'
import { encodeSqrtRatioX96, nearestUsableTick, TickMath } from '@uniswap/v3-sdk'
import { Pool } from '../entities/pool'
import { Trade } from '../entities/trade'
import { Route } from '../entities/route'
import { encodeRouteToPath } from './encodeRouteToPath'
import { ADDRESS_ZERO, FEE_AMOUNT_MEDIUM, TICK_SPACING_TEN, ONE_ETHER, NEGATIVE_ONE } from '../internalConstants'

import { Actions, V4Planner } from './v4Planner'

const ONE_ETHER_BN = BigNumber.from(1).mul(10).pow(18)
const TICKLIST = [
{
index: nearestUsableTick(TickMath.MIN_TICK, TICK_SPACING_TEN),
liquidityNet: ONE_ETHER,
liquidityGross: ONE_ETHER,
},
{
index: nearestUsableTick(TickMath.MAX_TICK, TICK_SPACING_TEN),
liquidityNet: JSBI.multiply(ONE_ETHER, NEGATIVE_ONE),
liquidityGross: ONE_ETHER,
},
]

const USDC = new Token(1, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD Coin')
const DAI = new Token(1, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'DAI Stablecoin')
const USDC_WETH = new Pool(
USDC,
WETH9[1],
FEE_AMOUNT_MEDIUM,
TICK_SPACING_TEN,
ADDRESS_ZERO,
encodeSqrtRatioX96(1, 1),
0,
0,
TICKLIST
)
const DAI_USDC = new Pool(
USDC,
DAI,
FEE_AMOUNT_MEDIUM,
TICK_SPACING_TEN,
ADDRESS_ZERO,
encodeSqrtRatioX96(1, 1),
0,
0,
TICKLIST
)

describe('RouterPlanner', () => {
let planner: V4Planner

beforeEach(() => {
planner = new V4Planner()
})

it('encodes a v4 exactInSingle swap', async () => {
planner.addAction(Actions.SWAP_EXACT_IN_SINGLE, [
{
poolKey: USDC_WETH.poolKey,
zeroForOne: true,
amountIn: ONE_ETHER_BN,
amountOutMinimum: ONE_ETHER_BN.div(2),
sqrtPriceLimitX96: 0,
hookData: '0x',
},
])
planner.addAction(Actions.SETTLE_TAKE_PAIR, [USDC.address, WETH9[1].address])

expect(planner.actions).toEqual('0x0416')
expect(planner.params[0]).toEqual(
'0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000006f05b59d3b20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000'
)
expect(planner.params[1]).toEqual(
'0x000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
)
})

it('completes a v4 exactIn 2 hop swap', async () => {
const route = new Route([DAI_USDC, USDC_WETH], DAI, WETH9[1])

// encode with addAction function
planner.addAction(Actions.SWAP_EXACT_IN, [
{
currencyIn: DAI.address,
path: encodeRouteToPath(route),
amountIn: ONE_ETHER_BN.toString(),
amountOutMinimum: 0,
},
])
planner.addAction(Actions.SETTLE_TAKE_PAIR, [DAI.address, WETH9[1].address])

// encode with addTrade function
const tradePlanner = new V4Planner()
const trade = await Trade.fromRoute(
route,
CurrencyAmount.fromRawAmount(DAI, ONE_ETHER.toString()),
TradeType.EXACT_INPUT
)
tradePlanner.addTrade(trade)

expect(planner.actions).toEqual('0x0516')
expect(planner.params[0]).toEqual(
'0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000'
)
expect(planner.params[1]).toEqual(
'0x0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
)

expect(planner.actions).toEqual(tradePlanner.actions)
expect(planner.params[0]).toEqual(tradePlanner.params[0])
expect(planner.params[1]).toEqual(tradePlanner.params[1])
})
})
Loading

0 comments on commit f8edf01

Please sign in to comment.