Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(router-sdk): fix mixed route ETH <-> WETH wrong route.path #299

Merged
merged 1 commit into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions sdks/router-sdk/src/entities/mixedRoute/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,14 @@ describe('MixedRoute', () => {
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, 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', () => {
Expand Down Expand Up @@ -90,7 +97,7 @@ 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_weth_eth, pool_v4_1_eth], token0, 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)
Expand All @@ -110,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
51 changes: 37 additions & 14 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,7 +38,11 @@ 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.v4InvolvesToken(output) || lastPool.v4InvolvesToken(output.wrapped), 'OUTPUT')
Expand All @@ -51,20 +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 instanceof V4Pool
? pool.token0.equals(inputToken)
? pool.token1
: pool.token0
: pool.token0.wrapped.equals(inputToken.wrapped)
? pool.token1
: pool.token0

invariant(isValidTokenPath(prevPool, pool, inputToken), `PATH`)
let outputToken
if (
jsy1218 marked this conversation as resolved.
Show resolved Hide resolved
// 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)}`)
jsy1218 marked this conversation as resolved.
Show resolved Hide resolved
}
} 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
1 change: 0 additions & 1 deletion sdks/router-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,3 @@ export * from './utils/encodeMixedRouteToPath'
export * from './utils/TPool'
export * from './utils/pathCurrency'
export * from './utils'
export * from './utils/isValidTokenPath'
3 changes: 2 additions & 1 deletion sdks/router-sdk/src/utils/encodeMixedRouteToPath.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ describe('#encodeMixedRouteToPath', () => {
const route_1_v2_weth_v0_eth_v4_token0 = new MixedRouteSDK(
[pair_1_weth, fake_v4_eth_weth_pool, pool_V4_0_eth],
token1,
token0
token0,
true
)

describe('pure V3', () => {
Expand Down
19 changes: 0 additions & 19 deletions sdks/router-sdk/src/utils/isValidTokenPath.test.ts

This file was deleted.

24 changes: 0 additions & 24 deletions sdks/router-sdk/src/utils/isValidTokenPath.ts

This file was deleted.

17 changes: 17 additions & 0 deletions sdks/router-sdk/src/utils/pathCurrency.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Ether, Token } from '@uniswap/sdk-core'
import { encodeSqrtRatioX96 } from '@uniswap/v3-sdk'
import { Pool as V4Pool } from '@uniswap/v4-sdk'
import { ADDRESS_ZERO } from '../constants'
import { getPathCurrency } from './pathCurrency'

describe('#getPathCurrency', () => {
const SQRT_RATIO_ONE = encodeSqrtRatioX96(1, 1)
const ETHER = Ether.onChain(1)
const token1 = new Token(1, '0x0000000000000000000000000000000000000002', 18, 't1')

const pool_v4_eth_token1 = new V4Pool(token1, ETHER, 0, 0, ADDRESS_ZERO, SQRT_RATIO_ONE, 0, 0)

it('returns eth input', () => {
expect(getPathCurrency(ETHER, pool_v4_eth_token1)).toEqual(ETHER)
})
})
Loading