Skip to content

Commit 0e64da8

Browse files
improve gas tests
1 parent d76cda3 commit 0e64da8

File tree

5 files changed

+260
-317
lines changed

5 files changed

+260
-317
lines changed

test/SwapRouter.gas.spec.ts

+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
import { Fixture } from 'ethereum-waffle'
2+
import { constants, Contract, ContractTransaction } from 'ethers'
3+
import { ethers, waffle } from 'hardhat'
4+
import { IUniswapV3Pool, IWETH9, MockTimeSwapRouter, TestERC20 } from '../typechain'
5+
import completeFixture from './shared/completeFixture'
6+
import { FeeAmount, TICK_SPACINGS } from './shared/constants'
7+
import { encodePriceSqrt } from './shared/encodePriceSqrt'
8+
import { expandTo18Decimals } from './shared/expandTo18Decimals'
9+
import { expect } from './shared/expect'
10+
import { encodePath } from './shared/path'
11+
import snapshotGasCost from './shared/snapshotGasCost'
12+
import { getMaxTick, getMinTick } from './shared/ticks'
13+
14+
import { abi as IUniswapV3PoolABI } from '@uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json'
15+
16+
describe('SwapRouter gas tests', () => {
17+
const wallets = waffle.provider.getWallets()
18+
const [wallet, trader] = wallets
19+
20+
const swapRouterFixture: Fixture<{
21+
weth9: IWETH9
22+
router: MockTimeSwapRouter
23+
tokens: [TestERC20, TestERC20, TestERC20]
24+
pools: [IUniswapV3Pool, IUniswapV3Pool, IUniswapV3Pool]
25+
}> = async (wallets, provider) => {
26+
const { weth9, factory, router, tokens, nft } = await completeFixture(wallets, provider)
27+
28+
// approve & fund wallets
29+
for (const token of tokens) {
30+
await Promise.all([
31+
token.approve(router.address, constants.MaxUint256),
32+
token.approve(nft.address, constants.MaxUint256),
33+
token.connect(trader).approve(router.address, constants.MaxUint256),
34+
token.transfer(trader.address, expandTo18Decimals(1_000_000)),
35+
])
36+
}
37+
38+
const liquidity = 1000000
39+
async function createPool(tokenAddressA: string, tokenAddressB: string) {
40+
if (tokenAddressA.toLowerCase() > tokenAddressB.toLowerCase())
41+
[tokenAddressA, tokenAddressB] = [tokenAddressB, tokenAddressA]
42+
43+
const liquidityParams = {
44+
token0: tokenAddressA,
45+
token1: tokenAddressB,
46+
fee: FeeAmount.MEDIUM,
47+
sqrtPriceX96: encodePriceSqrt(100005, 100000), // we don't want to cross any ticks
48+
tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
49+
tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
50+
recipient: wallet.address,
51+
amount: liquidity,
52+
deadline: 1,
53+
}
54+
55+
return nft.firstMint(liquidityParams)
56+
}
57+
58+
async function createPoolWETH9(tokenAddress: string) {
59+
await weth9.deposit({ value: liquidity * 2 })
60+
await weth9.approve(nft.address, constants.MaxUint256)
61+
return createPool(weth9.address, tokenAddress)
62+
}
63+
64+
// create pools
65+
await Promise.all([
66+
createPool(tokens[0].address, tokens[1].address),
67+
createPool(tokens[1].address, tokens[2].address),
68+
createPoolWETH9(tokens[0].address),
69+
])
70+
71+
const poolAddresses = await Promise.all([
72+
factory.getPool(tokens[0].address, tokens[1].address, FeeAmount.MEDIUM),
73+
factory.getPool(tokens[1].address, tokens[2].address, FeeAmount.MEDIUM),
74+
factory.getPool(weth9.address, tokens[0].address, FeeAmount.MEDIUM),
75+
])
76+
77+
const pools = poolAddresses.map(
78+
(poolAddress) => new ethers.Contract(poolAddress, IUniswapV3PoolABI, waffle.provider)
79+
) as [IUniswapV3Pool, IUniswapV3Pool, IUniswapV3Pool]
80+
81+
return {
82+
weth9,
83+
router,
84+
tokens,
85+
pools,
86+
}
87+
}
88+
89+
let weth9: IWETH9
90+
let router: MockTimeSwapRouter
91+
let tokens: [TestERC20, TestERC20, TestERC20]
92+
let pools: [IUniswapV3Pool, IUniswapV3Pool, IUniswapV3Pool]
93+
94+
let loadFixture: ReturnType<typeof waffle.createFixtureLoader>
95+
96+
before('create fixture loader', async () => {
97+
loadFixture = waffle.createFixtureLoader(wallets)
98+
})
99+
100+
beforeEach('load fixture', async () => {
101+
;({ router, weth9, tokens, pools } = await loadFixture(swapRouterFixture))
102+
})
103+
104+
async function exactInput(
105+
tokens: string[],
106+
amountIn: number = 2,
107+
amountOutMinimum: number = 1
108+
): Promise<ContractTransaction> {
109+
const inputIsWETH = [weth9.address].includes(tokens[0])
110+
const outputIsWETH9 = tokens[tokens.length - 1] === weth9.address
111+
112+
const value = inputIsWETH ? amountIn : 0
113+
114+
const params = {
115+
path: encodePath(tokens, new Array(tokens.length - 1).fill(FeeAmount.MEDIUM)),
116+
recipient: outputIsWETH9 ? router.address : trader.address,
117+
deadline: 1,
118+
}
119+
120+
const data = [router.interface.encodeFunctionData('exactInput', [params, amountIn, amountOutMinimum])]
121+
if (outputIsWETH9) data.push(router.interface.encodeFunctionData('unwrapWETH9', [amountOutMinimum, trader.address]))
122+
123+
// ensure that the swap fails if the limit is any tighter
124+
await expect(
125+
router.connect(trader).exactInput(params, amountIn, amountOutMinimum + 1, { value })
126+
).to.be.revertedWith('Too little received')
127+
128+
// optimized for the gas test
129+
return data.length === 1
130+
? router.connect(trader).exactInput(params, amountIn, amountOutMinimum, { value })
131+
: router.connect(trader).multicall(data, { value })
132+
}
133+
134+
async function exactOutput(tokens: string[]): Promise<ContractTransaction> {
135+
const amountInMaximum = 10 // we don't care
136+
const amountOut = 1
137+
138+
const inputIsWETH9 = tokens[0] === weth9.address
139+
const outputIsWETH9 = tokens[tokens.length - 1] === weth9.address
140+
141+
const value = inputIsWETH9 ? amountInMaximum : 0
142+
143+
const params = {
144+
path: encodePath(tokens.slice().reverse(), new Array(tokens.length - 1).fill(FeeAmount.MEDIUM)),
145+
recipient: outputIsWETH9 ? router.address : trader.address,
146+
deadline: 1,
147+
}
148+
149+
const data = [router.interface.encodeFunctionData('exactOutput', [params, amountOut, amountInMaximum])]
150+
if (inputIsWETH9) data.push(router.interface.encodeFunctionData('unwrapWETH9', [0, trader.address]))
151+
if (outputIsWETH9) data.push(router.interface.encodeFunctionData('unwrapWETH9', [amountOut, trader.address]))
152+
153+
return router.connect(trader).multicall(data, { value })
154+
}
155+
156+
// TODO should really throw this in the fixture
157+
beforeEach('intialize feeGrowthGlobals', async () => {
158+
await exactInput([tokens[0].address, tokens[1].address], 1, 0)
159+
await exactInput([tokens[1].address, tokens[0].address], 1, 0)
160+
await exactInput([tokens[1].address, tokens[2].address], 1, 0)
161+
await exactInput([tokens[2].address, tokens[1].address], 1, 0)
162+
await exactInput([tokens[0].address, weth9.address], 1, 0)
163+
await exactInput([weth9.address, tokens[0].address], 1, 0)
164+
})
165+
166+
beforeEach('ensure feeGrowthGlobals are >0', async () => {
167+
const slots = await Promise.all(
168+
pools.map((pool) =>
169+
Promise.all([
170+
pool.feeGrowthGlobal0X128().then((f) => f.toString()),
171+
pool.feeGrowthGlobal1X128().then((f) => f.toString()),
172+
])
173+
)
174+
)
175+
176+
expect(slots).to.deep.eq([
177+
['340282366920938463463374607431768', '340282366920938463463374607431768'],
178+
['340282366920938463463374607431768', '340282366920938463463374607431768'],
179+
['340282366920938463463374607431768', '340282366920938463463374607431768'],
180+
])
181+
})
182+
183+
beforeEach('ensure ticks are 0 before', async () => {
184+
const slots = await Promise.all(pools.map((pool) => pool.slot0().then(({ tick }) => tick)))
185+
expect(slots).to.deep.eq([0, 0, 0])
186+
})
187+
188+
afterEach('ensure ticks are 0 after', async () => {
189+
const slots = await Promise.all(pools.map((pool) => pool.slot0().then(({ tick }) => tick)))
190+
expect(slots).to.deep.eq([0, 0, 0])
191+
})
192+
193+
describe('#exactInput', () => {
194+
it('0 -> 1', async () => {
195+
await snapshotGasCost(exactInput(tokens.slice(0, 2).map((token) => token.address)))
196+
})
197+
198+
it('0 -> 1 -> 2', async () => {
199+
await snapshotGasCost(
200+
exactInput(
201+
tokens.map((token) => token.address),
202+
3
203+
)
204+
)
205+
})
206+
207+
it('WETH9 -> 0', async () => {
208+
await snapshotGasCost(
209+
exactInput(
210+
[weth9.address, tokens[0].address],
211+
weth9.address.toLowerCase() < tokens[0].address.toLowerCase() ? 2 : 3
212+
)
213+
)
214+
})
215+
216+
it('0 -> WETH9', async () => {
217+
await snapshotGasCost(
218+
exactInput(
219+
[tokens[0].address, weth9.address],
220+
tokens[0].address.toLowerCase() < weth9.address.toLowerCase() ? 2 : 3
221+
)
222+
)
223+
})
224+
})
225+
226+
describe('#exactOutput', () => {
227+
it('0 -> 1', async () => {
228+
await snapshotGasCost(exactOutput(tokens.slice(0, 2).map((token) => token.address)))
229+
})
230+
231+
it('0 -> 1 -> 2', async () => {
232+
await snapshotGasCost(exactOutput(tokens.map((token) => token.address)))
233+
})
234+
235+
it('WETH9 -> 0', async () => {
236+
await snapshotGasCost(exactOutput([weth9.address, tokens[0].address]))
237+
})
238+
239+
it('0 -> WETH9', async () => {
240+
await snapshotGasCost(exactOutput([tokens[0].address, weth9.address]))
241+
})
242+
})
243+
})

test/SwapRouter.spec.ts

-44
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,6 @@ describe('SwapRouter', () => {
152152
}
153153

154154
describe('single-pool', () => {
155-
it('gas', async () => {
156-
await snapshotGasCost(exactInput(tokens.slice(0, 2).map((token) => token.address)))
157-
})
158-
159155
it('0 -> 1', async () => {
160156
const pool = await factory.getPool(tokens[0].address, tokens[1].address, FeeAmount.MEDIUM)
161157

@@ -201,16 +197,6 @@ describe('SwapRouter', () => {
201197
})
202198

203199
describe('multi-pool', () => {
204-
it('gas', async () => {
205-
await snapshotGasCost(
206-
exactInput(
207-
tokens.map((token) => token.address),
208-
5,
209-
1
210-
)
211-
)
212-
})
213-
214200
it('0 -> 1 -> 2', async () => {
215201
const traderBefore = await getBalances(trader.address)
216202

@@ -244,10 +230,6 @@ describe('SwapRouter', () => {
244230
await createPoolWETH9(tokens[0].address)
245231
})
246232

247-
it('gas', async () => {
248-
await snapshotGasCost(exactInput([weth9.address, tokens[0].address]))
249-
})
250-
251233
it('WETH9 -> 0', async () => {
252234
const pool = await factory.getPool(weth9.address, tokens[0].address, FeeAmount.MEDIUM)
253235

@@ -289,10 +271,6 @@ describe('SwapRouter', () => {
289271
await createPoolWETH9(tokens[1].address)
290272
})
291273

292-
it('gas', async () => {
293-
await snapshotGasCost(exactInput([tokens[0].address, weth9.address]))
294-
})
295-
296274
it('0 -> WETH9', async () => {
297275
const pool = await factory.getPool(tokens[0].address, weth9.address, FeeAmount.MEDIUM)
298276

@@ -360,10 +338,6 @@ describe('SwapRouter', () => {
360338
}
361339

362340
describe('single-pool', () => {
363-
it('gas', async () => {
364-
await snapshotGasCost(exactOutput(tokens.slice(0, 2).map((token) => token.address)))
365-
})
366-
367341
it('0 -> 1', async () => {
368342
const pool = await factory.getPool(tokens[0].address, tokens[1].address, FeeAmount.MEDIUM)
369343

@@ -409,16 +383,6 @@ describe('SwapRouter', () => {
409383
})
410384

411385
describe('multi-pool', () => {
412-
it('gas', async () => {
413-
await snapshotGasCost(
414-
exactOutput(
415-
tokens.map((token) => token.address),
416-
1,
417-
5
418-
)
419-
)
420-
})
421-
422386
it('0 -> 1 -> 2', async () => {
423387
const traderBefore = await getBalances(trader.address)
424388

@@ -452,10 +416,6 @@ describe('SwapRouter', () => {
452416
await createPoolWETH9(tokens[0].address)
453417
})
454418

455-
it('gas', async () => {
456-
await snapshotGasCost(exactOutput([weth9.address, tokens[0].address]))
457-
})
458-
459419
it('WETH9 -> 0', async () => {
460420
const pool = await factory.getPool(weth9.address, tokens[0].address, FeeAmount.MEDIUM)
461421

@@ -497,10 +457,6 @@ describe('SwapRouter', () => {
497457
await createPoolWETH9(tokens[1].address)
498458
})
499459

500-
it('gas', async () => {
501-
await snapshotGasCost(exactOutput([tokens[0].address, weth9.address]))
502-
})
503-
504460
it('0 -> WETH9', async () => {
505461
const pool = await factory.getPool(tokens[0].address, weth9.address, FeeAmount.MEDIUM)
506462

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`SwapRouter gas tests #exactInput 0 -> 1 -> 2 1`] = `213885`;
4+
5+
exports[`SwapRouter gas tests #exactInput 0 -> 1 1`] = `111441`;
6+
7+
exports[`SwapRouter gas tests #exactInput 0 -> WETH9 1`] = `138268`;
8+
9+
exports[`SwapRouter gas tests #exactInput WETH9 -> 0 1`] = `111347`;
10+
11+
exports[`SwapRouter gas tests #exactOutput 0 -> 1 -> 2 1`] = `178075`;
12+
13+
exports[`SwapRouter gas tests #exactOutput 0 -> 1 1`] = `115995`;
14+
15+
exports[`SwapRouter gas tests #exactOutput 0 -> WETH9 1`] = `139560`;
16+
17+
exports[`SwapRouter gas tests #exactOutput WETH9 -> 0 1`] = `143642`;
-16
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,3 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`SwapRouter bytecode size 1`] = `9239`;
4-
5-
exports[`SwapRouter swaps #exactInput ETH input WETH9 gas 1`] = `126347`;
6-
7-
exports[`SwapRouter swaps #exactInput ETH output WETH9 gas 1`] = `170466`;
8-
9-
exports[`SwapRouter swaps #exactInput multi-pool gas 1`] = `278281`;
10-
11-
exports[`SwapRouter swaps #exactInput single-pool gas 1`] = `143639`;
12-
13-
exports[`SwapRouter swaps #exactOutput ETH input WETH9 gas 1`] = `137355`;
14-
15-
exports[`SwapRouter swaps #exactOutput ETH output WETH9 gas 1`] = `171531`;
16-
17-
exports[`SwapRouter swaps #exactOutput multi-pool gas 1`] = `242016`;
18-
19-
exports[`SwapRouter swaps #exactOutput single-pool gas 1`] = `147966`;

0 commit comments

Comments
 (0)