UNPKG

@uniswap/v4-core

Version:

🦄 Core smart contracts of Uniswap v4

567 lines (566 loc) • 24.7 kB
// import { ethers, waffle } from 'hardhat' // import { Wallet } from 'ethers' // import { PoolManager, PoolSwapTest, PoolDonateTest, PoolModifyPositionTest } from '../typechain' // import { expect } from './shared/expect' // import { ADDRESS_ZERO } from './shared/constants' // // import { tokensFixture } from './shared/fixtures' // import snapshotGasCost from '@uniswap/snapshot-gas-cost' // // import { // expandTo18Decimals, // FeeAmount, // getMinTick, // encodeSqrtPriceX96, // TICK_SPACINGS, // SwapFunction, // ModifyPositionFunction, // DonateFunction, // getMaxTick, // MaxUint128, // SwapToPriceFunction, // MIN_SQRT_PRICE, // getPoolId, // } from './shared/utilities' // // const { constants } = ethers // // const createFixtureLoader = waffle.createFixtureLoader // // type AsyncReturnType<T extends (...args: any) => any> = T extends (...args: any) => Promise<infer U> // ? U // : T extends (...args: any) => infer U // ? U // : any // // describe('PoolManager 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]) // }) // // const startingPrice = encodeSqrtPriceX96(100001, 100000) // const startingTick = 0 // const feeAmount = FeeAmount.MEDIUM // const tickSpacing = TICK_SPACINGS[feeAmount] // const minTick = getMinTick(tickSpacing) // const maxTick = getMaxTick(tickSpacing) // // describe('ERC20 tokens', () => { // const gasTestFixture = async ([wallet]: Wallet[]) => { // const { currency0, currency1 } = await tokensFixture() // // const singletonPoolFactory = await ethers.getContractFactory('PoolManager') // const swapTestFactory = await ethers.getContractFactory('PoolSwapTest') // const donateTestFactory = await ethers.getContractFactory('PoolDonateTest') // const mintTestFactory = await ethers.getContractFactory('PoolModifyPositionTest') // const CONTROLLER_GAS_LIMIT = 50000 // const manager = (await singletonPoolFactory.deploy(CONTROLLER_GAS_LIMIT)) as PoolManager // // const swapTest = (await swapTestFactory.deploy(manager.address)) as PoolSwapTest // const donateTest = (await donateTestFactory.deploy(manager.address)) as PoolDonateTest // const modifyPositionTest = (await mintTestFactory.deploy(manager.address)) as PoolModifyPositionTest // // for (const token of [currency0, currency1]) { // for (const spender of [swapTest, donateTest, modifyPositionTest]) { // await token.connect(wallet).approve(spender.address, constants.MaxUint256) // } // } // // const poolKey = { // currency0: currency0.address, // currency1: currency1.address, // fee: FeeAmount.MEDIUM, // tickSpacing: 60, // hooks: ADDRESS_ZERO, // } // // const swapExact0For1: SwapFunction = (amount, to, sqrtPriceLimitX96) => { // return swapTest.swap( // poolKey, // { // zeroForOne: true, // amountSpecified: amount, // sqrtPriceLimitX96: sqrtPriceLimitX96 ?? MIN_SQRT_PRICE.add(1), // }, // { // withdrawTokens: true, // settleUsingTransfer: true, // }, // '0x00' // ) // } // const swapToHigherPrice: SwapToPriceFunction = (sqrtPriceX96, to) => { // return swapTest.swap( // poolKey, // { // zeroForOne: false, // amountSpecified: MaxUint128, // sqrtPriceLimitX96: sqrtPriceX96, // }, // { // withdrawTokens: true, // settleUsingTransfer: true, // }, // '0x00' // ) // } // const swapToLowerPrice: SwapToPriceFunction = (sqrtPriceX96, to) => { // return swapTest.swap( // poolKey, // { // zeroForOne: true, // amountSpecified: MaxUint128, // sqrtPriceLimitX96: sqrtPriceX96, // }, // { // withdrawTokens: true, // settleUsingTransfer: true, // }, // '0x00' // ) // } // const modifyPosition: ModifyPositionFunction = (tickLower, tickUpper, liquidityDelta) => { // return modifyPositionTest.modifyPosition( // poolKey, // { // tickLower, // tickUpper, // liquidityDelta, // }, // '0x00' // ) // } // const donate: DonateFunction = (amount0, amount1) => { // return donateTest.donate(poolKey, amount0, amount1, '0x00') // } // const getSlot0 = async () => { // return await manager.getSlot0(getPoolId(poolKey)) // } // // await manager.initialize(poolKey, encodeSqrtPriceX96(1, 1), '0x00') // // await modifyPosition(minTick, maxTick, expandTo18Decimals(2)) // // await swapExact0For1(expandTo18Decimals(1), wallet.address) // await swapToHigherPrice(startingPrice, wallet.address) // // const { tick, sqrtPriceX96 } = await getSlot0() // // expect(tick).to.eq(startingTick) // expect(sqrtPriceX96).to.eq(startingPrice) // // return { manager, getSlot0, poolKey, swapExact0For1, modifyPosition, donate, swapToHigherPrice, swapToLowerPrice } // } // // let swapExact0For1: SwapFunction // let swapToHigherPrice: SwapToPriceFunction // let swapToLowerPrice: SwapToPriceFunction // let manager: PoolManager // let modifyPosition: ModifyPositionFunction // let donate: DonateFunction // let getSlot0: AsyncReturnType<typeof gasTestFixture>['getSlot0'] // let poolKey: AsyncReturnType<typeof gasTestFixture>['poolKey'] // // beforeEach('load the fixture', async () => { // ;({ swapExact0For1, manager, modifyPosition, donate, swapToHigherPrice, swapToLowerPrice, getSlot0, poolKey } = // await loadFixture(gasTestFixture)) // }) // // describe('#initialize', () => { // it('initialize pool with no hooks and no protocol fee', async () => { // let currency0 = wallet.address // let currency1 = other.address // // ;[currency0, currency1] = currency0 < currency1 ? [currency0, currency1] : [currency1, currency0] // // const altPoolKey = { // currency0, // currency1, // fee: FeeAmount.MEDIUM, // tickSpacing: 60, // hooks: '0x0000000000000000000000000000000000000000', // } // await snapshotGasCost(manager.initialize(altPoolKey, encodeSqrtPriceX96(1, 1), '0x00')) // }) // }) // // describe('#swapExact0For1', () => { // it('first swap in block with no tick movement', async () => { // await snapshotGasCost(swapExact0For1(2000, wallet.address)) // expect((await getSlot0()).sqrtPriceX96).to.not.eq(startingPrice) // expect((await getSlot0()).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 getSlot0()).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 getSlot0()).tick).to.eq(startingTick - 1) // await snapshotGasCost(swapExact0For1(2000, wallet.address)) // expect((await getSlot0()).tick).to.eq(startingTick - 1) // }) // // it('second swap in block moves tick, no initialized crossings', async () => { // await swapExact0For1(1000, wallet.address) // expect((await getSlot0()).tick).to.eq(startingTick) // await snapshotGasCost(swapExact0For1(expandTo18Decimals(1).div(10000), wallet.address)) // expect((await getSlot0()).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 getSlot0()).tick).to.eq(-35787) // }) // // it('first swap in block, large swap crossing several initialized ticks', async () => { // await modifyPosition(startingTick - 3 * tickSpacing, startingTick - tickSpacing, expandTo18Decimals(1)) // await modifyPosition(startingTick - 4 * tickSpacing, startingTick - 2 * tickSpacing, expandTo18Decimals(1)) // expect((await getSlot0()).tick).to.eq(startingTick) // await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address)) // expect((await getSlot0()).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 modifyPosition(minTick, startingTick - 2 * tickSpacing, expandTo18Decimals(1)) // await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address)) // expect((await getSlot0()).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 modifyPosition(startingTick - 3 * tickSpacing, startingTick - tickSpacing, expandTo18Decimals(1)) // await modifyPosition(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 getSlot0()).tick).to.be.lt(startingTick - 4 * tickSpacing) // }) // // it('second swap in block, large swap crossing a single initialized tick', async () => { // await modifyPosition(minTick, startingTick - 2 * tickSpacing, expandTo18Decimals(1)) // await swapExact0For1(expandTo18Decimals(1).div(10000), wallet.address) // expect((await getSlot0()).tick).to.be.gt(startingTick - 2 * tickSpacing) // we didn't cross the initialized tick // await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address)) // expect((await getSlot0()).tick).to.be.lt(startingTick - 2 * tickSpacing) // we crossed the last tick // }) // }) // // 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(modifyPosition(tickLower, tickUpper, expandTo18Decimals(1))) // }) // it('add to position existing', async () => { // await modifyPosition(tickLower, tickUpper, expandTo18Decimals(1)) // await snapshotGasCost(modifyPosition(tickLower, tickUpper, expandTo18Decimals(1))) // }) // it('second position in same range', async () => { // await modifyPosition(tickLower, tickUpper, expandTo18Decimals(1)) // await snapshotGasCost(modifyPosition(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 modifyPosition( 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 manager.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 modifyPosition( 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('Native Tokens', () => { // const gasTestFixture = async ([wallet]: Wallet[]) => { // const { currency1 } = await tokensFixture() // // const singletonPoolFactory = await ethers.getContractFactory('PoolManager') // const swapTestFactory = await ethers.getContractFactory('PoolSwapTest') // const donateTestFactory = await ethers.getContractFactory('PoolDonateTest') // const mintTestFactory = await ethers.getContractFactory('PoolModifyPositionTest') // const CONTROLLER_GAS_LIMIT = 50000 // const manager = (await singletonPoolFactory.deploy(CONTROLLER_GAS_LIMIT)) as PoolManager // // const swapTest = (await swapTestFactory.deploy(manager.address)) as PoolSwapTest // const donateTest = (await donateTestFactory.deploy(manager.address)) as PoolDonateTest // const modifyPositionTest = (await mintTestFactory.deploy(manager.address)) as PoolModifyPositionTest // // for (const spender of [swapTest, donateTest, modifyPositionTest]) { // await currency1.connect(wallet).approve(spender.address, constants.MaxUint256) // } // // const poolKey = { // currency0: ADDRESS_ZERO, // currency1: currency1.address, // fee: FeeAmount.MEDIUM, // tickSpacing: 60, // hooks: ADDRESS_ZERO, // } // // const swapExact0For1: SwapFunction = (amount, to, sqrtPriceLimitX96) => { // return swapTest.swap( // poolKey, // { // zeroForOne: true, // amountSpecified: amount, // sqrtPriceLimitX96: sqrtPriceLimitX96 ?? MIN_SQRT_PRICE.add(1), // }, // { // withdrawTokens: true, // settleUsingTransfer: true, // }, // '0x00', // { // value: amount, // } // ) // } // const swapToHigherPrice: SwapToPriceFunction = (sqrtPriceX96, to) => { // return swapTest.swap( // poolKey, // { // zeroForOne: false, // amountSpecified: MaxUint128, // sqrtPriceLimitX96: sqrtPriceX96, // }, // { // withdrawTokens: true, // settleUsingTransfer: true, // }, // '0x00' // ) // } // const swapToLowerPrice: SwapToPriceFunction = (sqrtPriceX96, to) => { // return swapTest.swap( // poolKey, // { // zeroForOne: true, // amountSpecified: MaxUint128, // sqrtPriceLimitX96: sqrtPriceX96, // }, // { // withdrawTokens: true, // settleUsingTransfer: true, // }, // '0x00', // { // value: MaxUint128, // } // ) // } // const modifyPosition: ModifyPositionFunction = (tickLower, tickUpper, liquidityDelta) => { // return modifyPositionTest.modifyPosition( // poolKey, // { // tickLower, // tickUpper, // liquidityDelta, // }, // '0x00', // { value: liquidityDelta } // ) // } // const donate: DonateFunction = (amount0, amount1) => { // return donateTest.donate(poolKey, amount0, amount1, '0x00', { value: amount0 }) // } // const getSlot0 = async () => { // return await manager.getSlot0(getPoolId(poolKey)) // } // // await manager.initialize(poolKey, encodeSqrtPriceX96(1, 1), '0x00') // // await modifyPosition(minTick, maxTick, expandTo18Decimals(2)) // // await swapExact0For1(expandTo18Decimals(1), wallet.address) // await swapToHigherPrice(startingPrice, wallet.address) // // const { tick, sqrtPriceX96 } = await getSlot0() // // expect(tick).to.eq(startingTick) // expect(sqrtPriceX96).to.eq(startingPrice) // // return { manager, getSlot0, poolKey, swapExact0For1, modifyPosition, donate, swapToHigherPrice, swapToLowerPrice } // } // // let swapExact0For1: SwapFunction // let swapToHigherPrice: SwapToPriceFunction // let swapToLowerPrice: SwapToPriceFunction // let manager: PoolManager // let modifyPosition: ModifyPositionFunction // let donate: DonateFunction // let getSlot0: AsyncReturnType<typeof gasTestFixture>['getSlot0'] // let poolKey: AsyncReturnType<typeof gasTestFixture>['poolKey'] // // beforeEach('load the fixture', async () => { // ;({ swapExact0For1, manager, modifyPosition, donate, swapToHigherPrice, swapToLowerPrice, getSlot0, poolKey } = // await loadFixture(gasTestFixture)) // }) // // describe('#swapExact0For1', () => { // it('first swap in block with no tick movement', async () => { // await snapshotGasCost(swapExact0For1(2000, wallet.address)) // expect((await getSlot0()).sqrtPriceX96).to.not.eq(startingPrice) // expect((await getSlot0()).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 getSlot0()).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 getSlot0()).tick).to.eq(startingTick - 1) // await snapshotGasCost(swapExact0For1(2000, wallet.address)) // expect((await getSlot0()).tick).to.eq(startingTick - 1) // }) // // it('second swap in block moves tick, no initialized crossings', async () => { // await swapExact0For1(1000, wallet.address) // expect((await getSlot0()).tick).to.eq(startingTick) // await snapshotGasCost(swapExact0For1(expandTo18Decimals(1).div(10000), wallet.address)) // expect((await getSlot0()).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 getSlot0()).tick).to.eq(-35787) // }) // // it('first swap in block, large swap crossing several initialized ticks', async () => { // await modifyPosition(startingTick - 3 * tickSpacing, startingTick - tickSpacing, expandTo18Decimals(1)) // await modifyPosition(startingTick - 4 * tickSpacing, startingTick - 2 * tickSpacing, expandTo18Decimals(1)) // expect((await getSlot0()).tick).to.eq(startingTick) // await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address)) // expect((await getSlot0()).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 modifyPosition(minTick, startingTick - 2 * tickSpacing, expandTo18Decimals(1)) // await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address)) // expect((await getSlot0()).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 modifyPosition(startingTick - 3 * tickSpacing, startingTick - tickSpacing, expandTo18Decimals(1)) // await modifyPosition(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 getSlot0()).tick).to.be.lt(startingTick - 4 * tickSpacing) // }) // // it('second swap in block, large swap crossing a single initialized tick', async () => { // await modifyPosition(minTick, startingTick - 2 * tickSpacing, expandTo18Decimals(1)) // await swapExact0For1(expandTo18Decimals(1).div(10000), wallet.address) // expect((await getSlot0()).tick).to.be.gt(startingTick - 2 * tickSpacing) // we didn't cross the initialized tick // await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address)) // expect((await getSlot0()).tick).to.be.lt(startingTick - 2 * tickSpacing) // we crossed the last tick // }) // }) // // 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(modifyPosition(tickLower, tickUpper, expandTo18Decimals(1))) // }) // it('add to position existing', async () => { // await modifyPosition(tickLower, tickUpper, expandTo18Decimals(1)) // await snapshotGasCost(modifyPosition(tickLower, tickUpper, expandTo18Decimals(1))) // }) // it('second position in same range', async () => { // await modifyPosition(tickLower, tickUpper, expandTo18Decimals(1)) // await snapshotGasCost(modifyPosition(tickLower, tickUpper, expandTo18Decimals(1))) // }) // }) // } // }) // }) // })