Skip to content

Commit

Permalink
merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
dianakocsis committed Feb 7, 2025
2 parents 88c1c4c + 02b8465 commit b3e1f44
Show file tree
Hide file tree
Showing 25 changed files with 1,141 additions and 158 deletions.
2 changes: 1 addition & 1 deletion sdks/router-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@uniswap/swap-router-contracts": "^1.3.0",
"@uniswap/v2-sdk": "^4.13.0",
"@uniswap/v3-sdk": "^3.24.0",
"@uniswap/v4-sdk": "^1.18.0"
"@uniswap/v4-sdk": "^1.18.1"
},
"devDependencies": {
"@types/jest": "^24.0.25",
Expand Down
33 changes: 28 additions & 5 deletions sdks/router-sdk/src/entities/mixedRoute/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('MixedRoute', () => {
const pool_v4_0_weth = new V4Pool(token0, weth, FeeAmount.MEDIUM, 60, ADDRESS_ZERO, SQRT_RATIO_ONE, 0, 0, [])
const pool_v4_1_eth = new V4Pool(token1, ETHER, FeeAmount.MEDIUM, 60, ADDRESS_ZERO, SQRT_RATIO_ONE, 0, 0, [])
const pool_v4_0_1 = new V4Pool(token0, token1, FeeAmount.MEDIUM, 60, ADDRESS_ZERO, SQRT_RATIO_ONE, 0, 0, [])
const pool_v4_weth_eth = new V4Pool(weth, ETHER, 0, 0, ADDRESS_ZERO, SQRT_RATIO_ONE, 0, 0)

const pool_v3_0_1 = new V3Pool(token0, token1, FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 0, [])
const pool_v3_0_weth = new V3Pool(token0, weth, FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 0, [])
Expand All @@ -34,6 +35,18 @@ describe('MixedRoute', () => {
const pair_2_3 = new Pair(CurrencyAmount.fromRawAmount(token2, '100'), CurrencyAmount.fromRawAmount(token3, '200'))

describe('path', () => {
it('real v3 weth pool and fake v4 eth/weth pool', () => {
const route = new MixedRouteSDK([pool_v3_0_weth, pool_v4_weth_eth], token0, ETHER)
expect(route.path).toEqual([token0, weth])
expect(route.pools).toEqual([pool_v3_0_weth])
})

it('real v3 weth pool and real v4 eth pool', () => {
const route = new MixedRouteSDK([pool_v3_0_weth, pool_v4_weth_eth, pool_v4_1_eth], token0, token1)
expect(route.path).toEqual([token0, weth, token1])
expect(route.pools).toEqual([pool_v3_0_weth, pool_v4_1_eth])
})

it('wraps pure v3 route object and successfully constructs a path from the tokens', () => {
/// @dev since the MixedRoute sdk object lives here in router-sdk we don't need to wrap it
const routeOriginal = new MixedRouteSDK([pool_v3_0_1], token0, token1)
Expand Down Expand Up @@ -84,9 +97,9 @@ describe('MixedRoute', () => {
})

it('wraps mixed route object with mixed v4 route that converts WETH -> ETH ', () => {
const route = new MixedRouteSDK([pool_v3_0_weth, pool_v4_1_eth], token0, token1)
expect(route.pools).toEqual([pool_v3_0_weth, pool_v4_1_eth])
expect(route.path).toEqual([token0, weth, token1])
const route = new MixedRouteSDK([pool_v3_0_weth, pool_v4_weth_eth, pool_v4_1_eth], token0, token1, true)
expect(route.pools).toEqual([pool_v3_0_weth, pool_v4_weth_eth, pool_v4_1_eth])
expect(route.path).toEqual([token0, weth, ETHER, token1])
expect(route.input).toEqual(token0)
expect(route.output).toEqual(token1)
expect(route.pathInput).toEqual(token0)
Expand All @@ -104,11 +117,21 @@ describe('MixedRoute', () => {
})

it('cannot wrap mixed route object with pure v4 route that converts ETH -> WETH ', () => {
expect(() => new MixedRouteSDK([pool_v4_1_eth, pool_v4_0_weth], token1, token0)).toThrow('PATH')
const route = new MixedRouteSDK([pool_v4_1_eth, pool_v4_0_weth], token1, token0)
expect(route.pools).toEqual([pool_v4_1_eth, pool_v4_0_weth])
expect(route.path).toEqual([token1, ETHER, token0])
expect(route.input).toEqual(token1)
expect(route.output).toEqual(token0)
expect(route.chainId).toEqual(1)
})

it('cannot wrap mixed route object with pure v4 route that converts WETH -> ETH ', () => {
expect(() => new MixedRouteSDK([pool_v4_0_weth, pool_v4_1_eth], token0, token1)).toThrow('PATH')
const route = new MixedRouteSDK([pool_v4_0_weth, pool_v4_1_eth], token0, token1)
expect(route.pools).toEqual([pool_v4_0_weth, pool_v4_1_eth])
expect(route.path).toEqual([token0, weth, token1])
expect(route.input).toEqual(token0)
expect(route.output).toEqual(token1)
expect(route.chainId).toEqual(1)
})

it('wraps complex mixed route object and successfully constructs a path from the tokens', () => {
Expand Down
45 changes: 38 additions & 7 deletions sdks/router-sdk/src/entities/mixedRoute/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import invariant from 'tiny-invariant'
import { Currency, Price, Token } from '@uniswap/sdk-core'
import { Pool as V4Pool } from '@uniswap/v4-sdk'
import { isValidTokenPath } from '../../utils/isValidTokenPath'
import { getPathCurrency } from '../../utils/pathCurrency'
import { TPool } from '../../utils/TPool'

Expand All @@ -25,9 +24,12 @@ export class MixedRouteSDK<TInput extends Currency, TOutput extends Currency> {
* @param pools An array of `TPool` objects (pools or pairs), ordered by the route the swap will take
* @param input The input token
* @param output The output token
* @param retainsFakePool Set to true to filter out a pool that has a fake eth-weth pool
*/
public constructor(pools: TPool[], input: TInput, output: TOutput) {
public constructor(pools: TPool[], input: TInput, output: TOutput, retainFakePools = false) {
pools = retainFakePools ? pools : pools.filter((pool) => !(pool instanceof V4Pool && pool.tickSpacing === 0))
invariant(pools.length > 0, 'POOLS')
// there is a pool mismatched to the path if we do not retain the fake eth-weth pools

const chainId = pools[0].chainId
const allOnSameChain = pools.every((pool) => pool.chainId === chainId)
Expand All @@ -36,10 +38,14 @@ export class MixedRouteSDK<TInput extends Currency, TOutput extends Currency> {
this.pathInput = getPathCurrency(input, pools[0])
this.pathOutput = getPathCurrency(output, pools[pools.length - 1])

invariant(pools[0].involvesToken(this.pathInput as Token), 'INPUT')
if (!(pools[0] instanceof V4Pool)) {
invariant(pools[0].involvesToken(this.pathInput as Token), 'INPUT')
} else {
invariant((pools[0] as V4Pool).v4InvolvesToken(this.pathInput), 'INPUT')
}
const lastPool = pools[pools.length - 1]
if (lastPool instanceof V4Pool) {
invariant(lastPool.involvesToken(output) || lastPool.involvesToken(output.wrapped), 'OUTPUT')
invariant(lastPool.v4InvolvesToken(output) || lastPool.v4InvolvesToken(output.wrapped), 'OUTPUT')
} else {
invariant(lastPool.involvesToken(output.wrapped as Token), 'OUTPUT')
}
Expand All @@ -51,12 +57,37 @@ export class MixedRouteSDK<TInput extends Currency, TOutput extends Currency> {
pools[0].token0.equals(this.pathInput) ? tokenPath.push(pools[0].token1) : tokenPath.push(pools[0].token0)

for (let i = 1; i < pools.length; i++) {
const prevPool = pools[i - 1]
const pool = pools[i]
const inputToken = tokenPath[i]
const outputToken = pool.token0.wrapped.equals(inputToken.wrapped) ? pool.token1 : pool.token0

invariant(isValidTokenPath(prevPool, pool, inputToken), 'PATH')
let outputToken
if (
// we hit an edge case if it's a v4 pool and neither of the tokens are in the pool OR it is not a v4 pool but the input currency is eth
(pool instanceof V4Pool && !pool.involvesToken(inputToken)) ||
(!(pool instanceof V4Pool) && inputToken.isNative)
) {
// We handle the case where the inputToken =/= pool.token0 or pool.token1. There are 2 specific cases.
if (inputToken.equals(pool.token0.wrapped)) {
// 1) the inputToken is WETH and the current pool has ETH
// for example, pools: USDC-WETH, ETH-PEPE, path: USDC, WETH, PEPE
// second pool is a v4 pool, the first could be any version
outputToken = pool.token1
} else if (inputToken.wrapped.equals(pool.token0) || inputToken.wrapped.equals(pool.token1)) {
// 2) the inputToken is ETH and the current pool has WETH
// for example, pools: USDC-ETH, WETH-PEPE, path: USDC, ETH, PEPE
// first pool is a v4 pool, the second could be any version
outputToken = inputToken.wrapped.equals(pool.token0) ? pool.token1 : pool.token0
} else {
throw new Error(`POOL_MISMATCH pool: ${JSON.stringify(pool)} inputToken: ${JSON.stringify(inputToken)}`)
}
} else {
// then the input token must equal either token0 or token1
invariant(
inputToken.equals(pool.token0) || inputToken.equals(pool.token1),
`PATH pool ${JSON.stringify(pool)} inputToken ${JSON.stringify(inputToken)}`
)
outputToken = inputToken.equals(pool.token0) ? pool.token1 : pool.token0
}
tokenPath.push(outputToken)
}

Expand Down
7 changes: 7 additions & 0 deletions sdks/router-sdk/src/entities/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,11 @@ describe('RouteV2', () => {
expect(route.input).toEqual(token0)
expect(route.output).toEqual(ETHER)
})

it('assigns pathInput and pathOutput correctly', () => {
const routeOriginal = new V2RouteSDK([pair_0_weth], token0, ETHER)
const route = new RouteV2(routeOriginal)
expect(route.pathInput).toEqual(token0)
expect(route.pathOutput).toEqual(weth)
})
})
22 changes: 22 additions & 0 deletions sdks/router-sdk/src/entities/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ import { Protocol } from './protocol'
import { Currency, Price, Token } from '@uniswap/sdk-core'
import { MixedRouteSDK } from './mixedRoute/route'

// Helper function to get the pathInput and pathOutput for a V2 / V3 route
// currency could be native so we check against the wrapped version as they don't support native ETH in path
export function getPathToken(currency: Currency, pool: Pair | V3Pool): Token {
if (pool.token0.wrapped.equals(currency.wrapped)) {
return pool.token0
} else if (pool.token1.wrapped.equals(currency.wrapped)) {
return pool.token1
} else {
throw new Error(`Expected token ${currency.symbol} to be either ${pool.token0.symbol} or ${pool.token1.symbol}`)
}
}

export interface IRoute<TInput extends Currency, TOutput extends Currency, TPool extends Pair | V3Pool | V4Pool> {
protocol: Protocol
// array of pools if v3 or pairs if v2
Expand All @@ -15,6 +27,8 @@ export interface IRoute<TInput extends Currency, TOutput extends Currency, TPool
midPrice: Price<TInput, TOutput>
input: TInput
output: TOutput
pathInput: Currency
pathOutput: Currency
}

// V2 route wrapper
Expand All @@ -24,10 +38,14 @@ export class RouteV2<TInput extends Currency, TOutput extends Currency>
{
public readonly protocol: Protocol = Protocol.V2
public readonly pools: Pair[]
public pathInput: Currency
public pathOutput: Currency

constructor(v2Route: V2RouteSDK<TInput, TOutput>) {
super(v2Route.pairs, v2Route.input, v2Route.output)
this.pools = this.pairs
this.pathInput = getPathToken(v2Route.input, this.pairs[0])
this.pathOutput = getPathToken(v2Route.output, this.pairs[this.pairs.length - 1])
}
}

Expand All @@ -38,10 +56,14 @@ export class RouteV3<TInput extends Currency, TOutput extends Currency>
{
public readonly protocol: Protocol = Protocol.V3
public readonly path: Token[]
public pathInput: Currency
public pathOutput: Currency

constructor(v3Route: V3RouteSDK<TInput, TOutput>) {
super(v3Route.pools, v3Route.input, v3Route.output)
this.path = v3Route.tokenPath
this.pathInput = getPathToken(v3Route.input, this.pools[0])
this.pathOutput = getPathToken(v3Route.output, this.pools[this.pools.length - 1])
}
}

Expand Down
Loading

0 comments on commit b3e1f44

Please sign in to comment.