From 52c601e00037cec70c96932f48fda990a996a53b Mon Sep 17 00:00:00 2001 From: Lee Wei Yuan <35588002+weiyuan95@users.noreply.github.com> Date: Wed, 28 Sep 2022 15:44:07 +0800 Subject: [PATCH] feat(release): Update contracts to not use hardcoded pool bytecode hash (#5) * feat(release): Update contracts and compile configs * feat(release): Remove brittle gas tests --- contracts/libraries/PoolAddress.sol | 5 +- contracts/test/PoolAddressTest.sol | 3 +- hardhat.config.ts | 41 +- test/Base64.spec.ts | 8 - test/LiquidityAmounts.spec.ts | 83 -- test/Multicall.spec.ts | 10 - test/NFTDescriptor.spec.ts | 22 - test/NonfungiblePositionManager.spec.ts | 1354 +++++++++-------------- test/OracleLibrary.spec.ts | 12 - test/PairFlash.spec.ts | 2 - test/PoolAddress.spec.ts | 12 - test/PositionValue.spec.ts | 18 - test/QuoterV2.spec.ts | 37 +- test/SwapRouter.gas.spec.ts | 453 -------- test/SwapRouter.spec.ts | 3 - test/TickLens.spec.ts | 15 - test/V3Migrator.spec.ts | 29 - 17 files changed, 543 insertions(+), 1564 deletions(-) delete mode 100644 test/SwapRouter.gas.spec.ts diff --git a/contracts/libraries/PoolAddress.sol b/contracts/libraries/PoolAddress.sol index 534071d37..f2ae26465 100644 --- a/contracts/libraries/PoolAddress.sol +++ b/contracts/libraries/PoolAddress.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity =0.7.6; +import { UniswapV3Pool } from '@birthdayresearch/uniswap-v3-core/contracts/UniswapV3Pool.sol'; + /// @title Provides functions for deriving a pool address from the factory, tokens, and the fee library PoolAddress { - bytes32 internal constant POOL_INIT_CODE_HASH = 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54; /// @notice The identifying key of the pool struct PoolKey { @@ -39,7 +40,7 @@ library PoolAddress { hex'ff', factory, keccak256(abi.encode(key.token0, key.token1, key.fee)), - POOL_INIT_CODE_HASH + keccak256(type(UniswapV3Pool).creationCode) ) ) ) diff --git a/contracts/test/PoolAddressTest.sol b/contracts/test/PoolAddressTest.sol index c1571e472..b023ed370 100644 --- a/contracts/test/PoolAddressTest.sol +++ b/contracts/test/PoolAddressTest.sol @@ -2,10 +2,11 @@ pragma solidity =0.7.6; import '../libraries/PoolAddress.sol'; +import { UniswapV3Pool } from '@birthdayresearch/uniswap-v3-core/contracts/UniswapV3Pool.sol'; contract PoolAddressTest { function POOL_INIT_CODE_HASH() external pure returns (bytes32) { - return PoolAddress.POOL_INIT_CODE_HASH; + return keccak256(type(UniswapV3Pool).creationCode); } function computeAddress( diff --git a/hardhat.config.ts b/hardhat.config.ts index 57343da87..313112a31 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -4,41 +4,14 @@ import '@nomiclabs/hardhat-waffle' import 'hardhat-typechain' import 'hardhat-watcher' -const LOW_OPTIMIZER_COMPILER_SETTINGS = { - version: '0.7.6', - settings: { - evmVersion: 'istanbul', - optimizer: { - enabled: true, - runs: 2_000, - }, - metadata: { - bytecodeHash: 'none', - }, - }, -} - -const LOWEST_OPTIMIZER_COMPILER_SETTINGS = { - version: '0.7.6', - settings: { - evmVersion: 'istanbul', - optimizer: { - enabled: true, - runs: 1_000, - }, - metadata: { - bytecodeHash: 'none', - }, - }, -} - +// Compiler settings is in line with V3-Core const DEFAULT_COMPILER_SETTINGS = { version: '0.7.6', settings: { evmVersion: 'istanbul', optimizer: { enabled: true, - runs: 1_000_000, + runs: 800, }, metadata: { bytecodeHash: 'none', @@ -49,7 +22,8 @@ const DEFAULT_COMPILER_SETTINGS = { export default { networks: { hardhat: { - allowUnlimitedContractSize: false, + // Since we only need to deploy the contracts for testing, we don't care about the contract size + allowUnlimitedContractSize: true, }, mainnet: { url: `https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`, @@ -86,13 +60,6 @@ export default { }, solidity: { compilers: [DEFAULT_COMPILER_SETTINGS], - overrides: { - 'contracts/NonfungiblePositionManager.sol': LOW_OPTIMIZER_COMPILER_SETTINGS, - 'contracts/test/MockTimeNonfungiblePositionManager.sol': LOW_OPTIMIZER_COMPILER_SETTINGS, - 'contracts/test/NFTDescriptorTest.sol': LOWEST_OPTIMIZER_COMPILER_SETTINGS, - 'contracts/NonfungibleTokenPositionDescriptor.sol': LOWEST_OPTIMIZER_COMPILER_SETTINGS, - 'contracts/libraries/NFTDescriptor.sol': LOWEST_OPTIMIZER_COMPILER_SETTINGS, - }, }, watcher: { test: { diff --git a/test/Base64.spec.ts b/test/Base64.spec.ts index 5baef4b96..2452bfde7 100644 --- a/test/Base64.spec.ts +++ b/test/Base64.spec.ts @@ -3,7 +3,6 @@ import { base64Encode } from './shared/base64' import { expect } from './shared/expect' import { Base64Test } from '../typechain' import { randomBytes } from 'crypto' -import snapshotGasCost from './shared/snapshotGasCost' function stringToHex(str: string): string { return `0x${Buffer.from(str, 'utf8').toString('hex')}` @@ -39,10 +38,6 @@ describe('Base64', () => { it(`works for "${example}"`, async () => { expect(await base64.encode(stringToHex(example))).to.eq(base64Encode(example)) }) - - it(`gas cost of encode(${example})`, async () => { - await snapshotGasCost(base64.getGasCostOfEncode(stringToHex(example))) - }) } describe('max size string (24kB)', () => { @@ -56,9 +51,6 @@ describe('Base64', () => { it('correctness', async () => { expect(await base64.encode(stringToHex(str))).to.eq(base64Encode(str)) }) - it('gas cost', async () => { - await snapshotGasCost(base64.getGasCostOfEncode(stringToHex(str))) - }) }) it('tiny fuzzing', async () => { diff --git a/test/LiquidityAmounts.spec.ts b/test/LiquidityAmounts.spec.ts index b24dd3cad..3e9f9169b 100644 --- a/test/LiquidityAmounts.spec.ts +++ b/test/LiquidityAmounts.spec.ts @@ -3,7 +3,6 @@ import { LiquidityAmountsTest } from '../typechain/LiquidityAmountsTest' import { encodePriceSqrt } from './shared/encodePriceSqrt' import { expect } from './shared/expect' -import snapshotGasCost from './shared/snapshotGasCost' describe('LiquidityAmounts', async () => { let liquidityFromAmounts: LiquidityAmountsTest @@ -13,22 +12,6 @@ describe('LiquidityAmounts', async () => { liquidityFromAmounts = (await liquidityFromAmountsTestFactory.deploy()) as LiquidityAmountsTest }) - describe('#getLiquidityForAmount0', () => { - it('gas', async () => { - const sqrtPriceAX96 = encodePriceSqrt(100, 110) - const sqrtPriceBX96 = encodePriceSqrt(110, 100) - await snapshotGasCost(liquidityFromAmounts.getGasCostOfGetLiquidityForAmount0(sqrtPriceAX96, sqrtPriceBX96, 100)) - }) - }) - - describe('#getLiquidityForAmount1', () => { - it('gas', async () => { - const sqrtPriceAX96 = encodePriceSqrt(100, 110) - const sqrtPriceBX96 = encodePriceSqrt(110, 100) - await snapshotGasCost(liquidityFromAmounts.getGasCostOfGetLiquidityForAmount1(sqrtPriceAX96, sqrtPriceBX96, 100)) - }) - }) - describe('#getLiquidityForAmounts', () => { it('amounts for price inside', async () => { const sqrtPriceX96 = encodePriceSqrt(1, 1) @@ -99,47 +82,6 @@ describe('LiquidityAmounts', async () => { ) expect(liquidity).to.eq(2097) }) - - it('gas for price below', async () => { - const sqrtPriceX96 = encodePriceSqrt(99, 110) - const sqrtPriceAX96 = encodePriceSqrt(100, 110) - const sqrtPriceBX96 = encodePriceSqrt(110, 100) - await snapshotGasCost( - liquidityFromAmounts.getGasCostOfGetLiquidityForAmounts(sqrtPriceX96, sqrtPriceAX96, sqrtPriceBX96, 100, 200) - ) - }) - it('gas for price above', async () => { - const sqrtPriceX96 = encodePriceSqrt(111, 100) - const sqrtPriceAX96 = encodePriceSqrt(100, 110) - const sqrtPriceBX96 = encodePriceSqrt(110, 100) - await snapshotGasCost( - liquidityFromAmounts.getGasCostOfGetLiquidityForAmounts(sqrtPriceX96, sqrtPriceAX96, sqrtPriceBX96, 100, 200) - ) - }) - it('gas for price inside', async () => { - const sqrtPriceX96 = encodePriceSqrt(1, 1) - const sqrtPriceAX96 = encodePriceSqrt(100, 110) - const sqrtPriceBX96 = encodePriceSqrt(110, 100) - await snapshotGasCost( - liquidityFromAmounts.getGasCostOfGetLiquidityForAmounts(sqrtPriceX96, sqrtPriceAX96, sqrtPriceBX96, 100, 200) - ) - }) - }) - - describe('#getAmount0ForLiquidity', () => { - it('gas', async () => { - const sqrtPriceAX96 = encodePriceSqrt(100, 110) - const sqrtPriceBX96 = encodePriceSqrt(110, 100) - await snapshotGasCost(liquidityFromAmounts.getGasCostOfGetAmount0ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, 100)) - }) - }) - - describe('#getLiquidityForAmount1', () => { - it('gas', async () => { - const sqrtPriceAX96 = encodePriceSqrt(100, 110) - const sqrtPriceBX96 = encodePriceSqrt(110, 100) - await snapshotGasCost(liquidityFromAmounts.getGasCostOfGetAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, 100)) - }) }) describe('#getAmountsForLiquidity', () => { @@ -212,30 +154,5 @@ describe('LiquidityAmounts', async () => { expect(amount0).to.eq(0) expect(amount1).to.eq(199) }) - - it('gas for price below', async () => { - const sqrtPriceX96 = encodePriceSqrt(99, 110) - const sqrtPriceAX96 = encodePriceSqrt(100, 110) - const sqrtPriceBX96 = encodePriceSqrt(110, 100) - await snapshotGasCost( - liquidityFromAmounts.getGasCostOfGetAmountsForLiquidity(sqrtPriceX96, sqrtPriceAX96, sqrtPriceBX96, 2148) - ) - }) - it('gas for price above', async () => { - const sqrtPriceX96 = encodePriceSqrt(111, 100) - const sqrtPriceAX96 = encodePriceSqrt(100, 110) - const sqrtPriceBX96 = encodePriceSqrt(110, 100) - await snapshotGasCost( - liquidityFromAmounts.getGasCostOfGetAmountsForLiquidity(sqrtPriceX96, sqrtPriceAX96, sqrtPriceBX96, 1048) - ) - }) - it('gas for price inside', async () => { - const sqrtPriceX96 = encodePriceSqrt(1, 1) - const sqrtPriceAX96 = encodePriceSqrt(100, 110) - const sqrtPriceBX96 = encodePriceSqrt(110, 100) - await snapshotGasCost( - liquidityFromAmounts.getGasCostOfGetAmountsForLiquidity(sqrtPriceX96, sqrtPriceAX96, sqrtPriceBX96, 2097) - ) - }) }) }) diff --git a/test/Multicall.spec.ts b/test/Multicall.spec.ts index 44ca3342b..a2f9cd96f 100644 --- a/test/Multicall.spec.ts +++ b/test/Multicall.spec.ts @@ -3,8 +3,6 @@ import { ethers, waffle } from 'hardhat' import { TestMulticall } from '../typechain/TestMulticall' import { expect } from './shared/expect' -import snapshotGasCost from './shared/snapshotGasCost' - describe('Multicall', async () => { let wallets: Wallet[] @@ -54,12 +52,4 @@ describe('Multicall', async () => { expect(await multicall.returnSender()).to.eq(wallets[0].address) }) }) - - it('gas cost of pay w/o multicall', async () => { - await snapshotGasCost(multicall.pays({ value: 3 })) - }) - - it('gas cost of pay w/ multicall', async () => { - await snapshotGasCost(multicall.multicall([multicall.interface.encodeFunctionData('pays')], { value: 3 })) - }) }) diff --git a/test/NFTDescriptor.spec.ts b/test/NFTDescriptor.spec.ts index fce07b102..2f75a0855 100644 --- a/test/NFTDescriptor.spec.ts +++ b/test/NFTDescriptor.spec.ts @@ -5,7 +5,6 @@ import { expect } from './shared/expect' import { TestERC20Metadata, NFTDescriptorTest } from '../typechain' import { Fixture } from 'ethereum-waffle' import { FeeAmount, TICK_SPACINGS } from './shared/constants' -import snapshotGasCost from './shared/snapshotGasCost' import { formatSqrtRatioX96 } from './shared/formatSqrtRatioX96' import { getMaxTick, getMinTick } from './shared/ticks' import { randomBytes } from 'crypto' @@ -305,27 +304,6 @@ describe('NFTDescriptor', () => { }) }) - it('gas', async () => { - await snapshotGasCost( - nftDescriptor.getGasCostOfConstructTokenURI({ - tokenId, - baseTokenAddress, - quoteTokenAddress, - baseTokenSymbol, - quoteTokenSymbol, - baseTokenDecimals, - quoteTokenDecimals, - flipRatio, - tickLower, - tickUpper, - tickCurrent, - tickSpacing, - fee, - poolAddress, - }) - ) - }) - it('snapshot matches', async () => { // get snapshot with super rare special sparkle tokenId = 1 diff --git a/test/NonfungiblePositionManager.spec.ts b/test/NonfungiblePositionManager.spec.ts index 669d476ef..4a113c524 100644 --- a/test/NonfungiblePositionManager.spec.ts +++ b/test/NonfungiblePositionManager.spec.ts @@ -21,7 +21,6 @@ import { extractJSONFromURI } from './shared/extractJSONFromURI' import getPermitNFTSignature from './shared/getPermitNFTSignature' import { encodePath } from './shared/path' import poolAtAddress from './shared/poolAtAddress' -import snapshotGasCost from './shared/snapshotGasCost' import { getMaxTick, getMinTick } from './shared/ticks' import { sortedTokens } from './shared/tokenSort' @@ -36,7 +35,7 @@ describe('NonfungiblePositionManager', () => { weth9: IWETH9 router: SwapRouter }> = async (wallets, provider) => { - const { weth9, factory, tokens, nft, router } = await completeFixture(wallets, provider) + const {weth9, factory, tokens, nft, router} = await completeFixture(wallets, provider) // approve & fund wallets for (const token of tokens) { @@ -70,11 +69,7 @@ describe('NonfungiblePositionManager', () => { }) beforeEach('load fixture', async () => { - ;({ nft, factory, tokens, weth9, router } = await loadFixture(nftFixture)) - }) - - it('bytecode size', async () => { - expect(((await nft.provider.getCode(nft.address)).length - 2) / 2).to.matchSnapshot() + ;({nft, factory, tokens, weth9, router} = await loadFixture(nftFixture)) }) describe('#createAndInitializePoolIfNecessary', () => { @@ -102,7 +97,7 @@ describe('NonfungiblePositionManager', () => { tokens[1].address, FeeAmount.MEDIUM, encodePriceSqrt(1, 1), - { value: 1 } + {value: 1} ) }) @@ -151,18 +146,7 @@ describe('NonfungiblePositionManager', () => { [token0.address, token1.address, FeeAmount.MEDIUM, encodePriceSqrt(1, 1)] ) - await nft.multicall([createAndInitializePoolIfNecessaryData], { value: expandTo18Decimals(1) }) - }) - - it('gas', async () => { - await snapshotGasCost( - nft.createAndInitializePoolIfNecessary( - tokens[0].address, - tokens[1].address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) - ) + await nft.multicall([createAndInitializePoolIfNecessaryData], {value: expandTo18Decimals(1)}) }) }) @@ -297,622 +281,491 @@ describe('NonfungiblePositionManager', () => { expect(balanceBefore).to.eq(balanceAfter.add(receipt.gasUsed.mul(tx.gasPrice)).add(100)) }) - it('emits an event') - - it('gas first mint for pool', async () => { - await nft.createAndInitializePoolIfNecessary( - tokens[0].address, - tokens[1].address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) + describe('#increaseLiquidity', () => { + const tokenId = 1 + beforeEach('create a position', async () => { + await nft.createAndInitializePoolIfNecessary( + tokens[0].address, + tokens[1].address, + FeeAmount.MEDIUM, + encodePriceSqrt(1, 1) + ) - await snapshotGasCost( - nft.mint({ + await nft.mint({ token0: tokens[0].address, token1: tokens[1].address, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), fee: FeeAmount.MEDIUM, - recipient: wallet.address, + recipient: other.address, + amount0Desired: 1000, + amount1Desired: 1000, + amount0Min: 0, + amount1Min: 0, + deadline: 1, + }) + }) + + it('increases position liquidity', async () => { + await nft.increaseLiquidity({ + tokenId: tokenId, amount0Desired: 100, amount1Desired: 100, amount0Min: 0, amount1Min: 0, - deadline: 10, + deadline: 1, }) - ) - }) + const {liquidity} = await nft.positions(tokenId) + expect(liquidity).to.eq(1100) + }) - it('gas first mint for pool using eth with zero refund', async () => { - const [token0, token1] = sortedTokens(weth9, tokens[0]) - await nft.createAndInitializePoolIfNecessary( - token0.address, - token1.address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) + it('emits an event') - await snapshotGasCost( - nft.multicall( - [ - nft.interface.encodeFunctionData('mint', [ - { - token0: token0.address, - token1: token1.address, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - fee: FeeAmount.MEDIUM, - recipient: wallet.address, - amount0Desired: 100, - amount1Desired: 100, - amount0Min: 0, - amount1Min: 0, - deadline: 10, - }, - ]), - nft.interface.encodeFunctionData('refundETH'), - ], - { value: 100 } - ) - ) - }) + it('can be paid with ETH', async () => { + const [token0, token1] = sortedTokens(tokens[0], weth9) - it('gas first mint for pool using eth with non-zero refund', async () => { - const [token0, token1] = sortedTokens(weth9, tokens[0]) - await nft.createAndInitializePoolIfNecessary( - token0.address, - token1.address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) + const tokenId = 1 - await snapshotGasCost( - nft.multicall( - [ - nft.interface.encodeFunctionData('mint', [ - { - token0: token0.address, - token1: token1.address, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - fee: FeeAmount.MEDIUM, - recipient: wallet.address, - amount0Desired: 100, - amount1Desired: 100, - amount0Min: 0, - amount1Min: 0, - deadline: 10, - }, - ]), - nft.interface.encodeFunctionData('refundETH'), - ], - { value: 1000 } + await nft.createAndInitializePoolIfNecessary( + token0.address, + token1.address, + FeeAmount.MEDIUM, + encodePriceSqrt(1, 1) ) - ) - }) - - it('gas mint on same ticks', async () => { - await nft.createAndInitializePoolIfNecessary( - tokens[0].address, - tokens[1].address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) - await nft.mint({ - token0: tokens[0].address, - token1: tokens[1].address, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - fee: FeeAmount.MEDIUM, - recipient: other.address, - amount0Desired: 100, - amount1Desired: 100, - amount0Min: 0, - amount1Min: 0, - deadline: 10, - }) + const mintData = nft.interface.encodeFunctionData('mint', [ + { + token0: token0.address, + token1: token1.address, + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: other.address, + amount0Desired: 100, + amount1Desired: 100, + amount0Min: 0, + amount1Min: 0, + deadline: 1, + }, + ]) + const refundETHData = nft.interface.encodeFunctionData('unwrapWETH9', [0, other.address]) + await nft.multicall([mintData, refundETHData], {value: expandTo18Decimals(1)}) + + const increaseLiquidityData = nft.interface.encodeFunctionData('increaseLiquidity', [ + { + tokenId: tokenId, + amount0Desired: 100, + amount1Desired: 100, + amount0Min: 0, + amount1Min: 0, + deadline: 1, + }, + ]) + await nft.multicall([increaseLiquidityData, refundETHData], {value: expandTo18Decimals(1)}) + }) + + }) + + describe('#decreaseLiquidity', () => { + const tokenId = 1 + beforeEach('create a position', async () => { + await nft.createAndInitializePoolIfNecessary( + tokens[0].address, + tokens[1].address, + FeeAmount.MEDIUM, + encodePriceSqrt(1, 1) + ) - await snapshotGasCost( - nft.mint({ + await nft.mint({ token0: tokens[0].address, token1: tokens[1].address, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), fee: FeeAmount.MEDIUM, - recipient: wallet.address, + recipient: other.address, amount0Desired: 100, amount1Desired: 100, amount0Min: 0, amount1Min: 0, - deadline: 10, + deadline: 1, }) - ) - }) - - it('gas mint for same pool, different ticks', async () => { - await nft.createAndInitializePoolIfNecessary( - tokens[0].address, - tokens[1].address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) - - await nft.mint({ - token0: tokens[0].address, - token1: tokens[1].address, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - fee: FeeAmount.MEDIUM, - recipient: other.address, - amount0Desired: 100, - amount1Desired: 100, - amount0Min: 0, - amount1Min: 0, - deadline: 10, }) - await snapshotGasCost( - nft.mint({ - token0: tokens[0].address, - token1: tokens[1].address, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]) + TICK_SPACINGS[FeeAmount.MEDIUM], - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]) - TICK_SPACINGS[FeeAmount.MEDIUM], - fee: FeeAmount.MEDIUM, - recipient: wallet.address, - amount0Desired: 100, - amount1Desired: 100, - amount0Min: 0, - amount1Min: 0, - deadline: 10, - }) - ) - }) - }) + it('emits an event') - describe('#increaseLiquidity', () => { - const tokenId = 1 - beforeEach('create a position', async () => { - await nft.createAndInitializePoolIfNecessary( - tokens[0].address, - tokens[1].address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) + it('fails if past deadline', async () => { + await nft.setTime(2) + await expect( + nft.connect(other).decreaseLiquidity({tokenId, liquidity: 50, amount0Min: 0, amount1Min: 0, deadline: 1}) + ).to.be.revertedWith('Transaction too old') + }) - await nft.mint({ - token0: tokens[0].address, - token1: tokens[1].address, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - fee: FeeAmount.MEDIUM, - recipient: other.address, - amount0Desired: 1000, - amount1Desired: 1000, - amount0Min: 0, - amount1Min: 0, - deadline: 1, + it('cannot be called by other addresses', async () => { + await expect( + nft.decreaseLiquidity({tokenId, liquidity: 50, amount0Min: 0, amount1Min: 0, deadline: 1}) + ).to.be.revertedWith('Not approved') }) - }) - it('increases position liquidity', async () => { - await nft.increaseLiquidity({ - tokenId: tokenId, - amount0Desired: 100, - amount1Desired: 100, - amount0Min: 0, - amount1Min: 0, - deadline: 1, + it('decreases position liquidity', async () => { + await nft.connect(other).decreaseLiquidity({tokenId, liquidity: 25, amount0Min: 0, amount1Min: 0, deadline: 1}) + const {liquidity} = await nft.positions(tokenId) + expect(liquidity).to.eq(75) }) - const { liquidity } = await nft.positions(tokenId) - expect(liquidity).to.eq(1100) - }) - it('emits an event') + it('is payable', async () => { + await nft + .connect(other) + .decreaseLiquidity({tokenId, liquidity: 25, amount0Min: 0, amount1Min: 0, deadline: 1}, {value: 1}) + }) - it('can be paid with ETH', async () => { - const [token0, token1] = sortedTokens(tokens[0], weth9) + it('accounts for tokens owed', async () => { + await nft.connect(other).decreaseLiquidity({tokenId, liquidity: 25, amount0Min: 0, amount1Min: 0, deadline: 1}) + const {tokensOwed0, tokensOwed1} = await nft.positions(tokenId) + expect(tokensOwed0).to.eq(24) + expect(tokensOwed1).to.eq(24) + }) - const tokenId = 1 + it('can decrease for all the liquidity', async () => { + await nft.connect(other).decreaseLiquidity({tokenId, liquidity: 100, amount0Min: 0, amount1Min: 0, deadline: 1}) + const {liquidity} = await nft.positions(tokenId) + expect(liquidity).to.eq(0) + }) - await nft.createAndInitializePoolIfNecessary( - token0.address, - token1.address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) + it('cannot decrease for more than all the liquidity', async () => { + await expect( + nft.connect(other).decreaseLiquidity({tokenId, liquidity: 101, amount0Min: 0, amount1Min: 0, deadline: 1}) + ).to.be.reverted + }) - const mintData = nft.interface.encodeFunctionData('mint', [ - { - token0: token0.address, - token1: token1.address, - fee: FeeAmount.MEDIUM, + it('cannot decrease for more than the liquidity of the nft position', async () => { + await nft.mint({ + token0: tokens[0].address, + token1: tokens[1].address, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + fee: FeeAmount.MEDIUM, recipient: other.address, - amount0Desired: 100, - amount1Desired: 100, + amount0Desired: 200, + amount1Desired: 200, amount0Min: 0, amount1Min: 0, deadline: 1, - }, - ]) - const refundETHData = nft.interface.encodeFunctionData('unwrapWETH9', [0, other.address]) - await nft.multicall([mintData, refundETHData], { value: expandTo18Decimals(1) }) + }) + await expect( + nft.connect(other).decreaseLiquidity({tokenId, liquidity: 101, amount0Min: 0, amount1Min: 0, deadline: 1}) + ).to.be.reverted + }) - const increaseLiquidityData = nft.interface.encodeFunctionData('increaseLiquidity', [ - { - tokenId: tokenId, - amount0Desired: 100, - amount1Desired: 100, - amount0Min: 0, - amount1Min: 0, - deadline: 1, - }, - ]) - await nft.multicall([increaseLiquidityData, refundETHData], { value: expandTo18Decimals(1) }) }) - it('gas', async () => { - await snapshotGasCost( - nft.increaseLiquidity({ - tokenId: tokenId, + describe('#collect', () => { + const tokenId = 1 + beforeEach('create a position', async () => { + await nft.createAndInitializePoolIfNecessary( + tokens[0].address, + tokens[1].address, + FeeAmount.MEDIUM, + encodePriceSqrt(1, 1) + ) + + await nft.mint({ + token0: tokens[0].address, + token1: tokens[1].address, + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: other.address, amount0Desired: 100, amount1Desired: 100, amount0Min: 0, amount1Min: 0, deadline: 1, }) - ) - }) - }) + }) - describe('#decreaseLiquidity', () => { - const tokenId = 1 - beforeEach('create a position', async () => { - await nft.createAndInitializePoolIfNecessary( - tokens[0].address, - tokens[1].address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) + it('emits an event') - await nft.mint({ - token0: tokens[0].address, - token1: tokens[1].address, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - fee: FeeAmount.MEDIUM, - recipient: other.address, - amount0Desired: 100, - amount1Desired: 100, - amount0Min: 0, - amount1Min: 0, - deadline: 1, + it('cannot be called by other addresses', async () => { + await expect( + nft.collect({ + tokenId, + recipient: wallet.address, + amount0Max: MaxUint128, + amount1Max: MaxUint128, + }) + ).to.be.revertedWith('Not approved') }) - }) - - it('emits an event') - it('fails if past deadline', async () => { - await nft.setTime(2) - await expect( - nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 50, amount0Min: 0, amount1Min: 0, deadline: 1 }) - ).to.be.revertedWith('Transaction too old') - }) + it('cannot be called with 0 for both amounts', async () => { + await expect( + nft.connect(other).collect({ + tokenId, + recipient: wallet.address, + amount0Max: 0, + amount1Max: 0, + }) + ).to.be.reverted + }) - it('cannot be called by other addresses', async () => { - await expect( - nft.decreaseLiquidity({ tokenId, liquidity: 50, amount0Min: 0, amount1Min: 0, deadline: 1 }) - ).to.be.revertedWith('Not approved') - }) + it('no op if no tokens are owed', async () => { + await expect( + nft.connect(other).collect({ + tokenId, + recipient: wallet.address, + amount0Max: MaxUint128, + amount1Max: MaxUint128, + }) + ) + .to.not.emit(tokens[0], 'Transfer') + .to.not.emit(tokens[1], 'Transfer') + }) - it('decreases position liquidity', async () => { - await nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 25, amount0Min: 0, amount1Min: 0, deadline: 1 }) - const { liquidity } = await nft.positions(tokenId) - expect(liquidity).to.eq(75) - }) + it('transfers tokens owed from burn', async () => { + await nft.connect(other).decreaseLiquidity({tokenId, liquidity: 50, amount0Min: 0, amount1Min: 0, deadline: 1}) + const poolAddress = computePoolAddress(factory.address, [tokens[0].address, tokens[1].address], FeeAmount.MEDIUM) + await expect( + nft.connect(other).collect({ + tokenId, + recipient: wallet.address, + amount0Max: MaxUint128, + amount1Max: MaxUint128, + }) + ) + .to.emit(tokens[0], 'Transfer') + .withArgs(poolAddress, wallet.address, 49) + .to.emit(tokens[1], 'Transfer') + .withArgs(poolAddress, wallet.address, 49) + }) - it('is payable', async () => { - await nft - .connect(other) - .decreaseLiquidity({ tokenId, liquidity: 25, amount0Min: 0, amount1Min: 0, deadline: 1 }, { value: 1 }) }) - it('accounts for tokens owed', async () => { - await nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 25, amount0Min: 0, amount1Min: 0, deadline: 1 }) - const { tokensOwed0, tokensOwed1 } = await nft.positions(tokenId) - expect(tokensOwed0).to.eq(24) - expect(tokensOwed1).to.eq(24) - }) - - it('can decrease for all the liquidity', async () => { - await nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 100, amount0Min: 0, amount1Min: 0, deadline: 1 }) - const { liquidity } = await nft.positions(tokenId) - expect(liquidity).to.eq(0) - }) - - it('cannot decrease for more than all the liquidity', async () => { - await expect( - nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 101, amount0Min: 0, amount1Min: 0, deadline: 1 }) - ).to.be.reverted - }) + describe('#burn', () => { + const tokenId = 1 + beforeEach('create a position', async () => { + await nft.createAndInitializePoolIfNecessary( + tokens[0].address, + tokens[1].address, + FeeAmount.MEDIUM, + encodePriceSqrt(1, 1) + ) - it('cannot decrease for more than the liquidity of the nft position', async () => { - await nft.mint({ - token0: tokens[0].address, - token1: tokens[1].address, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - fee: FeeAmount.MEDIUM, - recipient: other.address, - amount0Desired: 200, - amount1Desired: 200, - amount0Min: 0, - amount1Min: 0, - deadline: 1, + await nft.mint({ + token0: tokens[0].address, + token1: tokens[1].address, + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: other.address, + amount0Desired: 100, + amount1Desired: 100, + amount0Min: 0, + amount1Min: 0, + deadline: 1, + }) }) - await expect( - nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 101, amount0Min: 0, amount1Min: 0, deadline: 1 }) - ).to.be.reverted - }) - it('gas partial decrease', async () => { - await snapshotGasCost( - nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 50, amount0Min: 0, amount1Min: 0, deadline: 1 }) - ) - }) + it('emits an event') - it('gas complete decrease', async () => { - await snapshotGasCost( - nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 100, amount0Min: 0, amount1Min: 0, deadline: 1 }) - ) - }) - }) + it('cannot be called by other addresses', async () => { + await expect(nft.burn(tokenId)).to.be.revertedWith('Not approved') + }) - describe('#collect', () => { - const tokenId = 1 - beforeEach('create a position', async () => { - await nft.createAndInitializePoolIfNecessary( - tokens[0].address, - tokens[1].address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) + it('cannot be called while there is still liquidity', async () => { + await expect(nft.connect(other).burn(tokenId)).to.be.revertedWith('Not cleared') + }) - await nft.mint({ - token0: tokens[0].address, - token1: tokens[1].address, - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: other.address, - amount0Desired: 100, - amount1Desired: 100, - amount0Min: 0, - amount1Min: 0, - deadline: 1, + it('cannot be called while there is still partial liquidity', async () => { + await nft.connect(other).decreaseLiquidity({tokenId, liquidity: 50, amount0Min: 0, amount1Min: 0, deadline: 1}) + await expect(nft.connect(other).burn(tokenId)).to.be.revertedWith('Not cleared') }) - }) - it('emits an event') + it('cannot be called while there is still tokens owed', async () => { + await nft.connect(other).decreaseLiquidity({tokenId, liquidity: 100, amount0Min: 0, amount1Min: 0, deadline: 1}) + await expect(nft.connect(other).burn(tokenId)).to.be.revertedWith('Not cleared') + }) - it('cannot be called by other addresses', async () => { - await expect( - nft.collect({ + it('deletes the token', async () => { + await nft.connect(other).decreaseLiquidity({tokenId, liquidity: 100, amount0Min: 0, amount1Min: 0, deadline: 1}) + await nft.connect(other).collect({ tokenId, recipient: wallet.address, amount0Max: MaxUint128, amount1Max: MaxUint128, }) - ).to.be.revertedWith('Not approved') - }) - - it('cannot be called with 0 for both amounts', async () => { - await expect( - nft.connect(other).collect({ - tokenId, - recipient: wallet.address, - amount0Max: 0, - amount1Max: 0, - }) - ).to.be.reverted - }) + await nft.connect(other).burn(tokenId) + await expect(nft.positions(tokenId)).to.be.revertedWith('Invalid token ID') + }) - it('no op if no tokens are owed', async () => { - await expect( - nft.connect(other).collect({ - tokenId, - recipient: wallet.address, - amount0Max: MaxUint128, - amount1Max: MaxUint128, - }) - ) - .to.not.emit(tokens[0], 'Transfer') - .to.not.emit(tokens[1], 'Transfer') }) - it('transfers tokens owed from burn', async () => { - await nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 50, amount0Min: 0, amount1Min: 0, deadline: 1 }) - const poolAddress = computePoolAddress(factory.address, [tokens[0].address, tokens[1].address], FeeAmount.MEDIUM) - await expect( - nft.connect(other).collect({ - tokenId, - recipient: wallet.address, - amount0Max: MaxUint128, - amount1Max: MaxUint128, - }) - ) - .to.emit(tokens[0], 'Transfer') - .withArgs(poolAddress, wallet.address, 49) - .to.emit(tokens[1], 'Transfer') - .withArgs(poolAddress, wallet.address, 49) - }) + describe('#transferFrom', () => { + const tokenId = 1 + beforeEach('create a position', async () => { + await nft.createAndInitializePoolIfNecessary( + tokens[0].address, + tokens[1].address, + FeeAmount.MEDIUM, + encodePriceSqrt(1, 1) + ) - it('gas transfers both', async () => { - await nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 50, amount0Min: 0, amount1Min: 0, deadline: 1 }) - await snapshotGasCost( - nft.connect(other).collect({ - tokenId, - recipient: wallet.address, - amount0Max: MaxUint128, - amount1Max: MaxUint128, + await nft.mint({ + token0: tokens[0].address, + token1: tokens[1].address, + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: other.address, + amount0Desired: 100, + amount1Desired: 100, + amount0Min: 0, + amount1Min: 0, + deadline: 1, }) - ) - }) + }) - it('gas transfers token0 only', async () => { - await nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 50, amount0Min: 0, amount1Min: 0, deadline: 1 }) - await snapshotGasCost( - nft.connect(other).collect({ - tokenId, - recipient: wallet.address, - amount0Max: MaxUint128, - amount1Max: 0, - }) - ) - }) + it('can only be called by authorized or owner', async () => { + await expect(nft.transferFrom(other.address, wallet.address, tokenId)).to.be.revertedWith( + 'ERC721: transfer caller is not owner nor approved' + ) + }) - it('gas transfers token1 only', async () => { - await nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 50, amount0Min: 0, amount1Min: 0, deadline: 1 }) - await snapshotGasCost( - nft.connect(other).collect({ - tokenId, - recipient: wallet.address, - amount0Max: 0, - amount1Max: MaxUint128, - }) - ) - }) - }) + it('changes the owner', async () => { + await nft.connect(other).transferFrom(other.address, wallet.address, tokenId) + expect(await nft.ownerOf(tokenId)).to.eq(wallet.address) + }) - describe('#burn', () => { - const tokenId = 1 - beforeEach('create a position', async () => { - await nft.createAndInitializePoolIfNecessary( - tokens[0].address, - tokens[1].address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) + it('removes existing approval', async () => { + await nft.connect(other).approve(wallet.address, tokenId) + expect(await nft.getApproved(tokenId)).to.eq(wallet.address) + await nft.transferFrom(other.address, wallet.address, tokenId) + expect(await nft.getApproved(tokenId)).to.eq(constants.AddressZero) + }) - await nft.mint({ - token0: tokens[0].address, - token1: tokens[1].address, - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: other.address, - amount0Desired: 100, - amount1Desired: 100, - amount0Min: 0, - amount1Min: 0, - deadline: 1, + it('gas comes from approved', async () => { + await nft.connect(other).approve(wallet.address, tokenId) }) }) - it('emits an event') + describe('#permit', () => { + it('emits an event') - it('cannot be called by other addresses', async () => { - await expect(nft.burn(tokenId)).to.be.revertedWith('Not approved') - }) + describe('owned by eoa', () => { + const tokenId = 1 + beforeEach('create a position', async () => { + await nft.createAndInitializePoolIfNecessary( + tokens[0].address, + tokens[1].address, + FeeAmount.MEDIUM, + encodePriceSqrt(1, 1) + ) - it('cannot be called while there is still liquidity', async () => { - await expect(nft.connect(other).burn(tokenId)).to.be.revertedWith('Not cleared') - }) + await nft.mint({ + token0: tokens[0].address, + token1: tokens[1].address, + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: other.address, + amount0Desired: 100, + amount1Desired: 100, + amount0Min: 0, + amount1Min: 0, + deadline: 1, + }) + }) - it('cannot be called while there is still partial liquidity', async () => { - await nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 50, amount0Min: 0, amount1Min: 0, deadline: 1 }) - await expect(nft.connect(other).burn(tokenId)).to.be.revertedWith('Not cleared') - }) + it('changes the operator of the position and increments the nonce', async () => { + const {v, r, s} = await getPermitNFTSignature(other, nft, wallet.address, tokenId, 1) + await nft.permit(wallet.address, tokenId, 1, v, r, s) + expect((await nft.positions(tokenId)).nonce).to.eq(1) + expect((await nft.positions(tokenId)).operator).to.eq(wallet.address) + }) - it('cannot be called while there is still tokens owed', async () => { - await nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 100, amount0Min: 0, amount1Min: 0, deadline: 1 }) - await expect(nft.connect(other).burn(tokenId)).to.be.revertedWith('Not cleared') - }) + it('cannot be called twice with the same signature', async () => { + const {v, r, s} = await getPermitNFTSignature(other, nft, wallet.address, tokenId, 1) + await nft.permit(wallet.address, tokenId, 1, v, r, s) + await expect(nft.permit(wallet.address, tokenId, 1, v, r, s)).to.be.reverted + }) - it('deletes the token', async () => { - await nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 100, amount0Min: 0, amount1Min: 0, deadline: 1 }) - await nft.connect(other).collect({ - tokenId, - recipient: wallet.address, - amount0Max: MaxUint128, - amount1Max: MaxUint128, - }) - await nft.connect(other).burn(tokenId) - await expect(nft.positions(tokenId)).to.be.revertedWith('Invalid token ID') - }) + it('fails with invalid signature', async () => { + const {v, r, s} = await getPermitNFTSignature(wallet, nft, wallet.address, tokenId, 1) + await expect(nft.permit(wallet.address, tokenId, 1, v + 3, r, s)).to.be.revertedWith('Invalid signature') + }) - it('gas', async () => { - await nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 100, amount0Min: 0, amount1Min: 0, deadline: 1 }) - await nft.connect(other).collect({ - tokenId, - recipient: wallet.address, - amount0Max: MaxUint128, - amount1Max: MaxUint128, - }) - await snapshotGasCost(nft.connect(other).burn(tokenId)) - }) - }) + it('fails with signature not from owner', async () => { + const {v, r, s} = await getPermitNFTSignature(wallet, nft, wallet.address, tokenId, 1) + await expect(nft.permit(wallet.address, tokenId, 1, v, r, s)).to.be.revertedWith('Unauthorized') + }) - describe('#transferFrom', () => { - const tokenId = 1 - beforeEach('create a position', async () => { - await nft.createAndInitializePoolIfNecessary( - tokens[0].address, - tokens[1].address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) + it('fails with expired signature', async () => { + await nft.setTime(2) + const {v, r, s} = await getPermitNFTSignature(other, nft, wallet.address, tokenId, 1) + await expect(nft.permit(wallet.address, tokenId, 1, v, r, s)).to.be.revertedWith('Permit expired') + }) - await nft.mint({ - token0: tokens[0].address, - token1: tokens[1].address, - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: other.address, - amount0Desired: 100, - amount1Desired: 100, - amount0Min: 0, - amount1Min: 0, - deadline: 1, }) - }) + describe('owned by verifying contract', () => { + const tokenId = 1 + let testPositionNFTOwner: TestPositionNFTOwner + + beforeEach('deploy test owner and create a position', async () => { + testPositionNFTOwner = (await ( + await ethers.getContractFactory('TestPositionNFTOwner') + ).deploy()) as TestPositionNFTOwner + + await nft.createAndInitializePoolIfNecessary( + tokens[0].address, + tokens[1].address, + FeeAmount.MEDIUM, + encodePriceSqrt(1, 1) + ) + + await nft.mint({ + token0: tokens[0].address, + token1: tokens[1].address, + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: testPositionNFTOwner.address, + amount0Desired: 100, + amount1Desired: 100, + amount0Min: 0, + amount1Min: 0, + deadline: 1, + }) + }) - it('can only be called by authorized or owner', async () => { - await expect(nft.transferFrom(other.address, wallet.address, tokenId)).to.be.revertedWith( - 'ERC721: transfer caller is not owner nor approved' - ) - }) + it('changes the operator of the position and increments the nonce', async () => { + const {v, r, s} = await getPermitNFTSignature(other, nft, wallet.address, tokenId, 1) + await testPositionNFTOwner.setOwner(other.address) + await nft.permit(wallet.address, tokenId, 1, v, r, s) + expect((await nft.positions(tokenId)).nonce).to.eq(1) + expect((await nft.positions(tokenId)).operator).to.eq(wallet.address) + }) - it('changes the owner', async () => { - await nft.connect(other).transferFrom(other.address, wallet.address, tokenId) - expect(await nft.ownerOf(tokenId)).to.eq(wallet.address) - }) + it('fails if owner contract is owned by different address', async () => { + const {v, r, s} = await getPermitNFTSignature(other, nft, wallet.address, tokenId, 1) + await testPositionNFTOwner.setOwner(wallet.address) + await expect(nft.permit(wallet.address, tokenId, 1, v, r, s)).to.be.revertedWith('Unauthorized') + }) - it('removes existing approval', async () => { - await nft.connect(other).approve(wallet.address, tokenId) - expect(await nft.getApproved(tokenId)).to.eq(wallet.address) - await nft.transferFrom(other.address, wallet.address, tokenId) - expect(await nft.getApproved(tokenId)).to.eq(constants.AddressZero) - }) + it('fails with signature not from owner', async () => { + const {v, r, s} = await getPermitNFTSignature(wallet, nft, wallet.address, tokenId, 1) + await testPositionNFTOwner.setOwner(other.address) + await expect(nft.permit(wallet.address, tokenId, 1, v, r, s)).to.be.revertedWith('Unauthorized') + }) - it('gas', async () => { - await snapshotGasCost(nft.connect(other).transferFrom(other.address, wallet.address, tokenId)) - }) + it('fails with expired signature', async () => { + await nft.setTime(2) + const {v, r, s} = await getPermitNFTSignature(other, nft, wallet.address, tokenId, 1) + await testPositionNFTOwner.setOwner(other.address) + await expect(nft.permit(wallet.address, tokenId, 1, v, r, s)).to.be.revertedWith('Permit expired') + }) - it('gas comes from approved', async () => { - await nft.connect(other).approve(wallet.address, tokenId) - await snapshotGasCost(nft.transferFrom(other.address, wallet.address, tokenId)) + }) }) - }) - describe('#permit', () => { - it('emits an event') - - describe('owned by eoa', () => { + describe('multicall exit', () => { const tokenId = 1 beforeEach('create a position', async () => { await nft.createAndInitializePoolIfNecessary( @@ -937,49 +790,60 @@ describe('NonfungiblePositionManager', () => { }) }) - it('changes the operator of the position and increments the nonce', async () => { - const { v, r, s } = await getPermitNFTSignature(other, nft, wallet.address, tokenId, 1) - await nft.permit(wallet.address, tokenId, 1, v, r, s) - expect((await nft.positions(tokenId)).nonce).to.eq(1) - expect((await nft.positions(tokenId)).operator).to.eq(wallet.address) - }) - - it('cannot be called twice with the same signature', async () => { - const { v, r, s } = await getPermitNFTSignature(other, nft, wallet.address, tokenId, 1) - await nft.permit(wallet.address, tokenId, 1, v, r, s) - await expect(nft.permit(wallet.address, tokenId, 1, v, r, s)).to.be.reverted - }) - - it('fails with invalid signature', async () => { - const { v, r, s } = await getPermitNFTSignature(wallet, nft, wallet.address, tokenId, 1) - await expect(nft.permit(wallet.address, tokenId, 1, v + 3, r, s)).to.be.revertedWith('Invalid signature') - }) - - it('fails with signature not from owner', async () => { - const { v, r, s } = await getPermitNFTSignature(wallet, nft, wallet.address, tokenId, 1) - await expect(nft.permit(wallet.address, tokenId, 1, v, r, s)).to.be.revertedWith('Unauthorized') - }) + async function exit({ + nft, + liquidity, + tokenId, + amount0Min, + amount1Min, + recipient, + }: { + nft: MockTimeNonfungiblePositionManager + tokenId: BigNumberish + liquidity: BigNumberish + amount0Min: BigNumberish + amount1Min: BigNumberish + recipient: string + }) { + const decreaseLiquidityData = nft.interface.encodeFunctionData('decreaseLiquidity', [ + {tokenId, liquidity, amount0Min, amount1Min, deadline: 1}, + ]) + const collectData = nft.interface.encodeFunctionData('collect', [ + { + tokenId, + recipient, + amount0Max: MaxUint128, + amount1Max: MaxUint128, + }, + ]) + const burnData = nft.interface.encodeFunctionData('burn', [tokenId]) - it('fails with expired signature', async () => { - await nft.setTime(2) - const { v, r, s } = await getPermitNFTSignature(other, nft, wallet.address, tokenId, 1) - await expect(nft.permit(wallet.address, tokenId, 1, v, r, s)).to.be.revertedWith('Permit expired') - }) + return nft.multicall([decreaseLiquidityData, collectData, burnData]) + } - it('gas', async () => { - const { v, r, s } = await getPermitNFTSignature(other, nft, wallet.address, tokenId, 1) - await snapshotGasCost(nft.permit(wallet.address, tokenId, 1, v, r, s)) + it('executes all the actions', async () => { + const pool = poolAtAddress( + computePoolAddress(factory.address, [tokens[0].address, tokens[1].address], FeeAmount.MEDIUM), + wallet + ) + await expect( + exit({ + nft: nft.connect(other), + tokenId, + liquidity: 100, + amount0Min: 0, + amount1Min: 0, + recipient: wallet.address, + }) + ) + .to.emit(pool, 'Burn') + .to.emit(pool, 'Collect') }) }) - describe('owned by verifying contract', () => { - const tokenId = 1 - let testPositionNFTOwner: TestPositionNFTOwner - - beforeEach('deploy test owner and create a position', async () => { - testPositionNFTOwner = (await ( - await ethers.getContractFactory('TestPositionNFTOwner') - ).deploy()) as TestPositionNFTOwner + describe('#tokenURI', async () => { + const tokenId = 1 + beforeEach('create a position', async () => { await nft.createAndInitializePoolIfNecessary( tokens[0].address, tokens[1].address, @@ -993,7 +857,7 @@ describe('NonfungiblePositionManager', () => { fee: FeeAmount.MEDIUM, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: testPositionNFTOwner.address, + recipient: other.address, amount0Desired: 100, amount1Desired: 100, amount0Min: 0, @@ -1002,303 +866,123 @@ describe('NonfungiblePositionManager', () => { }) }) - it('changes the operator of the position and increments the nonce', async () => { - const { v, r, s } = await getPermitNFTSignature(other, nft, wallet.address, tokenId, 1) - await testPositionNFTOwner.setOwner(other.address) - await nft.permit(wallet.address, tokenId, 1, v, r, s) - expect((await nft.positions(tokenId)).nonce).to.eq(1) - expect((await nft.positions(tokenId)).operator).to.eq(wallet.address) - }) - - it('fails if owner contract is owned by different address', async () => { - const { v, r, s } = await getPermitNFTSignature(other, nft, wallet.address, tokenId, 1) - await testPositionNFTOwner.setOwner(wallet.address) - await expect(nft.permit(wallet.address, tokenId, 1, v, r, s)).to.be.revertedWith('Unauthorized') + it('reverts for invalid token id', async () => { + await expect(nft.tokenURI(tokenId + 1)).to.be.reverted }) - it('fails with signature not from owner', async () => { - const { v, r, s } = await getPermitNFTSignature(wallet, nft, wallet.address, tokenId, 1) - await testPositionNFTOwner.setOwner(other.address) - await expect(nft.permit(wallet.address, tokenId, 1, v, r, s)).to.be.revertedWith('Unauthorized') - }) - - it('fails with expired signature', async () => { - await nft.setTime(2) - const { v, r, s } = await getPermitNFTSignature(other, nft, wallet.address, tokenId, 1) - await testPositionNFTOwner.setOwner(other.address) - await expect(nft.permit(wallet.address, tokenId, 1, v, r, s)).to.be.revertedWith('Permit expired') + it('returns a data URI with correct mime type', async () => { + expect(await nft.tokenURI(tokenId)).to.match(/data:application\/json;base64,.+/) }) - it('gas', async () => { - const { v, r, s } = await getPermitNFTSignature(other, nft, wallet.address, tokenId, 1) - await testPositionNFTOwner.setOwner(other.address) - await snapshotGasCost(nft.permit(wallet.address, tokenId, 1, v, r, s)) + it('content is valid JSON and structure', async () => { + const content = extractJSONFromURI(await nft.tokenURI(tokenId)) + expect(content).to.haveOwnProperty('name').is.a('string') + expect(content).to.haveOwnProperty('description').is.a('string') + expect(content).to.haveOwnProperty('image').is.a('string') }) }) - }) - - describe('multicall exit', () => { - const tokenId = 1 - beforeEach('create a position', async () => { - await nft.createAndInitializePoolIfNecessary( - tokens[0].address, - tokens[1].address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) - await nft.mint({ - token0: tokens[0].address, - token1: tokens[1].address, - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: other.address, - amount0Desired: 100, - amount1Desired: 100, - amount0Min: 0, - amount1Min: 0, - deadline: 1, - }) - }) - - async function exit({ - nft, - liquidity, - tokenId, - amount0Min, - amount1Min, - recipient, - }: { - nft: MockTimeNonfungiblePositionManager - tokenId: BigNumberish - liquidity: BigNumberish - amount0Min: BigNumberish - amount1Min: BigNumberish - recipient: string - }) { - const decreaseLiquidityData = nft.interface.encodeFunctionData('decreaseLiquidity', [ - { tokenId, liquidity, amount0Min, amount1Min, deadline: 1 }, - ]) - const collectData = nft.interface.encodeFunctionData('collect', [ - { - tokenId, - recipient, - amount0Max: MaxUint128, - amount1Max: MaxUint128, - }, - ]) - const burnData = nft.interface.encodeFunctionData('burn', [tokenId]) - - return nft.multicall([decreaseLiquidityData, collectData, burnData]) - } - - it('executes all the actions', async () => { - const pool = poolAtAddress( - computePoolAddress(factory.address, [tokens[0].address, tokens[1].address], FeeAmount.MEDIUM), - wallet - ) - await expect( - exit({ - nft: nft.connect(other), - tokenId, - liquidity: 100, + describe('fees accounting', () => { + beforeEach('create two positions', async () => { + await nft.createAndInitializePoolIfNecessary( + tokens[0].address, + tokens[1].address, + FeeAmount.MEDIUM, + encodePriceSqrt(1, 1) + ) + // nft 1 earns 25% of fees + await nft.mint({ + token0: tokens[0].address, + token1: tokens[1].address, + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(FeeAmount.MEDIUM), + tickUpper: getMaxTick(FeeAmount.MEDIUM), + amount0Desired: 100, + amount1Desired: 100, amount0Min: 0, amount1Min: 0, + deadline: 1, recipient: wallet.address, }) - ) - .to.emit(pool, 'Burn') - .to.emit(pool, 'Collect') - }) + // nft 2 earns 75% of fees + await nft.mint({ + token0: tokens[0].address, + token1: tokens[1].address, + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(FeeAmount.MEDIUM), + tickUpper: getMaxTick(FeeAmount.MEDIUM), - it('gas', async () => { - await snapshotGasCost( - exit({ - nft: nft.connect(other), - tokenId, - liquidity: 100, + amount0Desired: 300, + amount1Desired: 300, amount0Min: 0, amount1Min: 0, - recipient: wallet.address, - }) - ) - }) - }) - - describe('#tokenURI', async () => { - const tokenId = 1 - beforeEach('create a position', async () => { - await nft.createAndInitializePoolIfNecessary( - tokens[0].address, - tokens[1].address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) - - await nft.mint({ - token0: tokens[0].address, - token1: tokens[1].address, - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: other.address, - amount0Desired: 100, - amount1Desired: 100, - amount0Min: 0, - amount1Min: 0, - deadline: 1, - }) - }) - - it('reverts for invalid token id', async () => { - await expect(nft.tokenURI(tokenId + 1)).to.be.reverted - }) - - it('returns a data URI with correct mime type', async () => { - expect(await nft.tokenURI(tokenId)).to.match(/data:application\/json;base64,.+/) - }) - - it('content is valid JSON and structure', async () => { - const content = extractJSONFromURI(await nft.tokenURI(tokenId)) - expect(content).to.haveOwnProperty('name').is.a('string') - expect(content).to.haveOwnProperty('description').is.a('string') - expect(content).to.haveOwnProperty('image').is.a('string') - }) - }) - - describe('fees accounting', () => { - beforeEach('create two positions', async () => { - await nft.createAndInitializePoolIfNecessary( - tokens[0].address, - tokens[1].address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) - // nft 1 earns 25% of fees - await nft.mint({ - token0: tokens[0].address, - token1: tokens[1].address, - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(FeeAmount.MEDIUM), - tickUpper: getMaxTick(FeeAmount.MEDIUM), - amount0Desired: 100, - amount1Desired: 100, - amount0Min: 0, - amount1Min: 0, - deadline: 1, - recipient: wallet.address, - }) - // nft 2 earns 75% of fees - await nft.mint({ - token0: tokens[0].address, - token1: tokens[1].address, - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(FeeAmount.MEDIUM), - tickUpper: getMaxTick(FeeAmount.MEDIUM), - - amount0Desired: 300, - amount1Desired: 300, - amount0Min: 0, - amount1Min: 0, - deadline: 1, - recipient: wallet.address, - }) - }) - - describe('10k of token0 fees collect', () => { - beforeEach('swap for ~10k of fees', async () => { - const swapAmount = 3_333_333 - await tokens[0].approve(router.address, swapAmount) - await router.exactInput({ - recipient: wallet.address, deadline: 1, - path: encodePath([tokens[0].address, tokens[1].address], [FeeAmount.MEDIUM]), - amountIn: swapAmount, - amountOutMinimum: 0, - }) - }) - it('expected amounts', async () => { - const { amount0: nft1Amount0, amount1: nft1Amount1 } = await nft.callStatic.collect({ - tokenId: 1, recipient: wallet.address, - amount0Max: MaxUint128, - amount1Max: MaxUint128, - }) - const { amount0: nft2Amount0, amount1: nft2Amount1 } = await nft.callStatic.collect({ - tokenId: 2, - recipient: wallet.address, - amount0Max: MaxUint128, - amount1Max: MaxUint128, }) - expect(nft1Amount0).to.eq(2501) - expect(nft1Amount1).to.eq(0) - expect(nft2Amount0).to.eq(7503) - expect(nft2Amount1).to.eq(0) }) - it('actually collected', async () => { - const poolAddress = computePoolAddress( - factory.address, - [tokens[0].address, tokens[1].address], - FeeAmount.MEDIUM - ) - - await expect( - nft.collect({ + describe('10k of token0 fees collect', () => { + beforeEach('swap for ~10k of fees', async () => { + const swapAmount = 3_333_333 + await tokens[0].approve(router.address, swapAmount) + await router.exactInput({ + recipient: wallet.address, + deadline: 1, + path: encodePath([tokens[0].address, tokens[1].address], [FeeAmount.MEDIUM]), + amountIn: swapAmount, + amountOutMinimum: 0, + }) + }) + it('expected amounts', async () => { + const {amount0: nft1Amount0, amount1: nft1Amount1} = await nft.callStatic.collect({ tokenId: 1, recipient: wallet.address, amount0Max: MaxUint128, amount1Max: MaxUint128, }) - ) - .to.emit(tokens[0], 'Transfer') - .withArgs(poolAddress, wallet.address, 2501) - .to.not.emit(tokens[1], 'Transfer') - await expect( - nft.collect({ + const {amount0: nft2Amount0, amount1: nft2Amount1} = await nft.callStatic.collect({ tokenId: 2, recipient: wallet.address, amount0Max: MaxUint128, amount1Max: MaxUint128, }) - ) - .to.emit(tokens[0], 'Transfer') - .withArgs(poolAddress, wallet.address, 7503) - .to.not.emit(tokens[1], 'Transfer') - }) - }) - }) - - describe('#positions', async () => { - it('gas', async () => { - const positionsGasTestFactory = await ethers.getContractFactory('NonfungiblePositionManagerPositionsGasTest') - const positionsGasTest = (await positionsGasTestFactory.deploy( - nft.address - )) as NonfungiblePositionManagerPositionsGasTest - - await nft.createAndInitializePoolIfNecessary( - tokens[0].address, - tokens[1].address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) + expect(nft1Amount0).to.eq(2501) + expect(nft1Amount1).to.eq(0) + expect(nft2Amount0).to.eq(7503) + expect(nft2Amount1).to.eq(0) + }) - await nft.mint({ - token0: tokens[0].address, - token1: tokens[1].address, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - fee: FeeAmount.MEDIUM, - recipient: other.address, - amount0Desired: 15, - amount1Desired: 15, - amount0Min: 0, - amount1Min: 0, - deadline: 10, + it('actually collected', async () => { + const poolAddress = computePoolAddress( + factory.address, + [tokens[0].address, tokens[1].address], + FeeAmount.MEDIUM + ) + + await expect( + nft.collect({ + tokenId: 1, + recipient: wallet.address, + amount0Max: MaxUint128, + amount1Max: MaxUint128, + }) + ) + .to.emit(tokens[0], 'Transfer') + .withArgs(poolAddress, wallet.address, 2501) + .to.not.emit(tokens[1], 'Transfer') + await expect( + nft.collect({ + tokenId: 2, + recipient: wallet.address, + amount0Max: MaxUint128, + amount1Max: MaxUint128, + }) + ) + .to.emit(tokens[0], 'Transfer') + .withArgs(poolAddress, wallet.address, 7503) + .to.not.emit(tokens[1], 'Transfer') + }) }) - - await snapshotGasCost(positionsGasTest.getGasCostOfPositions(1)) }) }) }) diff --git a/test/OracleLibrary.spec.ts b/test/OracleLibrary.spec.ts index d15864e6c..a8d317822 100644 --- a/test/OracleLibrary.spec.ts +++ b/test/OracleLibrary.spec.ts @@ -3,7 +3,6 @@ import { ethers, waffle } from 'hardhat' import { BigNumber, BigNumberish, constants, ContractFactory, Contract } from 'ethers' import { OracleTest, TestERC20 } from '../typechain' import { expandTo18Decimals } from './shared/expandTo18Decimals' -import snapshotGasCost from './shared/snapshotGasCost' describe('OracleLibrary', () => { let loadFixture: ReturnType @@ -184,17 +183,6 @@ describe('OracleLibrary', () => { ) expect(quoteAmount).to.equal(BigNumber.from('1')) }) - - it('gas test', async () => { - await snapshotGasCost( - oracle.getGasCostOfGetQuoteAtTick( - BigNumber.from(10), - expandTo18Decimals(1), - tokens[0].address, - tokens[1].address - ) - ) - }) }) describe('#getOldestObservationSecondsAgo', () => { diff --git a/test/PairFlash.spec.ts b/test/PairFlash.spec.ts index b6084ed6b..ccccf4c9c 100644 --- a/test/PairFlash.spec.ts +++ b/test/PairFlash.spec.ts @@ -16,7 +16,6 @@ import { import completeFixture from './shared/completeFixture' import { FeeAmount, MaxUint128, TICK_SPACINGS } from './shared/constants' import { encodePriceSqrt } from './shared/encodePriceSqrt' -import snapshotGasCost from './shared/snapshotGasCost' import { expect } from './shared/expect' import { getMaxTick, getMinTick } from './shared/ticks' @@ -162,7 +161,6 @@ describe('PairFlash test', () => { fee2: FeeAmount.LOW, fee3: FeeAmount.HIGH, } - await snapshotGasCost(flash.initFlash(flashParams)) }) }) }) diff --git a/test/PoolAddress.spec.ts b/test/PoolAddress.spec.ts index 9c834811c..251b4ef9d 100644 --- a/test/PoolAddress.spec.ts +++ b/test/PoolAddress.spec.ts @@ -4,7 +4,6 @@ import { waffle, ethers } from 'hardhat' import { PoolAddressTest } from '../typechain' import { POOL_BYTECODE_HASH } from './shared/computePoolAddress' import { expect } from './shared/expect' -import snapshotGasCost from './shared/snapshotGasCost' describe('PoolAddress', () => { let poolAddress: PoolAddressTest @@ -57,16 +56,5 @@ describe('PoolAddress', () => { ) ).to.be.reverted }) - - it('gas cost', async () => { - await snapshotGasCost( - poolAddress.getGasCostOfComputeAddress( - '0x5FbDB2315678afecb367f032d93F642f64180aa3', - '0x1000000000000000000000000000000000000000', - '0x2000000000000000000000000000000000000000', - 3000 - ) - ) - }) }) }) diff --git a/test/PositionValue.spec.ts b/test/PositionValue.spec.ts index 558ffcd0e..6e068e77d 100644 --- a/test/PositionValue.spec.ts +++ b/test/PositionValue.spec.ts @@ -5,7 +5,6 @@ import { PositionValueTest, SwapRouter, MockTimeNonfungiblePositionManager, - IUniswapV3Pool, TestERC20, IUniswapV3Factory, } from '../typechain' @@ -16,7 +15,6 @@ import { expandTo18Decimals } from './shared/expandTo18Decimals' import { encodePath } from './shared/path' import { computePoolAddress } from './shared/computePoolAddress' import completeFixture from './shared/completeFixture' -import snapshotGasCost from './shared/snapshotGasCost' import { expect } from './shared/expect' @@ -131,10 +129,6 @@ describe('PositionValue', async () => { expect(total[0]).to.equal(principal[0].add(fees[0])) expect(total[1]).to.equal(principal[1].add(fees[1])) }) - - it('gas', async () => { - await snapshotGasCost(positionValue.totalGas(nft.address, 1, sqrtRatioX96)) - }) }) describe('#principal', () => { @@ -259,8 +253,6 @@ describe('PositionValue', async () => { amount1Min: 0, deadline: 10, }) - - await snapshotGasCost(positionValue.principalGas(nft.address, 1, sqrtRatioX96)) }) }) @@ -370,10 +362,6 @@ describe('PositionValue', async () => { expect(feeAmounts[0]).to.equal(feesFromCollect[0]) expect(feeAmounts[1]).to.equal(feesFromCollect[1]) }) - - it('gas', async () => { - await snapshotGasCost(positionValue.feesGas(nft.address, tokenId)) - }) }) describe('when price is below the position range', async () => { @@ -427,9 +415,6 @@ describe('PositionValue', async () => { expect(feeAmounts[1]).to.equal(feesFromCollect[1]) }) - it('gas', async () => { - await snapshotGasCost(positionValue.feesGas(nft.address, tokenId)) - }) }) describe('when price is above the position range', async () => { @@ -482,9 +467,6 @@ describe('PositionValue', async () => { expect(feeAmounts[1]).to.equal(feesFromCollect[1]) }) - it('gas', async () => { - await snapshotGasCost(positionValue.feesGas(nft.address, tokenId)) - }) }) }) }) diff --git a/test/QuoterV2.spec.ts b/test/QuoterV2.spec.ts index d008dbc80..c8731fd23 100644 --- a/test/QuoterV2.spec.ts +++ b/test/QuoterV2.spec.ts @@ -77,7 +77,7 @@ describe('QuoterV2', function () { 10000 ) - await snapshotGasCost(gasEstimate) + expect(sqrtPriceX96AfterList.length).to.eq(1) expect(sqrtPriceX96AfterList[0]).to.eq('78461846509168490764501028180') expect(initializedTicksCrossedList[0]).to.eq(2) @@ -97,7 +97,7 @@ describe('QuoterV2', function () { 6200 ) - await snapshotGasCost(gasEstimate) + expect(sqrtPriceX96AfterList.length).to.eq(1) expect(sqrtPriceX96AfterList[0]).to.eq('78757224507315167622282810783') expect(initializedTicksCrossedList.length).to.eq(1) @@ -116,7 +116,7 @@ describe('QuoterV2', function () { 4000 ) - await snapshotGasCost(gasEstimate) + expect(initializedTicksCrossedList[0]).to.eq(1) expect(sqrtPriceX96AfterList.length).to.eq(1) expect(sqrtPriceX96AfterList[0]).to.eq('78926452400586371254602774705') @@ -135,7 +135,7 @@ describe('QuoterV2', function () { 10 ) - await snapshotGasCost(gasEstimate) + expect(initializedTicksCrossedList[0]).to.eq(0) expect(sqrtPriceX96AfterList.length).to.eq(1) expect(sqrtPriceX96AfterList[0]).to.eq('79227483487511329217250071027') @@ -156,7 +156,7 @@ describe('QuoterV2', function () { 10 ) - await snapshotGasCost(gasEstimate) + expect(initializedTicksCrossedList[0]).to.eq(1) expect(sqrtPriceX96AfterList.length).to.eq(1) expect(sqrtPriceX96AfterList[0]).to.eq('79227817515327498931091950511') @@ -174,7 +174,7 @@ describe('QuoterV2', function () { 10000 ) - await snapshotGasCost(gasEstimate) + expect(initializedTicksCrossedList[0]).to.eq(2) expect(sqrtPriceX96AfterList.length).to.eq(1) expect(sqrtPriceX96AfterList[0]).to.eq('80001962924147897865541384515') @@ -195,7 +195,7 @@ describe('QuoterV2', function () { 6250 ) - await snapshotGasCost(gasEstimate) + expect(initializedTicksCrossedList[0]).to.eq(2) expect(sqrtPriceX96AfterList.length).to.eq(1) expect(sqrtPriceX96AfterList[0]).to.eq('79705728824507063507279123685') @@ -217,7 +217,7 @@ describe('QuoterV2', function () { 200 ) - await snapshotGasCost(gasEstimate) + expect(initializedTicksCrossedList[0]).to.eq(0) expect(sqrtPriceX96AfterList.length).to.eq(1) expect(sqrtPriceX96AfterList[0]).to.eq('79235729830182478001034429156') @@ -237,7 +237,7 @@ describe('QuoterV2', function () { 103 ) - await snapshotGasCost(gasEstimate) + expect(initializedTicksCrossedList[0]).to.eq(0) expect(sqrtPriceX96AfterList.length).to.eq(1) expect(sqrtPriceX96AfterList[0]).to.eq('79235858216754624215638319723') @@ -256,7 +256,7 @@ describe('QuoterV2', function () { 10000 ) - await snapshotGasCost(gasEstimate) + expect(sqrtPriceX96AfterList.length).to.eq(1) expect(sqrtPriceX96AfterList[0]).to.eq('80018067294531553039351583520') expect(initializedTicksCrossedList[0]).to.eq(0) @@ -299,7 +299,7 @@ describe('QuoterV2', function () { sqrtPriceLimitX96: encodePriceSqrt(100, 102), }) - await snapshotGasCost(gasEstimate) + expect(initializedTicksCrossed).to.eq(2) expect(quote).to.eq(9871) expect(sqrtPriceX96After).to.eq('78461846509168490764501028180') @@ -320,7 +320,6 @@ describe('QuoterV2', function () { sqrtPriceLimitX96: encodePriceSqrt(102, 100), }) - await snapshotGasCost(gasEstimate) expect(initializedTicksCrossed).to.eq(2) expect(quote).to.eq(9871) expect(sqrtPriceX96After).to.eq('80001962924147897865541384515') @@ -360,7 +359,6 @@ describe('QuoterV2', function () { 6143 ) - await snapshotGasCost(gasEstimate) expect(sqrtPriceX96AfterList.length).to.eq(1) expect(sqrtPriceX96AfterList[0]).to.eq('78757225449310403327341205211') expect(initializedTicksCrossedList.length).to.eq(1) @@ -379,7 +377,7 @@ describe('QuoterV2', function () { 4000 ) - await snapshotGasCost(gasEstimate) + expect(initializedTicksCrossedList.length).to.eq(1) expect(initializedTicksCrossedList[0]).to.eq(1) expect(amountIn).to.eq(4029) @@ -401,7 +399,6 @@ describe('QuoterV2', function () { 100 ) - await snapshotGasCost(gasEstimate) expect(initializedTicksCrossedList.length).to.eq(1) expect(initializedTicksCrossedList[0]).to.eq(1) expect(amountIn).to.eq(102) @@ -421,7 +418,7 @@ describe('QuoterV2', function () { 10 ) - await snapshotGasCost(gasEstimate) + expect(initializedTicksCrossedList.length).to.eq(1) expect(initializedTicksCrossedList[0]).to.eq(0) expect(amountIn).to.eq(12) @@ -441,7 +438,7 @@ describe('QuoterV2', function () { 15000 ) - await snapshotGasCost(gasEstimate) + expect(initializedTicksCrossedList.length).to.eq(1) expect(initializedTicksCrossedList[0]).to.eq(2) expect(amountIn).to.eq(15273) @@ -462,7 +459,6 @@ describe('QuoterV2', function () { 6223 ) - await snapshotGasCost(gasEstimate) expect(initializedTicksCrossedList[0]).to.eq(2) expect(sqrtPriceX96AfterList.length).to.eq(1) expect(sqrtPriceX96AfterList[0]).to.eq('79708304437530892332449657932') @@ -481,7 +477,7 @@ describe('QuoterV2', function () { 6000 ) - await snapshotGasCost(gasEstimate) + expect(initializedTicksCrossedList[0]).to.eq(1) expect(sqrtPriceX96AfterList.length).to.eq(1) expect(sqrtPriceX96AfterList[0]).to.eq('79690640184021170956740081887') @@ -500,7 +496,6 @@ describe('QuoterV2', function () { 9871 ) - await snapshotGasCost(gasEstimate) expect(sqrtPriceX96AfterList.length).to.eq(1) expect(sqrtPriceX96AfterList[0]).to.eq('80018020393569259756601362385') expect(initializedTicksCrossedList[0]).to.eq(0) @@ -545,7 +540,6 @@ describe('QuoterV2', function () { sqrtPriceLimitX96: encodePriceSqrt(100, 102), }) - await snapshotGasCost(gasEstimate) expect(amountIn).to.eq(9981) expect(initializedTicksCrossed).to.eq(0) expect(sqrtPriceX96After).to.eq('78447570448055484695608110440') @@ -565,7 +559,6 @@ describe('QuoterV2', function () { sqrtPriceLimitX96: encodePriceSqrt(102, 100), }) - await snapshotGasCost(gasEstimate) expect(amountIn).to.eq(9981) expect(initializedTicksCrossed).to.eq(0) expect(sqrtPriceX96After).to.eq('80016521857016594389520272648') diff --git a/test/SwapRouter.gas.spec.ts b/test/SwapRouter.gas.spec.ts deleted file mode 100644 index 1c06b0bfe..000000000 --- a/test/SwapRouter.gas.spec.ts +++ /dev/null @@ -1,453 +0,0 @@ -import { abi as IUniswapV3PoolABI } from '@birthdayresearch/uniswap-v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json' -import { Fixture } from 'ethereum-waffle' -import { BigNumber, constants, ContractTransaction, Wallet } from 'ethers' -import { ethers, waffle } from 'hardhat' -import { IUniswapV3Pool, IWETH9, MockTimeSwapRouter, TestERC20 } from '../typechain' -import completeFixture from './shared/completeFixture' -import { FeeAmount, TICK_SPACINGS } from './shared/constants' -import { encodePriceSqrt } from './shared/encodePriceSqrt' -import { expandTo18Decimals } from './shared/expandTo18Decimals' -import { expect } from './shared/expect' -import { encodePath } from './shared/path' -import snapshotGasCost from './shared/snapshotGasCost' -import { getMaxTick, getMinTick } from './shared/ticks' - -describe('SwapRouter gas tests', function () { - this.timeout(40000) - let wallet: Wallet - let trader: Wallet - - const swapRouterFixture: Fixture<{ - weth9: IWETH9 - router: MockTimeSwapRouter - tokens: [TestERC20, TestERC20, TestERC20] - pools: [IUniswapV3Pool, IUniswapV3Pool, IUniswapV3Pool] - }> = async (wallets, provider) => { - const { weth9, factory, router, tokens, nft } = await completeFixture(wallets, provider) - - // approve & fund wallets - for (const token of tokens) { - await token.approve(router.address, constants.MaxUint256) - await token.approve(nft.address, constants.MaxUint256) - await token.connect(trader).approve(router.address, constants.MaxUint256) - await token.transfer(trader.address, expandTo18Decimals(1_000_000)) - } - - const liquidity = 1000000 - async function createPool(tokenAddressA: string, tokenAddressB: string) { - if (tokenAddressA.toLowerCase() > tokenAddressB.toLowerCase()) - [tokenAddressA, tokenAddressB] = [tokenAddressB, tokenAddressA] - - await nft.createAndInitializePoolIfNecessary( - tokenAddressA, - tokenAddressB, - FeeAmount.MEDIUM, - encodePriceSqrt(100005, 100000) // we don't want to cross any ticks - ) - - const liquidityParams = { - token0: tokenAddressA, - token1: tokenAddressB, - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: wallet.address, - amount0Desired: 1000000, - amount1Desired: 1000000, - amount0Min: 0, - amount1Min: 0, - deadline: 1, - } - - return nft.mint(liquidityParams) - } - - async function createPoolWETH9(tokenAddress: string) { - await weth9.deposit({ value: liquidity * 2 }) - await weth9.approve(nft.address, constants.MaxUint256) - return createPool(weth9.address, tokenAddress) - } - - // create pools - await createPool(tokens[0].address, tokens[1].address) - await createPool(tokens[1].address, tokens[2].address) - await createPoolWETH9(tokens[0].address) - - const poolAddresses = await Promise.all([ - factory.getPool(tokens[0].address, tokens[1].address, FeeAmount.MEDIUM), - factory.getPool(tokens[1].address, tokens[2].address, FeeAmount.MEDIUM), - factory.getPool(weth9.address, tokens[0].address, FeeAmount.MEDIUM), - ]) - - const pools = poolAddresses.map((poolAddress) => new ethers.Contract(poolAddress, IUniswapV3PoolABI, wallet)) as [ - IUniswapV3Pool, - IUniswapV3Pool, - IUniswapV3Pool - ] - - return { - weth9, - router, - tokens, - pools, - } - } - - let weth9: IWETH9 - let router: MockTimeSwapRouter - let tokens: [TestERC20, TestERC20, TestERC20] - let pools: [IUniswapV3Pool, IUniswapV3Pool, IUniswapV3Pool] - - let loadFixture: ReturnType - - before('create fixture loader', async () => { - const wallets = await (ethers as any).getSigners() - ;[wallet, trader] = wallets - - loadFixture = waffle.createFixtureLoader(wallets) - }) - - beforeEach('load fixture', async () => { - ;({ router, weth9, tokens, pools } = await loadFixture(swapRouterFixture)) - }) - - async function exactInput( - tokens: string[], - amountIn: number = 2, - amountOutMinimum: number = 1 - ): Promise { - const inputIsWETH = weth9.address === tokens[0] - const outputIsWETH9 = tokens[tokens.length - 1] === weth9.address - - const value = inputIsWETH ? amountIn : 0 - - const params = { - path: encodePath(tokens, new Array(tokens.length - 1).fill(FeeAmount.MEDIUM)), - recipient: outputIsWETH9 ? constants.AddressZero : trader.address, - deadline: 1, - amountIn, - amountOutMinimum: outputIsWETH9 ? 0 : amountOutMinimum, // save on calldata, - } - - const data = [router.interface.encodeFunctionData('exactInput', [params])] - if (outputIsWETH9) data.push(router.interface.encodeFunctionData('unwrapWETH9', [amountOutMinimum, trader.address])) - - // optimized for the gas test - return data.length === 1 - ? router.connect(trader).exactInput(params, { value }) - : router.connect(trader).multicall(data, { value }) - } - - async function exactInputSingle( - tokenIn: string, - tokenOut: string, - amountIn: number = 3, - amountOutMinimum: number = 1, - sqrtPriceLimitX96?: BigNumber - ): Promise { - const inputIsWETH = weth9.address === tokenIn - const outputIsWETH9 = tokenOut === weth9.address - - const value = inputIsWETH ? amountIn : 0 - - const params = { - tokenIn, - tokenOut, - fee: FeeAmount.MEDIUM, - sqrtPriceLimitX96: sqrtPriceLimitX96 ?? 0, - recipient: outputIsWETH9 ? constants.AddressZero : trader.address, - deadline: 1, - amountIn, - amountOutMinimum: outputIsWETH9 ? 0 : amountOutMinimum, // save on calldata - } - - const data = [router.interface.encodeFunctionData('exactInputSingle', [params])] - if (outputIsWETH9) data.push(router.interface.encodeFunctionData('unwrapWETH9', [amountOutMinimum, trader.address])) - - // optimized for the gas test - return data.length === 1 - ? router.connect(trader).exactInputSingle(params, { value }) - : router.connect(trader).multicall(data, { value }) - } - - async function exactOutput(tokens: string[]): Promise { - const amountInMaximum = 10 // we don't care - const amountOut = 1 - - const inputIsWETH9 = tokens[0] === weth9.address - const outputIsWETH9 = tokens[tokens.length - 1] === weth9.address - - const value = inputIsWETH9 ? amountInMaximum : 0 - - const params = { - path: encodePath(tokens.slice().reverse(), new Array(tokens.length - 1).fill(FeeAmount.MEDIUM)), - recipient: outputIsWETH9 ? constants.AddressZero : trader.address, - deadline: 1, - amountOut, - amountInMaximum, - } - - const data = [router.interface.encodeFunctionData('exactOutput', [params])] - if (inputIsWETH9) data.push(router.interface.encodeFunctionData('refundETH')) - if (outputIsWETH9) data.push(router.interface.encodeFunctionData('unwrapWETH9', [amountOut, trader.address])) - - return router.connect(trader).multicall(data, { value }) - } - - async function exactOutputSingle( - tokenIn: string, - tokenOut: string, - amountOut: number = 1, - amountInMaximum: number = 3, - sqrtPriceLimitX96?: BigNumber - ): Promise { - const inputIsWETH9 = tokenIn === weth9.address - const outputIsWETH9 = tokenOut === weth9.address - - const value = inputIsWETH9 ? amountInMaximum : 0 - - const params = { - tokenIn, - tokenOut, - fee: FeeAmount.MEDIUM, - recipient: outputIsWETH9 ? constants.AddressZero : trader.address, - deadline: 1, - amountOut, - amountInMaximum, - sqrtPriceLimitX96: sqrtPriceLimitX96 ?? 0, - } - - const data = [router.interface.encodeFunctionData('exactOutputSingle', [params])] - if (inputIsWETH9) data.push(router.interface.encodeFunctionData('unwrapWETH9', [0, trader.address])) - if (outputIsWETH9) data.push(router.interface.encodeFunctionData('unwrapWETH9', [amountOut, trader.address])) - - return router.connect(trader).multicall(data, { value }) - } - - // TODO should really throw this in the fixture - beforeEach('intialize feeGrowthGlobals', async () => { - await exactInput([tokens[0].address, tokens[1].address], 1, 0) - await exactInput([tokens[1].address, tokens[0].address], 1, 0) - await exactInput([tokens[1].address, tokens[2].address], 1, 0) - await exactInput([tokens[2].address, tokens[1].address], 1, 0) - await exactInput([tokens[0].address, weth9.address], 1, 0) - await exactInput([weth9.address, tokens[0].address], 1, 0) - }) - - beforeEach('ensure feeGrowthGlobals are >0', async () => { - const slots = await Promise.all( - pools.map((pool) => - Promise.all([ - pool.feeGrowthGlobal0X128().then((f) => f.toString()), - pool.feeGrowthGlobal1X128().then((f) => f.toString()), - ]) - ) - ) - - expect(slots).to.deep.eq([ - ['340290874192793283295456993856614', '340290874192793283295456993856614'], - ['340290874192793283295456993856614', '340290874192793283295456993856614'], - ['340290874192793283295456993856614', '340290874192793283295456993856614'], - ]) - }) - - beforeEach('ensure ticks are 0 before', async () => { - const slots = await Promise.all(pools.map((pool) => pool.slot0().then(({ tick }) => tick))) - expect(slots).to.deep.eq([0, 0, 0]) - }) - - afterEach('ensure ticks are 0 after', async () => { - const slots = await Promise.all(pools.map((pool) => pool.slot0().then(({ tick }) => tick))) - expect(slots).to.deep.eq([0, 0, 0]) - }) - - describe('#exactInput', () => { - it('0 -> 1', async () => { - await snapshotGasCost(exactInput(tokens.slice(0, 2).map((token) => token.address))) - }) - - it('0 -> 1 minimal', async () => { - const calleeFactory = await ethers.getContractFactory('TestUniswapV3Callee') - const callee = await calleeFactory.deploy() - - await tokens[0].connect(trader).approve(callee.address, constants.MaxUint256) - await snapshotGasCost(callee.connect(trader).swapExact0For1(pools[0].address, 2, trader.address, '4295128740')) - }) - - it('0 -> 1 -> 2', async () => { - await snapshotGasCost( - exactInput( - tokens.map((token) => token.address), - 3 - ) - ) - }) - - it('WETH9 -> 0', async () => { - await snapshotGasCost( - exactInput( - [weth9.address, tokens[0].address], - weth9.address.toLowerCase() < tokens[0].address.toLowerCase() ? 2 : 3 - ) - ) - }) - - it('0 -> WETH9', async () => { - await snapshotGasCost( - exactInput( - [tokens[0].address, weth9.address], - tokens[0].address.toLowerCase() < weth9.address.toLowerCase() ? 2 : 3 - ) - ) - }) - - it('2 trades (via router)', async () => { - await weth9.connect(trader).deposit({ value: 3 }) - await weth9.connect(trader).approve(router.address, constants.MaxUint256) - const swap0 = { - path: encodePath([weth9.address, tokens[0].address], [FeeAmount.MEDIUM]), - recipient: constants.AddressZero, - deadline: 1, - amountIn: 3, - amountOutMinimum: 0, // save on calldata - } - - const swap1 = { - path: encodePath([tokens[1].address, tokens[0].address], [FeeAmount.MEDIUM]), - recipient: constants.AddressZero, - deadline: 1, - amountIn: 3, - amountOutMinimum: 0, // save on calldata - } - - const data = [ - router.interface.encodeFunctionData('exactInput', [swap0]), - router.interface.encodeFunctionData('exactInput', [swap1]), - router.interface.encodeFunctionData('sweepToken', [tokens[0].address, 2, trader.address]), - ] - - await snapshotGasCost(router.connect(trader).multicall(data)) - }) - - it('3 trades (directly to sender)', async () => { - await weth9.connect(trader).deposit({ value: 3 }) - await weth9.connect(trader).approve(router.address, constants.MaxUint256) - const swap0 = { - path: encodePath([weth9.address, tokens[0].address], [FeeAmount.MEDIUM]), - recipient: trader.address, - deadline: 1, - amountIn: 3, - amountOutMinimum: 1, - } - - const swap1 = { - path: encodePath([tokens[0].address, tokens[1].address], [FeeAmount.MEDIUM]), - recipient: trader.address, - deadline: 1, - amountIn: 3, - amountOutMinimum: 1, - } - - const swap2 = { - path: encodePath([tokens[1].address, tokens[2].address], [FeeAmount.MEDIUM]), - recipient: trader.address, - deadline: 1, - amountIn: 3, - amountOutMinimum: 1, - } - - const data = [ - router.interface.encodeFunctionData('exactInput', [swap0]), - router.interface.encodeFunctionData('exactInput', [swap1]), - router.interface.encodeFunctionData('exactInput', [swap2]), - ] - - await snapshotGasCost(router.connect(trader).multicall(data)) - }) - }) - - it('3 trades (directly to sender)', async () => { - await weth9.connect(trader).deposit({ value: 3 }) - await weth9.connect(trader).approve(router.address, constants.MaxUint256) - const swap0 = { - path: encodePath([weth9.address, tokens[0].address], [FeeAmount.MEDIUM]), - recipient: trader.address, - deadline: 1, - amountIn: 3, - amountOutMinimum: 1, - } - - const swap1 = { - path: encodePath([tokens[1].address, tokens[0].address], [FeeAmount.MEDIUM]), - recipient: trader.address, - deadline: 1, - amountIn: 3, - amountOutMinimum: 1, - } - - const data = [ - router.interface.encodeFunctionData('exactInput', [swap0]), - router.interface.encodeFunctionData('exactInput', [swap1]), - ] - - await snapshotGasCost(router.connect(trader).multicall(data)) - }) - - describe('#exactInputSingle', () => { - it('0 -> 1', async () => { - await snapshotGasCost(exactInputSingle(tokens[0].address, tokens[1].address)) - }) - - it('WETH9 -> 0', async () => { - await snapshotGasCost( - exactInputSingle( - weth9.address, - tokens[0].address, - weth9.address.toLowerCase() < tokens[0].address.toLowerCase() ? 2 : 3 - ) - ) - }) - - it('0 -> WETH9', async () => { - await snapshotGasCost( - exactInputSingle( - tokens[0].address, - weth9.address, - tokens[0].address.toLowerCase() < weth9.address.toLowerCase() ? 2 : 3 - ) - ) - }) - }) - - describe('#exactOutput', () => { - it('0 -> 1', async () => { - await snapshotGasCost(exactOutput(tokens.slice(0, 2).map((token) => token.address))) - }) - - it('0 -> 1 -> 2', async () => { - await snapshotGasCost(exactOutput(tokens.map((token) => token.address))) - }) - - it('WETH9 -> 0', async () => { - await snapshotGasCost(exactOutput([weth9.address, tokens[0].address])) - }) - - it('0 -> WETH9', async () => { - await snapshotGasCost(exactOutput([tokens[0].address, weth9.address])) - }) - }) - - describe('#exactOutputSingle', () => { - it('0 -> 1', async () => { - await snapshotGasCost(exactOutputSingle(tokens[0].address, tokens[1].address)) - }) - - it('WETH9 -> 0', async () => { - await snapshotGasCost(exactOutputSingle(weth9.address, tokens[0].address)) - }) - - it('0 -> WETH9', async () => { - await snapshotGasCost(exactOutputSingle(tokens[0].address, weth9.address)) - }) - }) -}) diff --git a/test/SwapRouter.spec.ts b/test/SwapRouter.spec.ts index a3d8f6d72..dc1b6c0d4 100644 --- a/test/SwapRouter.spec.ts +++ b/test/SwapRouter.spec.ts @@ -91,9 +91,6 @@ describe('SwapRouter', function () { expect(balance.eq(0)).to.be.eq(true) }) - it('bytecode size', async () => { - expect(((await router.provider.getCode(router.address)).length - 2) / 2).to.matchSnapshot() - }) describe('swaps', () => { const liquidity = 1000000 diff --git a/test/TickLens.spec.ts b/test/TickLens.spec.ts index 4a927d708..3bc0c55bc 100644 --- a/test/TickLens.spec.ts +++ b/test/TickLens.spec.ts @@ -191,15 +191,6 @@ describe('TickLens', () => { expect(max.liquidityGross).to.be.eq(fullRangeLiquidity) }) - it('gas for single populated tick', async () => { - await snapshotGasCost( - tickLens.getGasCostOfGetPopulatedTicksInWord( - poolAddress, - getTickBitmapIndex(getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), TICK_SPACINGS[FeeAmount.MEDIUM]) - ) - ) - }) - it('fully populated ticks', async () => { // fully populate a word for (let i = 0; i < 128; i++) { @@ -212,12 +203,6 @@ describe('TickLens', () => { ) expect(ticks.length).to.be.eq(256) - await snapshotGasCost( - tickLens.getGasCostOfGetPopulatedTicksInWord( - poolAddress, - getTickBitmapIndex(0, TICK_SPACINGS[FeeAmount.MEDIUM]) - ) - ) }).timeout(300_000) }) }) diff --git a/test/V3Migrator.spec.ts b/test/V3Migrator.spec.ts index f113b2793..9c5e57a3c 100644 --- a/test/V3Migrator.spec.ts +++ b/test/V3Migrator.spec.ts @@ -413,34 +413,5 @@ describe('V3Migrator', () => { expect(await weth9.balanceOf(poolAddress)).to.be.eq(8999) } }) - - it('gas', async () => { - const [token0, token1] = sortedTokens(weth9, token) - await migrator.createAndInitializePoolIfNecessary( - token0.address, - token1.address, - FeeAmount.MEDIUM, - encodePriceSqrt(1, 1) - ) - - await pair.approve(migrator.address, expectedLiquidity) - await snapshotGasCost( - migrator.migrate({ - pair: pair.address, - liquidityToMigrate: expectedLiquidity, - percentageToMigrate: 100, - token0: tokenLower ? token.address : weth9.address, - token1: tokenLower ? weth9.address : token.address, - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(FeeAmount.MEDIUM), - tickUpper: getMaxTick(FeeAmount.MEDIUM), - amount0Min: 9000, - amount1Min: 9000, - recipient: wallet.address, - deadline: 1, - refundAsETH: false, - }) - ) - }) }) })