infrastructure-upgrade/lib/v3-core/test/UniswapV3Pool.gas.spec.ts
T-Hax 735546619e
init
Signed-off-by: T-Hax <>
2023-04-08 18:46:18 +00:00

316 lines
14 KiB
TypeScript

import { ethers, waffle } from 'hardhat'
import { Wallet } from 'ethers'
import { MockTimeUniswapV3Pool } from '../typechain/MockTimeUniswapV3Pool'
import { expect } from './shared/expect'
import { poolFixture } from './shared/fixtures'
import snapshotGasCost from './shared/snapshotGasCost'
import {
expandTo18Decimals,
FeeAmount,
getMinTick,
encodePriceSqrt,
TICK_SPACINGS,
createPoolFunctions,
SwapFunction,
MintFunction,
getMaxTick,
MaxUint128,
SwapToPriceFunction,
MAX_SQRT_RATIO,
MIN_SQRT_RATIO,
} from './shared/utilities'
const createFixtureLoader = waffle.createFixtureLoader
describe('UniswapV3Pool gas tests', () => {
let wallet: Wallet, other: Wallet
let loadFixture: ReturnType<typeof createFixtureLoader>
before('create fixture loader', async () => {
;[wallet, other] = await (ethers as any).getSigners()
loadFixture = createFixtureLoader([wallet, other])
})
for (const feeProtocol of [0, 6]) {
describe(feeProtocol > 0 ? 'fee is on' : 'fee is off', () => {
const startingPrice = encodePriceSqrt(100001, 100000)
const startingTick = 0
const feeAmount = FeeAmount.MEDIUM
const tickSpacing = TICK_SPACINGS[feeAmount]
const minTick = getMinTick(tickSpacing)
const maxTick = getMaxTick(tickSpacing)
const gasTestFixture = async ([wallet]: Wallet[]) => {
const fix = await poolFixture([wallet], waffle.provider)
const pool = await fix.createPool(feeAmount, tickSpacing)
const { swapExact0For1, swapToHigherPrice, mint, swapToLowerPrice } = await createPoolFunctions({
swapTarget: fix.swapTargetCallee,
token0: fix.token0,
token1: fix.token1,
pool,
})
await pool.initialize(encodePriceSqrt(1, 1))
await pool.setFeeProtocol(feeProtocol, feeProtocol)
await pool.increaseObservationCardinalityNext(4)
await pool.advanceTime(1)
await mint(wallet.address, minTick, maxTick, expandTo18Decimals(2))
await swapExact0For1(expandTo18Decimals(1), wallet.address)
await pool.advanceTime(1)
await swapToHigherPrice(startingPrice, wallet.address)
await pool.advanceTime(1)
expect((await pool.slot0()).tick).to.eq(startingTick)
expect((await pool.slot0()).sqrtPriceX96).to.eq(startingPrice)
return { pool, swapExact0For1, mint, swapToHigherPrice, swapToLowerPrice }
}
let swapExact0For1: SwapFunction
let swapToHigherPrice: SwapToPriceFunction
let swapToLowerPrice: SwapToPriceFunction
let pool: MockTimeUniswapV3Pool
let mint: MintFunction
beforeEach('load the fixture', async () => {
;({ swapExact0For1, pool, mint, swapToHigherPrice, swapToLowerPrice } = await loadFixture(gasTestFixture))
})
describe('#swapExact0For1', () => {
it('first swap in block with no tick movement', async () => {
await snapshotGasCost(swapExact0For1(2000, wallet.address))
expect((await pool.slot0()).sqrtPriceX96).to.not.eq(startingPrice)
expect((await pool.slot0()).tick).to.eq(startingTick)
})
it('first swap in block moves tick, no initialized crossings', async () => {
await snapshotGasCost(swapExact0For1(expandTo18Decimals(1).div(10000), wallet.address))
expect((await pool.slot0()).tick).to.eq(startingTick - 1)
})
it('second swap in block with no tick movement', async () => {
await swapExact0For1(expandTo18Decimals(1).div(10000), wallet.address)
expect((await pool.slot0()).tick).to.eq(startingTick - 1)
await snapshotGasCost(swapExact0For1(2000, wallet.address))
expect((await pool.slot0()).tick).to.eq(startingTick - 1)
})
it('second swap in block moves tick, no initialized crossings', async () => {
await swapExact0For1(1000, wallet.address)
expect((await pool.slot0()).tick).to.eq(startingTick)
await snapshotGasCost(swapExact0For1(expandTo18Decimals(1).div(10000), wallet.address))
expect((await pool.slot0()).tick).to.eq(startingTick - 1)
})
it('first swap in block, large swap, no initialized crossings', async () => {
await snapshotGasCost(swapExact0For1(expandTo18Decimals(10), wallet.address))
expect((await pool.slot0()).tick).to.eq(-35787)
})
it('first swap in block, large swap crossing several initialized ticks', async () => {
await mint(wallet.address, startingTick - 3 * tickSpacing, startingTick - tickSpacing, expandTo18Decimals(1))
await mint(
wallet.address,
startingTick - 4 * tickSpacing,
startingTick - 2 * tickSpacing,
expandTo18Decimals(1)
)
expect((await pool.slot0()).tick).to.eq(startingTick)
await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address))
expect((await pool.slot0()).tick).to.be.lt(startingTick - 4 * tickSpacing) // we crossed the last tick
})
it('first swap in block, large swap crossing a single initialized tick', async () => {
await mint(wallet.address, minTick, startingTick - 2 * tickSpacing, expandTo18Decimals(1))
await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address))
expect((await pool.slot0()).tick).to.be.lt(startingTick - 2 * tickSpacing) // we crossed the last tick
})
it('second swap in block, large swap crossing several initialized ticks', async () => {
await mint(wallet.address, startingTick - 3 * tickSpacing, startingTick - tickSpacing, expandTo18Decimals(1))
await mint(
wallet.address,
startingTick - 4 * tickSpacing,
startingTick - 2 * tickSpacing,
expandTo18Decimals(1)
)
await swapExact0For1(expandTo18Decimals(1).div(10000), wallet.address)
await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address))
expect((await pool.slot0()).tick).to.be.lt(startingTick - 4 * tickSpacing)
})
it('second swap in block, large swap crossing a single initialized tick', async () => {
await mint(wallet.address, minTick, startingTick - 2 * tickSpacing, expandTo18Decimals(1))
await swapExact0For1(expandTo18Decimals(1).div(10000), wallet.address)
expect((await pool.slot0()).tick).to.be.gt(startingTick - 2 * tickSpacing) // we didn't cross the initialized tick
await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address))
expect((await pool.slot0()).tick).to.be.lt(startingTick - 2 * tickSpacing) // we crossed the last tick
})
it('large swap crossing several initialized ticks after some time passes', async () => {
await mint(wallet.address, startingTick - 3 * tickSpacing, startingTick - tickSpacing, expandTo18Decimals(1))
await mint(
wallet.address,
startingTick - 4 * tickSpacing,
startingTick - 2 * tickSpacing,
expandTo18Decimals(1)
)
await swapExact0For1(2, wallet.address)
await pool.advanceTime(1)
await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address))
expect((await pool.slot0()).tick).to.be.lt(startingTick - 4 * tickSpacing)
})
it('large swap crossing several initialized ticks second time after some time passes', async () => {
await mint(wallet.address, startingTick - 3 * tickSpacing, startingTick - tickSpacing, expandTo18Decimals(1))
await mint(
wallet.address,
startingTick - 4 * tickSpacing,
startingTick - 2 * tickSpacing,
expandTo18Decimals(1)
)
await swapExact0For1(expandTo18Decimals(1), wallet.address)
await swapToHigherPrice(startingPrice, wallet.address)
await pool.advanceTime(1)
await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address))
expect((await pool.slot0()).tick).to.be.lt(tickSpacing * -4)
})
})
describe('#mint', () => {
for (const { description, tickLower, tickUpper } of [
{
description: 'around current price',
tickLower: startingTick - tickSpacing,
tickUpper: startingTick + tickSpacing,
},
{
description: 'below current price',
tickLower: startingTick - 2 * tickSpacing,
tickUpper: startingTick - tickSpacing,
},
{
description: 'above current price',
tickLower: startingTick + tickSpacing,
tickUpper: startingTick + 2 * tickSpacing,
},
]) {
describe(description, () => {
it('new position mint first in range', async () => {
await snapshotGasCost(mint(wallet.address, tickLower, tickUpper, expandTo18Decimals(1)))
})
it('add to position existing', async () => {
await mint(wallet.address, tickLower, tickUpper, expandTo18Decimals(1))
await snapshotGasCost(mint(wallet.address, tickLower, tickUpper, expandTo18Decimals(1)))
})
it('second position in same range', async () => {
await mint(wallet.address, tickLower, tickUpper, expandTo18Decimals(1))
await snapshotGasCost(mint(other.address, tickLower, tickUpper, expandTo18Decimals(1)))
})
it('add to position after some time passes', async () => {
await mint(wallet.address, tickLower, tickUpper, expandTo18Decimals(1))
await pool.advanceTime(1)
await snapshotGasCost(mint(wallet.address, tickLower, tickUpper, expandTo18Decimals(1)))
})
})
}
})
describe('#burn', () => {
for (const { description, tickLower, tickUpper } of [
{
description: 'around current price',
tickLower: startingTick - tickSpacing,
tickUpper: startingTick + tickSpacing,
},
{
description: 'below current price',
tickLower: startingTick - 2 * tickSpacing,
tickUpper: startingTick - tickSpacing,
},
{
description: 'above current price',
tickLower: startingTick + tickSpacing,
tickUpper: startingTick + 2 * tickSpacing,
},
]) {
describe(description, () => {
const liquidityAmount = expandTo18Decimals(1)
beforeEach('mint a position', async () => {
await mint(wallet.address, tickLower, tickUpper, liquidityAmount)
})
it('burn when only position using ticks', async () => {
await snapshotGasCost(pool.burn(tickLower, tickUpper, expandTo18Decimals(1)))
})
it('partial position burn', async () => {
await snapshotGasCost(pool.burn(tickLower, tickUpper, expandTo18Decimals(1).div(2)))
})
it('entire position burn but other positions are using the ticks', async () => {
await mint(other.address, tickLower, tickUpper, expandTo18Decimals(1))
await snapshotGasCost(pool.burn(tickLower, tickUpper, expandTo18Decimals(1)))
})
it('burn entire position after some time passes', async () => {
await pool.advanceTime(1)
await snapshotGasCost(pool.burn(tickLower, tickUpper, expandTo18Decimals(1)))
})
})
}
})
describe('#poke', () => {
const tickLower = startingTick - tickSpacing
const tickUpper = startingTick + tickSpacing
it('best case', async () => {
await mint(wallet.address, tickLower, tickUpper, expandTo18Decimals(1))
await swapExact0For1(expandTo18Decimals(1).div(100), wallet.address)
await pool.burn(tickLower, tickUpper, 0)
await swapExact0For1(expandTo18Decimals(1).div(100), wallet.address)
await snapshotGasCost(pool.burn(tickLower, tickUpper, 0))
})
})
describe('#collect', () => {
const tickLower = startingTick - tickSpacing
const tickUpper = startingTick + tickSpacing
it('close to worst case', async () => {
await mint(wallet.address, tickLower, tickUpper, expandTo18Decimals(1))
await swapExact0For1(expandTo18Decimals(1).div(100), wallet.address)
await pool.burn(tickLower, tickUpper, 0) // poke to accumulate fees
await snapshotGasCost(pool.collect(wallet.address, tickLower, tickUpper, MaxUint128, MaxUint128))
})
})
describe('#increaseObservationCardinalityNext', () => {
it('grow by 1 slot', async () => {
await snapshotGasCost(pool.increaseObservationCardinalityNext(5))
})
it('no op', async () => {
await snapshotGasCost(pool.increaseObservationCardinalityNext(3))
})
})
describe('#snapshotCumulativesInside', () => {
it('tick inside', async () => {
await snapshotGasCost(pool.estimateGas.snapshotCumulativesInside(minTick, maxTick))
})
it('tick above', async () => {
await swapToHigherPrice(MAX_SQRT_RATIO.sub(1), wallet.address)
await snapshotGasCost(pool.estimateGas.snapshotCumulativesInside(minTick, maxTick))
})
it('tick below', async () => {
await swapToLowerPrice(MIN_SQRT_RATIO.add(1), wallet.address)
await snapshotGasCost(pool.estimateGas.snapshotCumulativesInside(minTick, maxTick))
})
})
})
}
})