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): Address wrapped/native currency conditions in mixed routes with v4 #81

Merged
merged 5 commits into from
Sep 5, 2024
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
28 changes: 27 additions & 1 deletion sdks/router-sdk/src/entities/mixedRoute/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe('MixedRoute', () => {
expect(route.chainId).toEqual(1)
})

it('wraps mixed route object with v4 route successfully constructs a pth from the tokens', () => {
it('wraps mixed route object with v4 route successfully constructs a path from the tokens', () => {
const route = new MixedRouteSDK([pool_v3_0_1, pool_v4_0_weth], token1, weth)
expect(route.pools).toEqual([pool_v3_0_1, pool_v4_0_weth])
expect(route.path).toEqual([token1, token0, weth])
Expand All @@ -81,6 +81,32 @@ describe('MixedRoute', () => {
expect(route.chainId).toEqual(1)
})

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])
expect(route.input).toEqual(token0)
expect(route.output).toEqual(token1)
expect(route.chainId).toEqual(1)
})

it('wraps mixed route object with mixed v4 route that converts ETH -> WETH ', () => {
const route = new MixedRouteSDK([pool_v4_1_eth, pool_v3_0_weth], token1, token0)
expect(route.pools).toEqual([pool_v4_1_eth, pool_v3_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 ETH -> WETH ', () => {
expect(() => new MixedRouteSDK([pool_v4_1_eth, pool_v4_0_weth], token1, token0)).toThrow('PATH')
})

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')
})

it('wraps complex mixed route object and successfully constructs a path from the tokens', () => {
const route = new MixedRouteSDK([pool_v3_0_1, pair_1_weth, pair_weth_2], token0, token2)
expect(route.pools).toEqual([pool_v3_0_1, pair_1_weth, pair_weth_2])
Expand Down
16 changes: 11 additions & 5 deletions sdks/router-sdk/src/entities/mixedRoute/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Currency, Price, Token } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { Pool as V3Pool } from '@uniswap/v3-sdk'
import { Pool as V4Pool } from '@uniswap/v4-sdk'
import { isValidTokenPath } from '../../utils/isValidTokenPath'

type TPool = Pair | V3Pool | V4Pool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: as I was coding v4 routing in SOR, I realized getting this type exported can be beneficial

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yah I also need to DRY and put this somewhere, I keep meaning to do taht

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid of needing new approvals, so I'm just gonna merge..


Expand Down Expand Up @@ -52,11 +53,16 @@ export class MixedRouteSDK<TInput extends Currency, TOutput extends Currency> {
* Normalizes token0-token1 order and selects the next token/fee step to add to the path
* */
const tokenPath: Currency[] = [this.adjustedInput]
for (const [i, pool] of pools.entries()) {
const currentInputToken = tokenPath[i]
invariant(currentInputToken.equals(pool.token0) || currentInputToken.equals(pool.token1), 'PATH')
const nextToken = currentInputToken.equals(pool.token0) ? pool.token1 : pool.token0
tokenPath.push(nextToken)
pools[0].token0.equals(this.adjustedInput) ? 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')
tokenPath.push(outputToken)
}

this.pools = pools
Expand Down
57 changes: 56 additions & 1 deletion sdks/router-sdk/src/entities/mixedRoute/trade.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ describe('MixedRouteTrade', () => {
CurrencyAmount.fromRawAmount(WETH9[1], 130000),
true
)
const pool_v4_0_eth = v2StylePool(
CurrencyAmount.fromRawAmount(token0, 120000),
CurrencyAmount.fromRawAmount(ETHER, 130000),
true
)

const pool_v3_0_1 = v2StylePool(
CurrencyAmount.fromRawAmount(token0, 100000),
Expand Down Expand Up @@ -1381,7 +1386,7 @@ describe('MixedRouteTrade', () => {
})

describe('multihop v2 + v3 + v4', () => {
it('can be constructed with an eth output from a v4 pool', async () => {
it('can be constructed with a weth output from a v4 pool', async () => {
const trade = await MixedRouteTrade.fromRoute(
new MixedRouteSDK([pool_v3_0_1, pool_v4_0_weth], token1, WETH9[1]),
CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(100)),
Expand All @@ -1390,5 +1395,55 @@ describe('MixedRouteTrade', () => {
expect(trade.inputAmount.currency).toEqual(token1)
expect(trade.outputAmount.currency).toEqual(WETH9[1])
})

it('can be constructed with an eth output from a v4 pool', async () => {
const trade = await MixedRouteTrade.fromRoute(
new MixedRouteSDK([pool_v3_0_1, pool_v4_0_eth], token1, ETHER),
CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(100)),
TradeType.EXACT_INPUT
)
expect(trade.inputAmount.currency).toEqual(token1)
expect(trade.outputAmount.currency).toEqual(ETHER)
})

it('can be constructed with an eth output from a v4 weth pool', async () => {
const trade = await MixedRouteTrade.fromRoute(
new MixedRouteSDK([pool_v3_0_1, pool_v4_0_weth], token1, ETHER),
CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(100)),
TradeType.EXACT_INPUT
)
expect(trade.inputAmount.currency).toEqual(token1)
expect(trade.outputAmount.currency).toEqual(ETHER)
})

it('can be constructed with an intermediate conversion WETH->ETH when trading to v4 pool', async () => {
const trade = await MixedRouteTrade.fromRoute(
new MixedRouteSDK([pool_v3_weth_0, pool_v4_0_eth], token0, ETHER),
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100)),
TradeType.EXACT_INPUT
)
expect(trade.inputAmount.currency).toEqual(token0)
expect(trade.outputAmount.currency).toEqual(ETHER)
})

it('can be constructed with an intermediate conversion ETH->WETH when trading from a v4 pool', async () => {
const trade = await MixedRouteTrade.fromRoute(
new MixedRouteSDK([pool_v4_0_eth, pool_v3_weth_0], token0, WETH9[1]),
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100)),
TradeType.EXACT_INPUT
)
expect(trade.inputAmount.currency).toEqual(token0)
expect(trade.outputAmount.currency).toEqual(WETH9[1])
})

it('can be constructed with an intermediate conversion ETH->WETH when trading from a v4 pool with ETH output', async () => {
const trade = await MixedRouteTrade.fromRoute(
new MixedRouteSDK([pool_v4_0_eth, pool_v3_weth_0], token0, ETHER),
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100)),
TradeType.EXACT_INPUT
)
expect(trade.inputAmount.currency).toEqual(token0)
expect(trade.outputAmount.currency).toEqual(ETHER)
})
})
})
9 changes: 7 additions & 2 deletions sdks/router-sdk/src/utils/getOutputAmount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@ export async function getOutputAmount(
if (pool instanceof V4Pool) {
if (pool.involvesCurrency(amountIn.currency)) {
return await pool.getOutputAmount(amountIn)
} else if (pool.involvesCurrency(amountIn.currency.wrapped)) {
return await pool.getOutputAmount(amountIn.wrapped)
}
if (pool.token0.wrapped.equals(amountIn.currency)) {
return await pool.getOutputAmount(CurrencyAmount.fromRawAmount(pool.token0, amountIn.quotient))
}
if (pool.token1.wrapped.equals(amountIn.currency)) {
return await pool.getOutputAmount(CurrencyAmount.fromRawAmount(pool.token1, amountIn.quotient))
}
}

return await pool.getOutputAmount(amountIn.wrapped)
}
25 changes: 25 additions & 0 deletions sdks/router-sdk/src/utils/isValidTokenPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Currency, Token } from '@uniswap/sdk-core'
import { Pool as V4Pool } from '@uniswap/v4-sdk'
import { Pair } from '@uniswap/v2-sdk'
import { Pool as V3Pool } from '@uniswap/v3-sdk'

type TPool = Pair | V3Pool | V4Pool

export function isValidTokenPath(prevPool: TPool, currentPool: TPool, inputToken: Currency): boolean {
if (currentPool.involvesToken(inputToken as Token)) return true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should always be true if inputToken is not native right, a comment could be helpful

Copy link
Member Author

@ewilz ewilz Aug 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to put as Token bc v2/v3 pools only accept token parameters..so I'm basically hacking typescript so it will compile. this will work if the inputToken IS native as type Currency too....

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of want to go into v2/v3-sdks change these inputs to Currency and then throw an error if it's not type Token (a subset of Currency) then I can remove all of the as Token's from this sdk


// throw if both v4 pools, native/wrapped tokens not interchangeable in v4
if (prevPool instanceof V4Pool && currentPool instanceof V4Pool) return false

// v2/v3 --> v4 valid if v2/v3 output is the wrapped version of the v4 pool native currency
if (currentPool instanceof V4Pool) {
if (currentPool.token0.wrapped.equals(inputToken) || currentPool.token1.wrapped.equals(inputToken)) return true
}

// v4 --> v2/v3 valid if v4 output is the native version of the v2/v3 wrapped token
if (prevPool instanceof V4Pool) {
if (currentPool.involvesToken(inputToken.wrapped)) return true
}

return false
}
Loading