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

448 lines
16 KiB
TypeScript

import { Fixture } from 'ethereum-waffle'
import { constants, Contract, Wallet } from 'ethers'
import { ethers, waffle } from 'hardhat'
import {
IUniswapV2Pair,
IUniswapV3Factory,
IWETH9,
MockTimeNonfungiblePositionManager,
TestERC20,
V3Migrator,
} from '../typechain'
import completeFixture from './shared/completeFixture'
import { v2FactoryFixture } from './shared/externalFixtures'
import { abi as PAIR_V2_ABI } from '@uniswap/v2-core/build/UniswapV2Pair.json'
import { expect } from 'chai'
import { FeeAmount } from './shared/constants'
import { encodePriceSqrt } from './shared/encodePriceSqrt'
import snapshotGasCost from './shared/snapshotGasCost'
import { sortedTokens } from './shared/tokenSort'
import { getMaxTick, getMinTick } from './shared/ticks'
describe('V3Migrator', () => {
let wallet: Wallet
const migratorFixture: Fixture<{
factoryV2: Contract
factoryV3: IUniswapV3Factory
token: TestERC20
weth9: IWETH9
nft: MockTimeNonfungiblePositionManager
migrator: V3Migrator
}> = async (wallets, provider) => {
const { factory, tokens, nft, weth9 } = await completeFixture(wallets, provider)
const { factory: factoryV2 } = await v2FactoryFixture(wallets, provider)
const token = tokens[0]
await token.approve(factoryV2.address, constants.MaxUint256)
await weth9.deposit({ value: 10000 })
await weth9.approve(nft.address, constants.MaxUint256)
// deploy the migrator
const migrator = (await (await ethers.getContractFactory('V3Migrator')).deploy(
factory.address,
weth9.address,
nft.address
)) as V3Migrator
return {
factoryV2,
factoryV3: factory,
token,
weth9,
nft,
migrator,
}
}
let factoryV2: Contract
let factoryV3: IUniswapV3Factory
let token: TestERC20
let weth9: IWETH9
let nft: MockTimeNonfungiblePositionManager
let migrator: V3Migrator
let pair: IUniswapV2Pair
let loadFixture: ReturnType<typeof waffle.createFixtureLoader>
before('create fixture loader', async () => {
const wallets = await (ethers as any).getSigners()
wallet = wallets[0]
loadFixture = waffle.createFixtureLoader(wallets)
})
beforeEach('load fixture', async () => {
;({ factoryV2, factoryV3, token, weth9, nft, migrator } = await loadFixture(migratorFixture))
})
afterEach('ensure allowances are cleared', async () => {
const allowanceToken = await token.allowance(migrator.address, nft.address)
const allowanceWETH9 = await weth9.allowance(migrator.address, nft.address)
expect(allowanceToken).to.be.eq(0)
expect(allowanceWETH9).to.be.eq(0)
})
afterEach('ensure balances are cleared', async () => {
const balanceToken = await token.balanceOf(migrator.address)
const balanceWETH9 = await weth9.balanceOf(migrator.address)
expect(balanceToken).to.be.eq(0)
expect(balanceWETH9).to.be.eq(0)
})
afterEach('ensure eth balance is cleared', async () => {
const balanceETH = await ethers.provider.getBalance(migrator.address)
expect(balanceETH).to.be.eq(0)
})
describe('#migrate', () => {
let tokenLower: boolean
const expectedLiquidity = 10000 - 1000
beforeEach(() => {
tokenLower = token.address.toLowerCase() < weth9.address.toLowerCase()
})
beforeEach('add V2 liquidity', async () => {
await factoryV2.createPair(token.address, weth9.address)
const pairAddress = await factoryV2.getPair(token.address, weth9.address)
pair = new ethers.Contract(pairAddress, PAIR_V2_ABI, wallet) as IUniswapV2Pair
await token.transfer(pair.address, 10000)
await weth9.transfer(pair.address, 10000)
await pair.mint(wallet.address)
expect(await pair.balanceOf(wallet.address)).to.be.eq(expectedLiquidity)
})
it('fails if v3 pool is not initialized', async () => {
await pair.approve(migrator.address, expectedLiquidity)
await expect(
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: -1,
tickUpper: 1,
amount0Min: 9000,
amount1Min: 9000,
recipient: wallet.address,
deadline: 1,
refundAsETH: false,
})
).to.be.reverted
})
it('works once v3 pool is initialized', 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 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,
})
const position = await nft.positions(1)
expect(position.liquidity).to.be.eq(9000)
const poolAddress = await factoryV3.getPool(token.address, weth9.address, FeeAmount.MEDIUM)
expect(await token.balanceOf(poolAddress)).to.be.eq(9000)
expect(await weth9.balanceOf(poolAddress)).to.be.eq(9000)
})
it('works for partial', async () => {
const [token0, token1] = sortedTokens(weth9, token)
await migrator.createAndInitializePoolIfNecessary(
token0.address,
token1.address,
FeeAmount.MEDIUM,
encodePriceSqrt(1, 1)
)
const tokenBalanceBefore = await token.balanceOf(wallet.address)
const weth9BalanceBefore = await weth9.balanceOf(wallet.address)
await pair.approve(migrator.address, expectedLiquidity)
await migrator.migrate({
pair: pair.address,
liquidityToMigrate: expectedLiquidity,
percentageToMigrate: 50,
token0: tokenLower ? token.address : weth9.address,
token1: tokenLower ? weth9.address : token.address,
fee: FeeAmount.MEDIUM,
tickLower: getMinTick(FeeAmount.MEDIUM),
tickUpper: getMaxTick(FeeAmount.MEDIUM),
amount0Min: 4500,
amount1Min: 4500,
recipient: wallet.address,
deadline: 1,
refundAsETH: false,
})
const tokenBalanceAfter = await token.balanceOf(wallet.address)
const weth9BalanceAfter = await weth9.balanceOf(wallet.address)
expect(tokenBalanceAfter.sub(tokenBalanceBefore)).to.be.eq(4500)
expect(weth9BalanceAfter.sub(weth9BalanceBefore)).to.be.eq(4500)
const position = await nft.positions(1)
expect(position.liquidity).to.be.eq(4500)
const poolAddress = await factoryV3.getPool(token.address, weth9.address, FeeAmount.MEDIUM)
expect(await token.balanceOf(poolAddress)).to.be.eq(4500)
expect(await weth9.balanceOf(poolAddress)).to.be.eq(4500)
})
it('double the price', async () => {
const [token0, token1] = sortedTokens(weth9, token)
await migrator.createAndInitializePoolIfNecessary(
token0.address,
token1.address,
FeeAmount.MEDIUM,
encodePriceSqrt(2, 1)
)
const tokenBalanceBefore = await token.balanceOf(wallet.address)
const weth9BalanceBefore = await weth9.balanceOf(wallet.address)
await pair.approve(migrator.address, expectedLiquidity)
await 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: 4500,
amount1Min: 8999,
recipient: wallet.address,
deadline: 1,
refundAsETH: false,
})
const tokenBalanceAfter = await token.balanceOf(wallet.address)
const weth9BalanceAfter = await weth9.balanceOf(wallet.address)
const position = await nft.positions(1)
expect(position.liquidity).to.be.eq(6363)
const poolAddress = await factoryV3.getPool(token.address, weth9.address, FeeAmount.MEDIUM)
if (token.address.toLowerCase() < weth9.address.toLowerCase()) {
expect(await token.balanceOf(poolAddress)).to.be.eq(4500)
expect(tokenBalanceAfter.sub(tokenBalanceBefore)).to.be.eq(4500)
expect(await weth9.balanceOf(poolAddress)).to.be.eq(8999)
expect(weth9BalanceAfter.sub(weth9BalanceBefore)).to.be.eq(1)
} else {
expect(await token.balanceOf(poolAddress)).to.be.eq(8999)
expect(tokenBalanceAfter.sub(tokenBalanceBefore)).to.be.eq(1)
expect(await weth9.balanceOf(poolAddress)).to.be.eq(4500)
expect(weth9BalanceAfter.sub(weth9BalanceBefore)).to.be.eq(4500)
}
})
it('half the price', async () => {
const [token0, token1] = sortedTokens(weth9, token)
await migrator.createAndInitializePoolIfNecessary(
token0.address,
token1.address,
FeeAmount.MEDIUM,
encodePriceSqrt(1, 2)
)
const tokenBalanceBefore = await token.balanceOf(wallet.address)
const weth9BalanceBefore = await weth9.balanceOf(wallet.address)
await pair.approve(migrator.address, expectedLiquidity)
await 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: 8999,
amount1Min: 4500,
recipient: wallet.address,
deadline: 1,
refundAsETH: false,
})
const tokenBalanceAfter = await token.balanceOf(wallet.address)
const weth9BalanceAfter = await weth9.balanceOf(wallet.address)
const position = await nft.positions(1)
expect(position.liquidity).to.be.eq(6363)
const poolAddress = await factoryV3.getPool(token.address, weth9.address, FeeAmount.MEDIUM)
if (token.address.toLowerCase() < weth9.address.toLowerCase()) {
expect(await token.balanceOf(poolAddress)).to.be.eq(8999)
expect(tokenBalanceAfter.sub(tokenBalanceBefore)).to.be.eq(1)
expect(await weth9.balanceOf(poolAddress)).to.be.eq(4500)
expect(weth9BalanceAfter.sub(weth9BalanceBefore)).to.be.eq(4500)
} else {
expect(await token.balanceOf(poolAddress)).to.be.eq(4500)
expect(tokenBalanceAfter.sub(tokenBalanceBefore)).to.be.eq(4500)
expect(await weth9.balanceOf(poolAddress)).to.be.eq(8999)
expect(weth9BalanceAfter.sub(weth9BalanceBefore)).to.be.eq(1)
}
})
it('double the price - as ETH', async () => {
const [token0, token1] = sortedTokens(weth9, token)
await migrator.createAndInitializePoolIfNecessary(
token0.address,
token1.address,
FeeAmount.MEDIUM,
encodePriceSqrt(2, 1)
)
const tokenBalanceBefore = await token.balanceOf(wallet.address)
await pair.approve(migrator.address, expectedLiquidity)
await expect(
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: 4500,
amount1Min: 8999,
recipient: wallet.address,
deadline: 1,
refundAsETH: true,
})
)
.to.emit(weth9, 'Withdrawal')
.withArgs(migrator.address, tokenLower ? 1 : 4500)
const tokenBalanceAfter = await token.balanceOf(wallet.address)
const position = await nft.positions(1)
expect(position.liquidity).to.be.eq(6363)
const poolAddress = await factoryV3.getPool(token.address, weth9.address, FeeAmount.MEDIUM)
if (tokenLower) {
expect(await token.balanceOf(poolAddress)).to.be.eq(4500)
expect(tokenBalanceAfter.sub(tokenBalanceBefore)).to.be.eq(4500)
expect(await weth9.balanceOf(poolAddress)).to.be.eq(8999)
} else {
expect(await token.balanceOf(poolAddress)).to.be.eq(8999)
expect(tokenBalanceAfter.sub(tokenBalanceBefore)).to.be.eq(1)
expect(await weth9.balanceOf(poolAddress)).to.be.eq(4500)
}
})
it('half the price - as ETH', async () => {
const [token0, token1] = sortedTokens(weth9, token)
await migrator.createAndInitializePoolIfNecessary(
token0.address,
token1.address,
FeeAmount.MEDIUM,
encodePriceSqrt(1, 2)
)
const tokenBalanceBefore = await token.balanceOf(wallet.address)
await pair.approve(migrator.address, expectedLiquidity)
await expect(
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: 8999,
amount1Min: 4500,
recipient: wallet.address,
deadline: 1,
refundAsETH: true,
})
)
.to.emit(weth9, 'Withdrawal')
.withArgs(migrator.address, tokenLower ? 4500 : 1)
const tokenBalanceAfter = await token.balanceOf(wallet.address)
const position = await nft.positions(1)
expect(position.liquidity).to.be.eq(6363)
const poolAddress = await factoryV3.getPool(token.address, weth9.address, FeeAmount.MEDIUM)
if (tokenLower) {
expect(await token.balanceOf(poolAddress)).to.be.eq(8999)
expect(tokenBalanceAfter.sub(tokenBalanceBefore)).to.be.eq(1)
expect(await weth9.balanceOf(poolAddress)).to.be.eq(4500)
} else {
expect(await token.balanceOf(poolAddress)).to.be.eq(4500)
expect(tokenBalanceAfter.sub(tokenBalanceBefore)).to.be.eq(4500)
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,
})
)
})
})
})