From c48480bbe1511eac200abd7e1197357433bc89e8 Mon Sep 17 00:00:00 2001 From: chefburger Date: Wed, 10 Jul 2024 17:50:07 +0800 Subject: [PATCH] test: add tests cases for binPool migrator --- ...V2Test#testMigrateFromV2IncludingInit.snap | 1 + ...apV2Test#testMigrateFromV2WithoutInit.snap | 1 + ...t#testMigrateFromV2WithoutNativeToken.snap | 1 + ...V3Test#testMigrateFromV3IncludingInit.snap | 1 + ...apV3Test#testMigrateFromV3WithoutInit.snap | 1 + ...t#testMigrateFromV3WithoutNativeToken.snap | 1 + ...V2Test#testMigrateFromV2IncludingInit.snap | 1 + ...apV2Test#testMigrateFromV2WithoutInit.snap | 1 + ...t#testMigrateFromV2WithoutNativeToken.snap | 1 + ...V3Test#testMigrateFromV3IncludingInit.snap | 1 + ...apV3Test#testMigrateFromV3WithoutInit.snap | 1 + ...t#testMigrateFromV3WithoutNativeToken.snap | 1 + src/pool-bin/interfaces/IBinMigrator.sol | 4 +- .../BinMigratorFromPancakeswapV2.t.sol | 18 + .../BinMigratorFromPancakeswapV3.t.sol | 26 + .../migrator/BinMigratorFromUniswapV2.t.sol | 18 + .../migrator/BinMigratorFromUniswapV3.t.sol | 25 + test/pool-bin/migrator/BinMigratorFromV2.sol | 759 +++++++++++++++ test/pool-bin/migrator/BinMigratorFromV3.sol | 894 ++++++++++++++++++ test/pool-cl/migrator/CLMigratorFromV3.sol | 1 - 20 files changed, 1754 insertions(+), 3 deletions(-) create mode 100644 .forge-snapshots/BinMigratorFromPancakeswapV2Test#testMigrateFromV2IncludingInit.snap create mode 100644 .forge-snapshots/BinMigratorFromPancakeswapV2Test#testMigrateFromV2WithoutInit.snap create mode 100644 .forge-snapshots/BinMigratorFromPancakeswapV2Test#testMigrateFromV2WithoutNativeToken.snap create mode 100644 .forge-snapshots/BinMigratorFromPancakeswapV3Test#testMigrateFromV3IncludingInit.snap create mode 100644 .forge-snapshots/BinMigratorFromPancakeswapV3Test#testMigrateFromV3WithoutInit.snap create mode 100644 .forge-snapshots/BinMigratorFromPancakeswapV3Test#testMigrateFromV3WithoutNativeToken.snap create mode 100644 .forge-snapshots/BinMigratorFromUniswapV2Test#testMigrateFromV2IncludingInit.snap create mode 100644 .forge-snapshots/BinMigratorFromUniswapV2Test#testMigrateFromV2WithoutInit.snap create mode 100644 .forge-snapshots/BinMigratorFromUniswapV2Test#testMigrateFromV2WithoutNativeToken.snap create mode 100644 .forge-snapshots/BinMigratorFromUniswapV3Test#testMigrateFromV3IncludingInit.snap create mode 100644 .forge-snapshots/BinMigratorFromUniswapV3Test#testMigrateFromV3WithoutInit.snap create mode 100644 .forge-snapshots/BinMigratorFromUniswapV3Test#testMigrateFromV3WithoutNativeToken.snap create mode 100644 test/pool-bin/migrator/BinMigratorFromPancakeswapV2.t.sol create mode 100644 test/pool-bin/migrator/BinMigratorFromPancakeswapV3.t.sol create mode 100644 test/pool-bin/migrator/BinMigratorFromUniswapV2.t.sol create mode 100644 test/pool-bin/migrator/BinMigratorFromUniswapV3.t.sol create mode 100644 test/pool-bin/migrator/BinMigratorFromV2.sol create mode 100644 test/pool-bin/migrator/BinMigratorFromV3.sol diff --git a/.forge-snapshots/BinMigratorFromPancakeswapV2Test#testMigrateFromV2IncludingInit.snap b/.forge-snapshots/BinMigratorFromPancakeswapV2Test#testMigrateFromV2IncludingInit.snap new file mode 100644 index 0000000..9d6ff8f --- /dev/null +++ b/.forge-snapshots/BinMigratorFromPancakeswapV2Test#testMigrateFromV2IncludingInit.snap @@ -0,0 +1 @@ +1016136 \ No newline at end of file diff --git a/.forge-snapshots/BinMigratorFromPancakeswapV2Test#testMigrateFromV2WithoutInit.snap b/.forge-snapshots/BinMigratorFromPancakeswapV2Test#testMigrateFromV2WithoutInit.snap new file mode 100644 index 0000000..18747f3 --- /dev/null +++ b/.forge-snapshots/BinMigratorFromPancakeswapV2Test#testMigrateFromV2WithoutInit.snap @@ -0,0 +1 @@ +976173 \ No newline at end of file diff --git a/.forge-snapshots/BinMigratorFromPancakeswapV2Test#testMigrateFromV2WithoutNativeToken.snap b/.forge-snapshots/BinMigratorFromPancakeswapV2Test#testMigrateFromV2WithoutNativeToken.snap new file mode 100644 index 0000000..c596add --- /dev/null +++ b/.forge-snapshots/BinMigratorFromPancakeswapV2Test#testMigrateFromV2WithoutNativeToken.snap @@ -0,0 +1 @@ +1020620 \ No newline at end of file diff --git a/.forge-snapshots/BinMigratorFromPancakeswapV3Test#testMigrateFromV3IncludingInit.snap b/.forge-snapshots/BinMigratorFromPancakeswapV3Test#testMigrateFromV3IncludingInit.snap new file mode 100644 index 0000000..72c48c3 --- /dev/null +++ b/.forge-snapshots/BinMigratorFromPancakeswapV3Test#testMigrateFromV3IncludingInit.snap @@ -0,0 +1 @@ +1095907 \ No newline at end of file diff --git a/.forge-snapshots/BinMigratorFromPancakeswapV3Test#testMigrateFromV3WithoutInit.snap b/.forge-snapshots/BinMigratorFromPancakeswapV3Test#testMigrateFromV3WithoutInit.snap new file mode 100644 index 0000000..5f720a1 --- /dev/null +++ b/.forge-snapshots/BinMigratorFromPancakeswapV3Test#testMigrateFromV3WithoutInit.snap @@ -0,0 +1 @@ +1056008 \ No newline at end of file diff --git a/.forge-snapshots/BinMigratorFromPancakeswapV3Test#testMigrateFromV3WithoutNativeToken.snap b/.forge-snapshots/BinMigratorFromPancakeswapV3Test#testMigrateFromV3WithoutNativeToken.snap new file mode 100644 index 0000000..89a2f9b --- /dev/null +++ b/.forge-snapshots/BinMigratorFromPancakeswapV3Test#testMigrateFromV3WithoutNativeToken.snap @@ -0,0 +1 @@ +1093854 \ No newline at end of file diff --git a/.forge-snapshots/BinMigratorFromUniswapV2Test#testMigrateFromV2IncludingInit.snap b/.forge-snapshots/BinMigratorFromUniswapV2Test#testMigrateFromV2IncludingInit.snap new file mode 100644 index 0000000..aff190e --- /dev/null +++ b/.forge-snapshots/BinMigratorFromUniswapV2Test#testMigrateFromV2IncludingInit.snap @@ -0,0 +1 @@ +1016148 \ No newline at end of file diff --git a/.forge-snapshots/BinMigratorFromUniswapV2Test#testMigrateFromV2WithoutInit.snap b/.forge-snapshots/BinMigratorFromUniswapV2Test#testMigrateFromV2WithoutInit.snap new file mode 100644 index 0000000..448a84c --- /dev/null +++ b/.forge-snapshots/BinMigratorFromUniswapV2Test#testMigrateFromV2WithoutInit.snap @@ -0,0 +1 @@ +976185 \ No newline at end of file diff --git a/.forge-snapshots/BinMigratorFromUniswapV2Test#testMigrateFromV2WithoutNativeToken.snap b/.forge-snapshots/BinMigratorFromUniswapV2Test#testMigrateFromV2WithoutNativeToken.snap new file mode 100644 index 0000000..f012e83 --- /dev/null +++ b/.forge-snapshots/BinMigratorFromUniswapV2Test#testMigrateFromV2WithoutNativeToken.snap @@ -0,0 +1 @@ +1020617 \ No newline at end of file diff --git a/.forge-snapshots/BinMigratorFromUniswapV3Test#testMigrateFromV3IncludingInit.snap b/.forge-snapshots/BinMigratorFromUniswapV3Test#testMigrateFromV3IncludingInit.snap new file mode 100644 index 0000000..9d538b9 --- /dev/null +++ b/.forge-snapshots/BinMigratorFromUniswapV3Test#testMigrateFromV3IncludingInit.snap @@ -0,0 +1 @@ +1093889 \ No newline at end of file diff --git a/.forge-snapshots/BinMigratorFromUniswapV3Test#testMigrateFromV3WithoutInit.snap b/.forge-snapshots/BinMigratorFromUniswapV3Test#testMigrateFromV3WithoutInit.snap new file mode 100644 index 0000000..a46c9e5 --- /dev/null +++ b/.forge-snapshots/BinMigratorFromUniswapV3Test#testMigrateFromV3WithoutInit.snap @@ -0,0 +1 @@ +1053990 \ No newline at end of file diff --git a/.forge-snapshots/BinMigratorFromUniswapV3Test#testMigrateFromV3WithoutNativeToken.snap b/.forge-snapshots/BinMigratorFromUniswapV3Test#testMigrateFromV3WithoutNativeToken.snap new file mode 100644 index 0000000..2c6961f --- /dev/null +++ b/.forge-snapshots/BinMigratorFromUniswapV3Test#testMigrateFromV3WithoutNativeToken.snap @@ -0,0 +1 @@ +1091832 \ No newline at end of file diff --git a/src/pool-bin/interfaces/IBinMigrator.sol b/src/pool-bin/interfaces/IBinMigrator.sol index 72a4583..03e371a 100644 --- a/src/pool-bin/interfaces/IBinMigrator.sol +++ b/src/pool-bin/interfaces/IBinMigrator.sol @@ -26,7 +26,7 @@ interface IBinMigrator is IBaseMigrator { function migrateFromV2( V2PoolParams calldata v2PoolParams, - V4BinPoolParams calldata v4MintParams, + V4BinPoolParams calldata v4BinPoolParams, // extra funds to be added uint256 extraAmount0, uint256 extraAmount1 @@ -34,7 +34,7 @@ interface IBinMigrator is IBaseMigrator { function migrateFromV3( V3PoolParams calldata v3PoolParams, - V4BinPoolParams calldata v4MintParams, + V4BinPoolParams calldata v4BinPoolParams, // extra funds to be added uint256 extraAmount0, uint256 extraAmount1 diff --git a/test/pool-bin/migrator/BinMigratorFromPancakeswapV2.t.sol b/test/pool-bin/migrator/BinMigratorFromPancakeswapV2.t.sol new file mode 100644 index 0000000..1469eb9 --- /dev/null +++ b/test/pool-bin/migrator/BinMigratorFromPancakeswapV2.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import {BinMigratorFromV2} from "./BinMigratorFromV2.sol"; + +contract BinMigratorFromPancakeswapV2Test is BinMigratorFromV2 { + function _getBytecodePath() internal pure override returns (string memory) { + // Create a Pancakeswap V2 pair + // relative to the root of the project + // https://etherscan.io/address/0x1097053Fd2ea711dad45caCcc45EfF7548fCB362#code + return "./test/bin/pcsV2Factory.bytecode"; + } + + function _getContractName() internal pure override returns (string memory) { + return "BinMigratorFromPancakeswapV2Test"; + } +} diff --git a/test/pool-bin/migrator/BinMigratorFromPancakeswapV3.t.sol b/test/pool-bin/migrator/BinMigratorFromPancakeswapV3.t.sol new file mode 100644 index 0000000..b4a6f9a --- /dev/null +++ b/test/pool-bin/migrator/BinMigratorFromPancakeswapV3.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import {BinMigratorFromV3} from "./BinMigratorFromV3.sol"; + +contract BinMigratorFromPancakeswapV3Test is BinMigratorFromV3 { + function _getDeployerBytecodePath() internal pure override returns (string memory) { + // https://etherscan.io/address/0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9#code + return "./test/bin/pcsV3Deployer.bytecode"; + } + + function _getFactoryBytecodePath() internal pure override returns (string memory) { + // https://etherscan.io/address/0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865#code + return "./test/bin/pcsV3Factory.bytecode"; + } + + function _getNfpmBytecodePath() internal pure override returns (string memory) { + // https://etherscan.io/address/0x46A15B0b27311cedF172AB29E4f4766fbE7F4364#code + return "./test/bin/pcsV3Nfpm.bytecode"; + } + + function _getContractName() internal pure override returns (string memory) { + return "BinMigratorFromPancakeswapV3Test"; + } +} diff --git a/test/pool-bin/migrator/BinMigratorFromUniswapV2.t.sol b/test/pool-bin/migrator/BinMigratorFromUniswapV2.t.sol new file mode 100644 index 0000000..d0b28e4 --- /dev/null +++ b/test/pool-bin/migrator/BinMigratorFromUniswapV2.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import {BinMigratorFromV2} from "./BinMigratorFromV2.sol"; + +contract BinMigratorFromUniswapV2Test is BinMigratorFromV2 { + function _getBytecodePath() internal pure override returns (string memory) { + // Create a Uniswap V2 pair + // relative to the root of the project + // https://etherscan.io/address/0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f#code + return "./test/bin/uniV2Factory.bytecode"; + } + + function _getContractName() internal pure override returns (string memory) { + return "BinMigratorFromUniswapV2Test"; + } +} diff --git a/test/pool-bin/migrator/BinMigratorFromUniswapV3.t.sol b/test/pool-bin/migrator/BinMigratorFromUniswapV3.t.sol new file mode 100644 index 0000000..c1b5825 --- /dev/null +++ b/test/pool-bin/migrator/BinMigratorFromUniswapV3.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import {BinMigratorFromV3} from "./BinMigratorFromV3.sol"; + +contract BinMigratorFromUniswapV3Test is BinMigratorFromV3 { + function _getDeployerBytecodePath() internal pure override returns (string memory) { + return ""; + } + + function _getFactoryBytecodePath() internal pure override returns (string memory) { + // https://etherscan.io/address/0x1F98431c8aD98523631AE4a59f267346ea31F984#code + return "./test/bin/uniV3Factory.bytecode"; + } + + function _getNfpmBytecodePath() internal pure override returns (string memory) { + // https://etherscan.io/address/0xC36442b4a4522E871399CD717aBDD847Ab11FE88#code + return "./test/bin/uniV3Nfpm.bytecode"; + } + + function _getContractName() internal pure override returns (string memory) { + return "BinMigratorFromUniswapV3Test"; + } +} diff --git a/test/pool-bin/migrator/BinMigratorFromV2.sol b/test/pool-bin/migrator/BinMigratorFromV2.sol new file mode 100644 index 0000000..f3fb9d8 --- /dev/null +++ b/test/pool-bin/migrator/BinMigratorFromV2.sol @@ -0,0 +1,759 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import {OldVersionHelper} from "../../helpers/OldVersionHelper.sol"; +import {IPancakePair} from "../../../src/interfaces/external/IPancakePair.sol"; +import {WETH} from "solmate/tokens/WETH.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {BinMigrator} from "../../../src/pool-bin/BinMigrator.sol"; +import {IBinMigrator, IBaseMigrator} from "../../../src/pool-bin/interfaces/IBinMigrator.sol"; +import {BinFungiblePositionManager} from "../../../src/pool-bin/BinFungiblePositionManager.sol"; +import {Vault} from "pancake-v4-core/src/Vault.sol"; +import {BinPoolManager} from "pancake-v4-core/src/pool-bin/BinPoolManager.sol"; +import {PoolKey} from "pancake-v4-core/src/types/PoolKey.sol"; +import {BinPoolParametersHelper} from "pancake-v4-core/src/pool-bin/libraries/BinPoolParametersHelper.sol"; +import {Currency} from "pancake-v4-core/src/types/Currency.sol"; +import {IPoolManager} from "pancake-v4-core/src/interfaces/IPoolManager.sol"; +import {IHooks} from "pancake-v4-core/src/interfaces/IHooks.sol"; +import {PoolId, PoolIdLibrary} from "pancake-v4-core/src/types/PoolId.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {LiquidityParamsHelper, IBinFungiblePositionManager} from "../helpers/LiquidityParamsHelper.sol"; +import {BinTokenLibrary} from "../../../src/pool-bin/libraries/BinTokenLibrary.sol"; + +interface IPancakeV2LikePairFactory { + function createPair(address tokenA, address tokenB) external returns (address pair); +} + +abstract contract BinMigratorFromV2 is OldVersionHelper, LiquidityParamsHelper, GasSnapshot { + using BinPoolParametersHelper for bytes32; + using PoolIdLibrary for PoolKey; + using BinTokenLibrary for PoolId; + + // 1 tokenX = 1 tokenY + uint24 public constant ACTIVE_BIN_ID = 2 ** 23; + + WETH weth; + MockERC20 token0; + MockERC20 token1; + + Vault vault; + BinPoolManager poolManager; + BinFungiblePositionManager binFungiblePositionManager; + IBinMigrator migrator; + PoolKey poolKey; + PoolKey poolKeyWithoutNativeToken; + + IPancakeV2LikePairFactory v2Factory; + IPancakePair v2Pair; + IPancakePair v2PairWithoutNativeToken; + + function _getBytecodePath() internal pure virtual returns (string memory); + + function _getContractName() internal pure virtual returns (string memory); + + function setUp() public { + weth = new WETH(); + token0 = new MockERC20("Token0", "TKN0", 18); + token1 = new MockERC20("Token1", "TKN1", 18); + (token0, token1) = token0 < token1 ? (token0, token1) : (token1, token0); + + // init v4 nfpm & migrator + vault = new Vault(); + poolManager = new BinPoolManager(vault, 3000); + vault.registerApp(address(poolManager)); + binFungiblePositionManager = new BinFungiblePositionManager(vault, poolManager, address(weth)); + migrator = new BinMigrator(address(weth), address(binFungiblePositionManager)); + + poolKey = PoolKey({ + // WETH after migration will be native token + currency0: Currency.wrap(address(0)), + currency1: Currency.wrap(address(token0)), + hooks: IHooks(address(0)), + poolManager: poolManager, + fee: 0, + parameters: bytes32(0).setBinStep(1) + }); + + poolKeyWithoutNativeToken = poolKey; + poolKeyWithoutNativeToken.currency0 = Currency.wrap(address(token0)); + poolKeyWithoutNativeToken.currency1 = Currency.wrap(address(token1)); + + // make sure the contract has enough balance + // WETH: 100 ether + // Token: 100 ether + // ETH: 90 ether + deal(address(this), 1000 ether); + weth.deposit{value: 100 ether}(); + token0.mint(address(this), 100 ether); + token1.mint(address(this), 100 ether); + + v2Factory = IPancakeV2LikePairFactory(createContractThroughBytecode(_getBytecodePath())); + v2Pair = IPancakePair(v2Factory.createPair(address(weth), address(token0))); + v2PairWithoutNativeToken = IPancakePair(v2Factory.createPair(address(token0), address(token1))); + } + + function testMigrateFromV2IncludingInit() public { + // 1. mint some liquidity to the v2 pair + _mintV2Liquidity(v2Pair); + uint256 lpTokenBefore = v2Pair.balanceOf(address(this)); + assertGt(lpTokenBefore, 0); + + // 2. make sure migrator can transfer user's v2 lp token + v2Pair.approve(address(migrator), lpTokenBefore); + + IBaseMigrator.V2PoolParams memory v2PoolParams = IBaseMigrator.V2PoolParams({ + pair: address(v2Pair), + migrateAmount: lpTokenBefore, + // minor precision loss is acceptable + amount0Min: 9.999 ether, + amount1Min: 9.999 ether + }); + + IBinFungiblePositionManager.AddLiquidityParams memory params = + _getAddParams(poolKey, getBinIds(ACTIVE_BIN_ID, 3), 10 ether, 10 ether, ACTIVE_BIN_ID, address(this)); + + IBinMigrator.V4BinPoolParams memory v4BinPoolParams = IBinMigrator.V4BinPoolParams({ + poolKey: params.poolKey, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + activeIdDesired: params.activeIdDesired, + idSlippage: params.idSlippage, + deltaIds: params.deltaIds, + distributionX: params.distributionX, + distributionY: params.distributionY, + to: params.to, + deadline: params.deadline + }); + + // 3. multicall, combine initialize and migrateFromV2 + bytes[] memory data = new bytes[](2); + data[0] = abi.encodeWithSelector(migrator.initialize.selector, poolKey, ACTIVE_BIN_ID, bytes("")); + data[1] = abi.encodeWithSelector(migrator.migrateFromV2.selector, v2PoolParams, v4BinPoolParams, 0, 0); + snapStart(string(abi.encodePacked(_getContractName(), "#testMigrateFromV2IncludingInit"))); + migrator.multicall(data); + snapEnd(); + + // necessary checks + // v2 pair should be burned already + assertEq(v2Pair.balanceOf(address(this)), 0); + + // make sure liuqidty is minted to the correct pool + assertApproxEqAbs(address(vault).balance, 10 ether, 0.000001 ether); + assertApproxEqAbs(token0.balanceOf(address(vault)), 10 ether, 0.000001 ether); + + uint256 positionId0 = poolKey.toId().toTokenId(ACTIVE_BIN_ID - 1); + uint256 positionId1 = poolKey.toId().toTokenId(ACTIVE_BIN_ID); + uint256 positionId2 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 1); + uint256 positionId3 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 2); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId0), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId1), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId2), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId3), 0); + + (PoolId poolId, Currency currency0, Currency currency1, uint24 fee, uint24 binId) = + binFungiblePositionManager.positions(positionId0); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID - 1); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId1); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId2); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID + 1); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId3); + } + + function testMigrateFromV2WithoutInit() public { + // 1. mint some liquidity to the v2 pair + _mintV2Liquidity(v2Pair); + uint256 lpTokenBefore = v2Pair.balanceOf(address(this)); + assertGt(lpTokenBefore, 0); + + // 2. make sure migrator can transfer user's v2 lp token + v2Pair.approve(address(migrator), lpTokenBefore); + + // 3. initialize the pool + migrator.initialize(poolKey, ACTIVE_BIN_ID, bytes("")); + + IBaseMigrator.V2PoolParams memory v2PoolParams = IBaseMigrator.V2PoolParams({ + pair: address(v2Pair), + migrateAmount: lpTokenBefore, + // minor precision loss is acceptable + amount0Min: 9.999 ether, + amount1Min: 9.999 ether + }); + + IBinFungiblePositionManager.AddLiquidityParams memory params = + _getAddParams(poolKey, getBinIds(ACTIVE_BIN_ID, 3), 10 ether, 10 ether, ACTIVE_BIN_ID, address(this)); + + IBinMigrator.V4BinPoolParams memory v4BinPoolParams = IBinMigrator.V4BinPoolParams({ + poolKey: params.poolKey, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + activeIdDesired: params.activeIdDesired, + idSlippage: params.idSlippage, + deltaIds: params.deltaIds, + distributionX: params.distributionX, + distributionY: params.distributionY, + to: params.to, + deadline: params.deadline + }); + + // 4. migrateFromV2 + snapStart(string(abi.encodePacked(_getContractName(), "#testMigrateFromV2WithoutInit"))); + migrator.migrateFromV2(v2PoolParams, v4BinPoolParams, 0, 0); + snapEnd(); + + // necessary checks + // v2 pair should be burned already + assertEq(v2Pair.balanceOf(address(this)), 0); + + // make sure liuqidty is minted to the correct pool + assertApproxEqAbs(address(vault).balance, 10 ether, 0.000001 ether); + assertApproxEqAbs(token0.balanceOf(address(vault)), 10 ether, 0.000001 ether); + + uint256 positionId0 = poolKey.toId().toTokenId(ACTIVE_BIN_ID - 1); + uint256 positionId1 = poolKey.toId().toTokenId(ACTIVE_BIN_ID); + uint256 positionId2 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 1); + uint256 positionId3 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 2); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId0), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId1), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId2), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId3), 0); + + (PoolId poolId, Currency currency0, Currency currency1, uint24 fee, uint24 binId) = + binFungiblePositionManager.positions(positionId0); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID - 1); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId1); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId2); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID + 1); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId3); + } + + function testMigrateFromV2WithoutNativeToken() public { + // 1. mint some liquidity to the v2 pair + _mintV2Liquidity(v2PairWithoutNativeToken); + uint256 lpTokenBefore = v2PairWithoutNativeToken.balanceOf(address(this)); + assertGt(lpTokenBefore, 0); + + // 2. make sure migrator can transfer user's v2 lp token + v2PairWithoutNativeToken.approve(address(migrator), lpTokenBefore); + + // 3. initialize the pool + migrator.initialize(poolKeyWithoutNativeToken, ACTIVE_BIN_ID, bytes("")); + + IBaseMigrator.V2PoolParams memory v2PoolParams = IBaseMigrator.V2PoolParams({ + pair: address(v2PairWithoutNativeToken), + migrateAmount: lpTokenBefore, + // minor precision loss is acceptable + amount0Min: 9.999 ether, + amount1Min: 9.999 ether + }); + + IBinFungiblePositionManager.AddLiquidityParams memory params = _getAddParams( + poolKeyWithoutNativeToken, getBinIds(ACTIVE_BIN_ID, 3), 10 ether, 10 ether, ACTIVE_BIN_ID, address(this) + ); + + IBinMigrator.V4BinPoolParams memory v4BinPoolParams = IBinMigrator.V4BinPoolParams({ + poolKey: params.poolKey, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + activeIdDesired: params.activeIdDesired, + idSlippage: params.idSlippage, + deltaIds: params.deltaIds, + distributionX: params.distributionX, + distributionY: params.distributionY, + to: params.to, + deadline: params.deadline + }); + + // 4. migrate from v2 to v4 + snapStart(string(abi.encodePacked(_getContractName(), "#testMigrateFromV2WithoutNativeToken"))); + migrator.migrateFromV2(v2PoolParams, v4BinPoolParams, 0, 0); + snapEnd(); + + // necessary checks + // v2 pair should be burned already + assertEq(v2PairWithoutNativeToken.balanceOf(address(this)), 0); + + // make sure liuqidty is minted to the correct pool + assertApproxEqAbs(token0.balanceOf(address(vault)), 10 ether, 0.000001 ether); + assertApproxEqAbs(token1.balanceOf(address(vault)), 10 ether, 0.000001 ether); + + uint256 positionId0 = poolKeyWithoutNativeToken.toId().toTokenId(ACTIVE_BIN_ID - 1); + uint256 positionId1 = poolKeyWithoutNativeToken.toId().toTokenId(ACTIVE_BIN_ID); + uint256 positionId2 = poolKeyWithoutNativeToken.toId().toTokenId(ACTIVE_BIN_ID + 1); + uint256 positionId3 = poolKeyWithoutNativeToken.toId().toTokenId(ACTIVE_BIN_ID + 2); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId0), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId1), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId2), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId3), 0); + + (PoolId poolId, Currency currency0, Currency currency1, uint24 fee, uint24 binId) = + binFungiblePositionManager.positions(positionId0); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKeyWithoutNativeToken.toId())); + assertEq(Currency.unwrap(currency0), address(token0)); + assertEq(Currency.unwrap(currency1), address(token1)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID - 1); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId1); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKeyWithoutNativeToken.toId())); + assertEq(Currency.unwrap(currency0), address(token0)); + assertEq(Currency.unwrap(currency1), address(token1)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId2); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKeyWithoutNativeToken.toId())); + assertEq(Currency.unwrap(currency0), address(token0)); + assertEq(Currency.unwrap(currency1), address(token1)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID + 1); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId3); + } + + function testMigrateFromV2AddExtraAmount() public { + // 1. mint some liquidity to the v2 pair + _mintV2Liquidity(v2Pair); + uint256 lpTokenBefore = v2Pair.balanceOf(address(this)); + assertGt(lpTokenBefore, 0); + + // 2. make sure migrator can transfer user's v2 lp token + v2Pair.approve(address(migrator), lpTokenBefore); + + // 3. initialize the pool + migrator.initialize(poolKey, ACTIVE_BIN_ID, bytes("")); + + IBaseMigrator.V2PoolParams memory v2PoolParams = IBaseMigrator.V2PoolParams({ + pair: address(v2Pair), + migrateAmount: lpTokenBefore, + // minor precision loss is acceptable + amount0Min: 9.999 ether, + amount1Min: 9.999 ether + }); + + IBinFungiblePositionManager.AddLiquidityParams memory params = + _getAddParams(poolKey, getBinIds(ACTIVE_BIN_ID, 3), 10 ether, 10 ether, ACTIVE_BIN_ID, address(this)); + + IBinMigrator.V4BinPoolParams memory v4BinPoolParams = IBinMigrator.V4BinPoolParams({ + poolKey: params.poolKey, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + activeIdDesired: params.activeIdDesired, + idSlippage: params.idSlippage, + deltaIds: params.deltaIds, + distributionX: params.distributionX, + distributionY: params.distributionY, + to: params.to, + deadline: params.deadline + }); + + uint256 balance0Before = address(this).balance; + uint256 balance1Before = token0.balanceOf(address(this)); + + IERC20(address(token0)).approve(address(migrator), 20 ether); + // 4. migrate from v2 to v4 + migrator.migrateFromV2{value: 20 ether}(v2PoolParams, v4BinPoolParams, 20 ether, 20 ether); + + // necessary checks + // consumed extra 20 ether from user + assertApproxEqAbs(balance0Before - address(this).balance, 20 ether, 0.000001 ether); + assertEq(balance1Before - token0.balanceOf(address(this)), 20 ether); + // WETH balance unchanged + assertEq(weth.balanceOf(address(this)), 90 ether); + + // v2 pair should be burned already + assertEq(v2Pair.balanceOf(address(this)), 0); + + // make sure liuqidty is minted to the correct pool + assertApproxEqAbs(address(vault).balance, 30 ether, 0.000001 ether); + assertApproxEqAbs(token0.balanceOf(address(vault)), 30 ether, 0.000001 ether); + + uint256 positionId0 = poolKey.toId().toTokenId(ACTIVE_BIN_ID - 1); + uint256 positionId1 = poolKey.toId().toTokenId(ACTIVE_BIN_ID); + uint256 positionId2 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 1); + uint256 positionId3 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 2); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId0), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId1), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId2), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId3), 0); + + (PoolId poolId, Currency currency0, Currency currency1, uint24 fee, uint24 binId) = + binFungiblePositionManager.positions(positionId0); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID - 1); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId1); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId2); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID + 1); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId3); + } + + function testMigrateFromV2AddExtraAmountThroughWETH() public { + // 1. mint some liquidity to the v2 pair + _mintV2Liquidity(v2Pair); + uint256 lpTokenBefore = v2Pair.balanceOf(address(this)); + assertGt(lpTokenBefore, 0); + + // 2. make sure migrator can transfer user's v2 lp token + v2Pair.approve(address(migrator), lpTokenBefore); + + // 3. initialize the pool + migrator.initialize(poolKey, ACTIVE_BIN_ID, bytes("")); + + IBaseMigrator.V2PoolParams memory v2PoolParams = IBaseMigrator.V2PoolParams({ + pair: address(v2Pair), + migrateAmount: lpTokenBefore, + // minor precision loss is acceptable + amount0Min: 9.999 ether, + amount1Min: 9.999 ether + }); + + IBinFungiblePositionManager.AddLiquidityParams memory params = + _getAddParams(poolKey, getBinIds(ACTIVE_BIN_ID, 3), 10 ether, 10 ether, ACTIVE_BIN_ID, address(this)); + + IBinMigrator.V4BinPoolParams memory v4BinPoolParams = IBinMigrator.V4BinPoolParams({ + poolKey: params.poolKey, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + activeIdDesired: params.activeIdDesired, + idSlippage: params.idSlippage, + deltaIds: params.deltaIds, + distributionX: params.distributionX, + distributionY: params.distributionY, + to: params.to, + deadline: params.deadline + }); + + uint256 balance0Before = address(this).balance; + uint256 balance1Before = token0.balanceOf(address(this)); + + weth.approve(address(migrator), 20 ether); + IERC20(address(token0)).approve(address(migrator), 20 ether); + // 4. migrate from v2 to v4, not sending ETH denotes pay by WETH + migrator.migrateFromV2(v2PoolParams, v4BinPoolParams, 20 ether, 20 ether); + + // necessary checks + // consumed extra 20 ether from user + // native token balance unchanged + assertApproxEqAbs(balance0Before - address(this).balance, 0 ether, 0.000001 ether); + assertEq(balance1Before - token0.balanceOf(address(this)), 20 ether); + // consumed 20 ether WETH + assertEq(weth.balanceOf(address(this)), 70 ether); + + // v2 pair should be burned already + assertEq(v2Pair.balanceOf(address(this)), 0); + + // make sure liuqidty is minted to the correct pool + assertApproxEqAbs(address(vault).balance, 30 ether, 0.000001 ether); + assertApproxEqAbs(token0.balanceOf(address(vault)), 30 ether, 0.000001 ether); + + uint256 positionId0 = poolKey.toId().toTokenId(ACTIVE_BIN_ID - 1); + uint256 positionId1 = poolKey.toId().toTokenId(ACTIVE_BIN_ID); + uint256 positionId2 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 1); + uint256 positionId3 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 2); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId0), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId1), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId2), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId3), 0); + + (PoolId poolId, Currency currency0, Currency currency1, uint24 fee, uint24 binId) = + binFungiblePositionManager.positions(positionId0); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID - 1); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId1); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId2); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID + 1); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId3); + } + + function testMigrateFromV2Refund() public { + // 1. mint some liquidity to the v2 pair + _mintV2Liquidity(v2Pair, 10 ether, 10 ether); + uint256 lpTokenBefore = v2Pair.balanceOf(address(this)); + assertGt(lpTokenBefore, 0); + + // 2. make sure migrator can transfer user's v2 lp token + v2Pair.approve(address(migrator), lpTokenBefore); + + // 3. initialize the pool + migrator.initialize(poolKey, ACTIVE_BIN_ID, bytes("")); + + IBaseMigrator.V2PoolParams memory v2PoolParams = IBaseMigrator.V2PoolParams({ + pair: address(v2Pair), + migrateAmount: lpTokenBefore, + // the order of token0 and token1 respect to the pair + // but may mismatch the order of v4 pool key when WETH is invovled + amount0Min: 9.99 ether, + amount1Min: 9.99 ether + }); + + IBinFungiblePositionManager.AddLiquidityParams memory params = + _getAddParams(poolKey, getBinIds(ACTIVE_BIN_ID, 3), 10 ether, 10 ether, ACTIVE_BIN_ID, address(this)); + + int256[] memory deltaIds = new int256[](2); + deltaIds[0] = params.deltaIds[0]; + deltaIds[1] = params.deltaIds[1]; + + uint256[] memory distributionX = new uint256[](2); + distributionX[0] = params.distributionX[0]; + distributionX[1] = params.distributionX[1]; + + uint256[] memory distributionY = new uint256[](2); + distributionY[0] = params.distributionY[0]; + distributionY[1] = params.distributionY[1]; + + // delete the last distribution point so that the refund is triggered + // we expect to get 50% of tokenX back + // (0, 50%) (50%, 50%) (50%, 0) => (0, 50%) (50%, 50%) + IBinMigrator.V4BinPoolParams memory v4BinPoolParams = IBinMigrator.V4BinPoolParams({ + poolKey: params.poolKey, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + activeIdDesired: params.activeIdDesired, + idSlippage: params.idSlippage, + deltaIds: deltaIds, + distributionX: distributionX, + distributionY: distributionY, + to: params.to, + deadline: params.deadline + }); + + uint256 balance0Before = address(this).balance; + uint256 balance1Before = token0.balanceOf(address(this)); + + // 4. migrate from v2 to v4, not sending ETH denotes pay by WETH + migrator.migrateFromV2(v2PoolParams, v4BinPoolParams, 0, 0); + + // necessary checks + // refund 5 ether in the form of native token + assertApproxEqAbs(address(this).balance - balance0Before, 5 ether, 0.000001 ether); + assertEq(balance1Before - token0.balanceOf(address(this)), 0 ether); + // WETH balance unchanged + assertEq(weth.balanceOf(address(this)), 90 ether); + + // v2 pair should be burned already + assertEq(v2Pair.balanceOf(address(this)), 0); + + // make sure liuqidty is minted to the correct pool + assertApproxEqAbs(address(vault).balance, 5 ether, 0.000001 ether); + assertApproxEqAbs(token0.balanceOf(address(vault)), 10 ether, 0.000001 ether); + + uint256 positionId0 = poolKey.toId().toTokenId(ACTIVE_BIN_ID - 1); + uint256 positionId1 = poolKey.toId().toTokenId(ACTIVE_BIN_ID); + uint256 positionId2 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 1); + uint256 positionId3 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 2); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId0), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId1), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId2), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId3), 0); + + (PoolId poolId, Currency currency0, Currency currency1, uint24 fee, uint24 binId) = + binFungiblePositionManager.positions(positionId0); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID - 1); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId1); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId2); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId3); + } + + function testMigrateFromV2RefundNonNativeToken() public { + // 1. mint some liquidity to the v2 pair + _mintV2Liquidity(v2PairWithoutNativeToken, 10 ether, 10 ether); + uint256 lpTokenBefore = v2PairWithoutNativeToken.balanceOf(address(this)); + assertGt(lpTokenBefore, 0); + + // 2. make sure migrator can transfer user's v2 lp token + v2PairWithoutNativeToken.approve(address(migrator), lpTokenBefore); + + // 3. initialize the pool + migrator.initialize(poolKeyWithoutNativeToken, ACTIVE_BIN_ID, bytes("")); + + IBaseMigrator.V2PoolParams memory v2PoolParams = IBaseMigrator.V2PoolParams({ + pair: address(v2PairWithoutNativeToken), + migrateAmount: lpTokenBefore, + // the order of token0 and token1 respect to the pair + // but may mismatch the order of v4 pool key when WETH is invovled + amount0Min: 9.999 ether, + amount1Min: 9.999 ether + }); + + IBinFungiblePositionManager.AddLiquidityParams memory params = _getAddParams( + poolKeyWithoutNativeToken, getBinIds(ACTIVE_BIN_ID, 3), 10 ether, 10 ether, ACTIVE_BIN_ID, address(this) + ); + + int256[] memory deltaIds = new int256[](2); + deltaIds[0] = params.deltaIds[0]; + deltaIds[1] = params.deltaIds[1]; + + uint256[] memory distributionX = new uint256[](2); + distributionX[0] = params.distributionX[0]; + distributionX[1] = params.distributionX[1]; + + uint256[] memory distributionY = new uint256[](2); + distributionY[0] = params.distributionY[0]; + distributionY[1] = params.distributionY[1]; + + // delete the last distribution point so that the refund is triggered + // we expect to get 50% of tokenX back + // (0, 50%) (50%, 50%) (50%, 0) => (0, 50%) (50%, 50%) + IBinMigrator.V4BinPoolParams memory v4BinPoolParams = IBinMigrator.V4BinPoolParams({ + poolKey: params.poolKey, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + activeIdDesired: params.activeIdDesired, + idSlippage: params.idSlippage, + deltaIds: deltaIds, + distributionX: distributionX, + distributionY: distributionY, + to: params.to, + deadline: params.deadline + }); + + uint256 balance0Before = token0.balanceOf(address(this)); + uint256 balance1Before = token1.balanceOf(address(this)); + + // 4. migrate from v2 to v4 + migrator.migrateFromV2(v2PoolParams, v4BinPoolParams, 0, 0); + + // necessary checks + + // refund 5 ether of token0 + assertApproxEqAbs(token0.balanceOf(address(this)) - balance0Before, 5 ether, 0.000001 ether); + assertEq(balance1Before - token1.balanceOf(address(this)), 0 ether); + // WETH balance unchanged + assertEq(weth.balanceOf(address(this)), 100 ether); + + // v2 pair should be burned already + assertEq(v2PairWithoutNativeToken.balanceOf(address(this)), 0); + + // make sure liuqidty is minted to the correct pool + assertApproxEqAbs(token0.balanceOf(address(vault)), 5 ether, 0.000001 ether); + assertApproxEqAbs(token1.balanceOf(address(vault)), 10 ether, 0.000001 ether); + + uint256 positionId0 = poolKeyWithoutNativeToken.toId().toTokenId(ACTIVE_BIN_ID - 1); + uint256 positionId1 = poolKeyWithoutNativeToken.toId().toTokenId(ACTIVE_BIN_ID); + uint256 positionId2 = poolKeyWithoutNativeToken.toId().toTokenId(ACTIVE_BIN_ID + 1); + uint256 positionId3 = poolKeyWithoutNativeToken.toId().toTokenId(ACTIVE_BIN_ID + 2); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId0), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId1), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId2), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId3), 0); + + (PoolId poolId, Currency currency0, Currency currency1, uint24 fee, uint24 binId) = + binFungiblePositionManager.positions(positionId0); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKeyWithoutNativeToken.toId())); + assertEq(Currency.unwrap(currency0), address(token0)); + assertEq(Currency.unwrap(currency1), address(token1)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID - 1); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId1); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKeyWithoutNativeToken.toId())); + assertEq(Currency.unwrap(currency0), address(token0)); + assertEq(Currency.unwrap(currency1), address(token1)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId2); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId3); + } + + function _mintV2Liquidity(IPancakePair pair) public { + IERC20(pair.token0()).transfer(address(pair), 10 ether); + IERC20(pair.token1()).transfer(address(pair), 10 ether); + + pair.mint(address(this)); + } + + function _mintV2Liquidity(IPancakePair pair, uint256 amount0, uint256 amount1) public { + IERC20(pair.token0()).transfer(address(pair), amount0); + IERC20(pair.token1()).transfer(address(pair), amount1); + + pair.mint(address(this)); + } + + receive() external payable {} +} diff --git a/test/pool-bin/migrator/BinMigratorFromV3.sol b/test/pool-bin/migrator/BinMigratorFromV3.sol new file mode 100644 index 0000000..932b088 --- /dev/null +++ b/test/pool-bin/migrator/BinMigratorFromV3.sol @@ -0,0 +1,894 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import {OldVersionHelper} from "../../helpers/OldVersionHelper.sol"; +import {IPancakePair} from "../../../src/interfaces/external/IPancakePair.sol"; +import {WETH} from "solmate/tokens/WETH.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {BinMigrator} from "../../../src/pool-bin/BinMigrator.sol"; +import {IBinMigrator, IBaseMigrator} from "../../../src/pool-bin/interfaces/IBinMigrator.sol"; +import {BinFungiblePositionManager} from "../../../src/pool-bin/BinFungiblePositionManager.sol"; +import {Vault} from "pancake-v4-core/src/Vault.sol"; +import {BinPoolManager} from "pancake-v4-core/src/pool-bin/BinPoolManager.sol"; +import {PoolKey} from "pancake-v4-core/src/types/PoolKey.sol"; +import {BinPoolParametersHelper} from "pancake-v4-core/src/pool-bin/libraries/BinPoolParametersHelper.sol"; +import {Currency} from "pancake-v4-core/src/types/Currency.sol"; +import {IPoolManager} from "pancake-v4-core/src/interfaces/IPoolManager.sol"; +import {IHooks} from "pancake-v4-core/src/interfaces/IHooks.sol"; +import {PoolId, PoolIdLibrary} from "pancake-v4-core/src/types/PoolId.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {IV3NonfungiblePositionManager} from "../../../src/interfaces/external/IV3NonfungiblePositionManager.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {LiquidityParamsHelper, IBinFungiblePositionManager} from "../helpers/LiquidityParamsHelper.sol"; +import {BinTokenLibrary} from "../../../src/pool-bin/libraries/BinTokenLibrary.sol"; + +interface IPancakeV3LikePairFactory { + function createPool(address tokenA, address tokenB, uint24 fee) external returns (address pool); +} + +abstract contract BinMigratorFromV3 is OldVersionHelper, LiquidityParamsHelper, GasSnapshot { + using BinPoolParametersHelper for bytes32; + using PoolIdLibrary for PoolKey; + using BinTokenLibrary for PoolId; + + uint160 public constant INIT_SQRT_PRICE = 79228162514264337593543950336; + // 1 tokenX = 1 tokenY + uint24 public constant ACTIVE_BIN_ID = 2 ** 23; + + WETH weth; + MockERC20 token0; + MockERC20 token1; + + Vault vault; + BinPoolManager poolManager; + BinFungiblePositionManager binFungiblePositionManager; + IBinMigrator migrator; + PoolKey poolKey; + PoolKey poolKeyWithoutNativeToken; + + IPancakeV3LikePairFactory v3Factory; + IV3NonfungiblePositionManager v3Nfpm; + + function _getDeployerBytecodePath() internal pure virtual returns (string memory); + function _getFactoryBytecodePath() internal pure virtual returns (string memory); + function _getNfpmBytecodePath() internal pure virtual returns (string memory); + + function _getContractName() internal pure virtual returns (string memory); + + function setUp() public { + weth = new WETH(); + token0 = new MockERC20("Token0", "TKN0", 18); + token1 = new MockERC20("Token1", "TKN1", 18); + (token0, token1) = token0 < token1 ? (token0, token1) : (token1, token0); + + // init v4 nfpm & migrator + vault = new Vault(); + poolManager = new BinPoolManager(vault, 3000); + vault.registerApp(address(poolManager)); + binFungiblePositionManager = new BinFungiblePositionManager(vault, poolManager, address(weth)); + migrator = new BinMigrator(address(weth), address(binFungiblePositionManager)); + + poolKey = PoolKey({ + // WETH after migration will be native token + currency0: Currency.wrap(address(0)), + currency1: Currency.wrap(address(token0)), + hooks: IHooks(address(0)), + poolManager: poolManager, + fee: 0, + parameters: bytes32(0).setBinStep(1) + }); + + poolKeyWithoutNativeToken = poolKey; + poolKeyWithoutNativeToken.currency0 = Currency.wrap(address(token0)); + poolKeyWithoutNativeToken.currency1 = Currency.wrap(address(token1)); + + // make sure the contract has enough balance + // WETH: 100 ether + // Token: 100 ether + // ETH: 90 ether + deal(address(this), 1000 ether); + weth.deposit{value: 100 ether}(); + token0.mint(address(this), 100 ether); + token1.mint(address(this), 100 ether); + + // pcs v3 + if (bytes(_getDeployerBytecodePath()).length != 0) { + address deployer = createContractThroughBytecode(_getDeployerBytecodePath()); + v3Factory = IPancakeV3LikePairFactory( + createContractThroughBytecode(_getFactoryBytecodePath(), toBytes32(address(deployer))) + ); + (bool success,) = deployer.call(abi.encodeWithSignature("setFactoryAddress(address)", address(v3Factory))); + require(success, "setFactoryAddress failed"); + v3Nfpm = IV3NonfungiblePositionManager( + createContractThroughBytecode( + _getNfpmBytecodePath(), + toBytes32(deployer), + toBytes32(address(v3Factory)), + toBytes32(address(weth)), + 0 + ) + ); + } else { + v3Factory = IPancakeV3LikePairFactory(createContractThroughBytecode(_getFactoryBytecodePath())); + + v3Nfpm = IV3NonfungiblePositionManager( + createContractThroughBytecode( + _getNfpmBytecodePath(), toBytes32(address(v3Factory)), toBytes32(address(weth)), 0 + ) + ); + } + + // make sure v3Nfpm has allowance + weth.approve(address(v3Nfpm), type(uint256).max); + token0.approve(address(v3Nfpm), type(uint256).max); + token1.approve(address(v3Nfpm), type(uint256).max); + } + + function testMigrateFromV3IncludingInit() public { + // 1. mint some liquidity to the v3 pool + _mintV3Liquidity(address(weth), address(token0)); + assertEq(v3Nfpm.ownerOf(1), address(this)); + (,,,,,,, uint128 liquidityFromV3Before,,,,) = v3Nfpm.positions(1); + assertGt(liquidityFromV3Before, 0); + + // 2. make sure migrator can transfer user's v3 lp token + v3Nfpm.approve(address(migrator), 1); + + IBaseMigrator.V3PoolParams memory v3PoolParams = IBaseMigrator.V3PoolParams({ + nfp: address(v3Nfpm), + tokenId: 1, + liquidity: liquidityFromV3Before, + amount0Min: 9.9 ether, + amount1Min: 9.9 ether, + collectFee: false, + deadline: block.timestamp + 100 + }); + + IBinFungiblePositionManager.AddLiquidityParams memory params = + _getAddParams(poolKey, getBinIds(ACTIVE_BIN_ID, 3), 10 ether, 10 ether, ACTIVE_BIN_ID, address(this)); + + IBinMigrator.V4BinPoolParams memory v4BinPoolParams = IBinMigrator.V4BinPoolParams({ + poolKey: params.poolKey, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + activeIdDesired: params.activeIdDesired, + idSlippage: params.idSlippage, + deltaIds: params.deltaIds, + distributionX: params.distributionX, + distributionY: params.distributionY, + to: params.to, + deadline: params.deadline + }); + + // 3. multicall, combine initialize and migrateFromV3 + bytes[] memory data = new bytes[](2); + data[0] = abi.encodeWithSelector(migrator.initialize.selector, poolKey, ACTIVE_BIN_ID, bytes("")); + data[1] = abi.encodeWithSelector(migrator.migrateFromV3.selector, v3PoolParams, v4BinPoolParams, 0, 0); + snapStart(string(abi.encodePacked(_getContractName(), "#testMigrateFromV3IncludingInit"))); + migrator.multicall(data); + snapEnd(); + + // necessary checks + // v3 liqudity should be 0 + (,,,,,,, uint128 liquidityFromV3After,,,,) = v3Nfpm.positions(1); + assertEq(liquidityFromV3After, 0); + + // make sure liuqidty is minted to the correct pooA + assertApproxEqAbs(address(vault).balance, 10 ether, 0.000001 ether); + assertApproxEqAbs(token0.balanceOf(address(vault)), 10 ether, 0.000001 ether); + + uint256 positionId0 = poolKey.toId().toTokenId(ACTIVE_BIN_ID - 1); + uint256 positionId1 = poolKey.toId().toTokenId(ACTIVE_BIN_ID); + uint256 positionId2 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 1); + uint256 positionId3 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 2); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId0), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId1), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId2), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId3), 0); + + (PoolId poolId, Currency currency0, Currency currency1, uint24 fee, uint24 binId) = + binFungiblePositionManager.positions(positionId0); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID - 1); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId1); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId2); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID + 1); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId3); + } + + function testMigrateFromV3WithoutInit() public { + // 1. mint some liquidity to the v3 pool + _mintV3Liquidity(address(weth), address(token0)); + assertEq(v3Nfpm.ownerOf(1), address(this)); + (,,,,,,, uint128 liquidityFromV3Before,,,,) = v3Nfpm.positions(1); + assertGt(liquidityFromV3Before, 0); + + // 2. make sure migrator can transfer user's v3 lp token + v3Nfpm.approve(address(migrator), 1); + + // 3. initialize the pool + migrator.initialize(poolKey, ACTIVE_BIN_ID, bytes("")); + + IBaseMigrator.V3PoolParams memory v3PoolParams = IBaseMigrator.V3PoolParams({ + nfp: address(v3Nfpm), + tokenId: 1, + liquidity: liquidityFromV3Before, + amount0Min: 9.9 ether, + amount1Min: 9.9 ether, + collectFee: false, + deadline: block.timestamp + 100 + }); + + IBinFungiblePositionManager.AddLiquidityParams memory params = + _getAddParams(poolKey, getBinIds(ACTIVE_BIN_ID, 3), 10 ether, 10 ether, ACTIVE_BIN_ID, address(this)); + + IBinMigrator.V4BinPoolParams memory v4BinPoolParams = IBinMigrator.V4BinPoolParams({ + poolKey: params.poolKey, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + activeIdDesired: params.activeIdDesired, + idSlippage: params.idSlippage, + deltaIds: params.deltaIds, + distributionX: params.distributionX, + distributionY: params.distributionY, + to: params.to, + deadline: params.deadline + }); + + // 4. migrateFromV3 directly given pool has been initialized + snapStart(string(abi.encodePacked(_getContractName(), "#testMigrateFromV3WithoutInit"))); + migrator.migrateFromV3(v3PoolParams, v4BinPoolParams, 0, 0); + snapEnd(); + + // necessary checks + // v3 liqudity should be 0 + (,,,,,,, uint128 liquidityFromV3After,,,,) = v3Nfpm.positions(1); + assertEq(liquidityFromV3After, 0); + + // make sure liuqidty is minted to the correct pool + assertApproxEqAbs(address(vault).balance, 10 ether, 0.000001 ether); + assertApproxEqAbs(token0.balanceOf(address(vault)), 10 ether, 0.000001 ether); + + uint256 positionId0 = poolKey.toId().toTokenId(ACTIVE_BIN_ID - 1); + uint256 positionId1 = poolKey.toId().toTokenId(ACTIVE_BIN_ID); + uint256 positionId2 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 1); + uint256 positionId3 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 2); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId0), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId1), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId2), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId3), 0); + + (PoolId poolId, Currency currency0, Currency currency1, uint24 fee, uint24 binId) = + binFungiblePositionManager.positions(positionId0); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID - 1); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId1); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId2); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID + 1); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId3); + } + + function testMigrateFromV3WithoutNativeToken() public { + // 1. mint some liquidity to the v3 pool + _mintV3Liquidity(address(token0), address(token1)); + + assertEq(v3Nfpm.ownerOf(1), address(this)); + (,,,,,,, uint128 liquidityFromV3Before,,,,) = v3Nfpm.positions(1); + assertGt(liquidityFromV3Before, 0); + + // 2. make sure migrator can transfer user's v3 lp token + v3Nfpm.approve(address(migrator), 1); + + // 3. initialize the pool + migrator.initialize(poolKeyWithoutNativeToken, ACTIVE_BIN_ID, bytes("")); + + IBaseMigrator.V3PoolParams memory v3PoolParams = IBaseMigrator.V3PoolParams({ + nfp: address(v3Nfpm), + tokenId: 1, + liquidity: liquidityFromV3Before, + amount0Min: 9.9 ether, + amount1Min: 9.9 ether, + collectFee: false, + deadline: block.timestamp + 100 + }); + + IBinFungiblePositionManager.AddLiquidityParams memory params = _getAddParams( + poolKeyWithoutNativeToken, getBinIds(ACTIVE_BIN_ID, 3), 10 ether, 10 ether, ACTIVE_BIN_ID, address(this) + ); + + IBinMigrator.V4BinPoolParams memory v4BinPoolParams = IBinMigrator.V4BinPoolParams({ + poolKey: params.poolKey, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + activeIdDesired: params.activeIdDesired, + idSlippage: params.idSlippage, + deltaIds: params.deltaIds, + distributionX: params.distributionX, + distributionY: params.distributionY, + to: params.to, + deadline: params.deadline + }); + + // 4. migrate from v3 to v4 + snapStart(string(abi.encodePacked(_getContractName(), "#testMigrateFromV3WithoutNativeToken"))); + migrator.migrateFromV3(v3PoolParams, v4BinPoolParams, 0, 0); + snapEnd(); + + // necessary checks + // v3 liqudity should be 0 + (,,,,,,, uint128 liquidityFromV3After,,,,) = v3Nfpm.positions(1); + assertEq(liquidityFromV3After, 0); + + // make sure liuqidty is minted to the correct pool + assertApproxEqAbs(token0.balanceOf(address(vault)), 10 ether, 0.000001 ether); + assertApproxEqAbs(token1.balanceOf(address(vault)), 10 ether, 0.000001 ether); + + uint256 positionId0 = poolKeyWithoutNativeToken.toId().toTokenId(ACTIVE_BIN_ID - 1); + uint256 positionId1 = poolKeyWithoutNativeToken.toId().toTokenId(ACTIVE_BIN_ID); + uint256 positionId2 = poolKeyWithoutNativeToken.toId().toTokenId(ACTIVE_BIN_ID + 1); + uint256 positionId3 = poolKeyWithoutNativeToken.toId().toTokenId(ACTIVE_BIN_ID + 2); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId0), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId1), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId2), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId3), 0); + + (PoolId poolId, Currency currency0, Currency currency1, uint24 fee, uint24 binId) = + binFungiblePositionManager.positions(positionId0); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKeyWithoutNativeToken.toId())); + assertEq(Currency.unwrap(currency0), address(token0)); + assertEq(Currency.unwrap(currency1), address(token1)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID - 1); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId1); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKeyWithoutNativeToken.toId())); + assertEq(Currency.unwrap(currency0), address(token0)); + assertEq(Currency.unwrap(currency1), address(token1)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId2); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKeyWithoutNativeToken.toId())); + assertEq(Currency.unwrap(currency0), address(token0)); + assertEq(Currency.unwrap(currency1), address(token1)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID + 1); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId3); + } + + function testMigrateFromV3AddExtraAmount() public { + // 1. mint some liquidity to the v3 pool + _mintV3Liquidity(address(weth), address(token0)); + assertEq(v3Nfpm.ownerOf(1), address(this)); + (,,,,,,, uint128 liquidityFromV3Before,,,,) = v3Nfpm.positions(1); + assertGt(liquidityFromV3Before, 0); + + // 2. make sure migrator can transfer user's v3 lp token + v3Nfpm.approve(address(migrator), 1); + + // 3. init the pool + migrator.initialize(poolKey, ACTIVE_BIN_ID, bytes("")); + + IBaseMigrator.V3PoolParams memory v3PoolParams = IBaseMigrator.V3PoolParams({ + nfp: address(v3Nfpm), + tokenId: 1, + liquidity: liquidityFromV3Before, + amount0Min: 9.9 ether, + amount1Min: 9.9 ether, + collectFee: false, + deadline: block.timestamp + 100 + }); + + IBinFungiblePositionManager.AddLiquidityParams memory params = + _getAddParams(poolKey, getBinIds(ACTIVE_BIN_ID, 3), 10 ether, 10 ether, ACTIVE_BIN_ID, address(this)); + + IBinMigrator.V4BinPoolParams memory v4BinPoolParams = IBinMigrator.V4BinPoolParams({ + poolKey: params.poolKey, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + activeIdDesired: params.activeIdDesired, + idSlippage: params.idSlippage, + deltaIds: params.deltaIds, + distributionX: params.distributionX, + distributionY: params.distributionY, + to: params.to, + deadline: params.deadline + }); + + uint256 balance0Before = address(this).balance; + uint256 balance1Before = token0.balanceOf(address(this)); + + IERC20(address(token0)).approve(address(migrator), 20 ether); + // 4. migrate from v3 to v4 + migrator.migrateFromV3{value: 20 ether}(v3PoolParams, v4BinPoolParams, 20 ether, 20 ether); + + // necessary checks + // consumed extra 20 ether from user + assertApproxEqAbs(balance0Before - address(this).balance, 20 ether, 0.000001 ether); + assertApproxEqAbs(balance1Before - token0.balanceOf(address(this)), 20 ether, 0.000001 ether); + // WETH balance unchanged + assertEq(weth.balanceOf(address(this)), 90 ether); + + // v3 liqudity should be 0 + (,,,,,,, uint128 liquidityFromV3After,,,,) = v3Nfpm.positions(1); + assertEq(liquidityFromV3After, 0); + + // make sure liuqidty is minted to the correct pool + assertApproxEqAbs(address(vault).balance, 30 ether, 0.000001 ether); + assertApproxEqAbs(token0.balanceOf(address(vault)), 30 ether, 0.000001 ether); + + uint256 positionId0 = poolKey.toId().toTokenId(ACTIVE_BIN_ID - 1); + uint256 positionId1 = poolKey.toId().toTokenId(ACTIVE_BIN_ID); + uint256 positionId2 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 1); + uint256 positionId3 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 2); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId0), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId1), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId2), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId3), 0); + + (PoolId poolId, Currency currency0, Currency currency1, uint24 fee, uint24 binId) = + binFungiblePositionManager.positions(positionId0); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID - 1); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId1); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId2); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID + 1); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId3); + } + + function testMigrateFromV3AddExtraAmountThroughWETH() public { + // 1. mint some liquidity to the v3 pool + _mintV3Liquidity(address(weth), address(token0)); + assertEq(v3Nfpm.ownerOf(1), address(this)); + (,,,,,,, uint128 liquidityFromV3Before,,,,) = v3Nfpm.positions(1); + assertGt(liquidityFromV3Before, 0); + + // 2. make sure migrator can transfer user's v3 lp token + v3Nfpm.approve(address(migrator), 1); + + // 3. init the pool + migrator.initialize(poolKey, ACTIVE_BIN_ID, bytes("")); + + IBaseMigrator.V3PoolParams memory v3PoolParams = IBaseMigrator.V3PoolParams({ + nfp: address(v3Nfpm), + tokenId: 1, + liquidity: liquidityFromV3Before, + amount0Min: 9.9 ether, + amount1Min: 9.9 ether, + collectFee: false, + deadline: block.timestamp + 100 + }); + + IBinFungiblePositionManager.AddLiquidityParams memory params = + _getAddParams(poolKey, getBinIds(ACTIVE_BIN_ID, 3), 10 ether, 10 ether, ACTIVE_BIN_ID, address(this)); + + IBinMigrator.V4BinPoolParams memory v4BinPoolParams = IBinMigrator.V4BinPoolParams({ + poolKey: params.poolKey, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + activeIdDesired: params.activeIdDesired, + idSlippage: params.idSlippage, + deltaIds: params.deltaIds, + distributionX: params.distributionX, + distributionY: params.distributionY, + to: params.to, + deadline: params.deadline + }); + + uint256 balance0Before = address(this).balance; + uint256 balance1Before = token0.balanceOf(address(this)); + + weth.approve(address(migrator), 20 ether); + IERC20(address(token0)).approve(address(migrator), 20 ether); + // 4. migrate from v3 to v4, not sending ETH denotes pay by WETH + migrator.migrateFromV3(v3PoolParams, v4BinPoolParams, 20 ether, 20 ether); + + // necessary checks + // consumed extra 20 ether from user + // native token balance unchanged + assertApproxEqAbs(address(this).balance - balance0Before, 0 ether, 0.000001 ether); + assertApproxEqAbs(balance1Before - token0.balanceOf(address(this)), 20 ether, 0.00001 ether); + // consumed 20 ether WETH + assertEq(weth.balanceOf(address(this)), 70 ether); + + // v3 liqudity should be 0 + (,,,,,,, uint128 liquidityFromV3After,,,,) = v3Nfpm.positions(1); + assertEq(liquidityFromV3After, 0); + + // make sure liuqidty is minted to the correct pool + assertApproxEqAbs(address(vault).balance, 30 ether, 0.000001 ether); + assertApproxEqAbs(token0.balanceOf(address(vault)), 30 ether, 0.000001 ether); + + uint256 positionId0 = poolKey.toId().toTokenId(ACTIVE_BIN_ID - 1); + uint256 positionId1 = poolKey.toId().toTokenId(ACTIVE_BIN_ID); + uint256 positionId2 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 1); + uint256 positionId3 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 2); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId0), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId1), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId2), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId3), 0); + + (PoolId poolId, Currency currency0, Currency currency1, uint24 fee, uint24 binId) = + binFungiblePositionManager.positions(positionId0); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID - 1); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId1); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId2); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID + 1); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId3); + } + + function testMigrateFromV3Refund() public { + // 1. mint some liquidity to the v3 pool + _mintV3Liquidity(address(weth), address(token0)); + assertEq(v3Nfpm.ownerOf(1), address(this)); + (,,,,,,, uint128 liquidityFromV3Before,,,,) = v3Nfpm.positions(1); + assertGt(liquidityFromV3Before, 0); + + // 2. make sure migrator can transfer user's v3 lp token + v3Nfpm.approve(address(migrator), 1); + + // 3. init the pool + migrator.initialize(poolKey, ACTIVE_BIN_ID, bytes("")); + + IBaseMigrator.V3PoolParams memory v3PoolParams = IBaseMigrator.V3PoolParams({ + nfp: address(v3Nfpm), + tokenId: 1, + liquidity: liquidityFromV3Before, + amount0Min: 0, + amount1Min: 0, + collectFee: false, + deadline: block.timestamp + 100 + }); + + // adding half of the liquidity to the pool + IBinFungiblePositionManager.AddLiquidityParams memory params = + _getAddParams(poolKey, getBinIds(ACTIVE_BIN_ID, 3), 10 ether, 10 ether, ACTIVE_BIN_ID, address(this)); + + int256[] memory deltaIds = new int256[](2); + deltaIds[0] = params.deltaIds[0]; + deltaIds[1] = params.deltaIds[1]; + + uint256[] memory distributionX = new uint256[](2); + distributionX[0] = params.distributionX[0]; + distributionX[1] = params.distributionX[1]; + + uint256[] memory distributionY = new uint256[](2); + distributionY[0] = params.distributionY[0]; + distributionY[1] = params.distributionY[1]; + + // delete the last distribution point so that the refund is triggered + // we expect to get 50% of tokenX back + // (0, 50%) (50%, 50%) (50%, 0) => (0, 50%) (50%, 50%) + IBinMigrator.V4BinPoolParams memory v4BinPoolParams = IBinMigrator.V4BinPoolParams({ + poolKey: params.poolKey, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + activeIdDesired: params.activeIdDesired, + idSlippage: params.idSlippage, + deltaIds: deltaIds, + distributionX: distributionX, + distributionY: distributionY, + to: params.to, + deadline: params.deadline + }); + + uint256 balance0Before = address(this).balance; + uint256 balance1Before = token0.balanceOf(address(this)); + + // 4. migrate from v3 to v4, not sending ETH denotes pay by WETH + migrator.migrateFromV3(v3PoolParams, v4BinPoolParams, 0, 0); + + // necessary checks + // refund 5 ether in the form of native token + assertApproxEqAbs(address(this).balance - balance0Before, 5.0 ether, 0.1 ether); + assertApproxEqAbs(token0.balanceOf(address(this)) - balance1Before, 0 ether, 1); + // WETH balance unchanged + assertApproxEqAbs(weth.balanceOf(address(this)), 90 ether, 0.1 ether); + + // v3 liqudity should be 0 + (,,,,,,, uint128 liquidityFromV3After,,,,) = v3Nfpm.positions(1); + assertEq(liquidityFromV3After, 0); + + // make sure liuqidty is minted to the correct pool + assertApproxEqAbs(address(vault).balance, 5 ether, 0.000001 ether); + assertApproxEqAbs(token0.balanceOf(address(vault)), 10 ether, 0.000001 ether); + + uint256 positionId0 = poolKey.toId().toTokenId(ACTIVE_BIN_ID - 1); + uint256 positionId1 = poolKey.toId().toTokenId(ACTIVE_BIN_ID); + uint256 positionId2 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 1); + uint256 positionId3 = poolKey.toId().toTokenId(ACTIVE_BIN_ID + 2); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId0), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId1), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId2), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId3), 0); + + (PoolId poolId, Currency currency0, Currency currency1, uint24 fee, uint24 binId) = + binFungiblePositionManager.positions(positionId0); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID - 1); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId1); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKey.toId())); + assertEq(Currency.unwrap(currency0), address(0)); + assertEq(Currency.unwrap(currency1), address(token0)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId2); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId3); + } + + function testMigrateFromV3RefundNonNativeToken() public { + // 1. mint some liquidity to the v3 pool + _mintV3Liquidity(address(token0), address(token1)); + assertEq(v3Nfpm.ownerOf(1), address(this)); + (,,,,,,, uint128 liquidityFromV3Before,,,,) = v3Nfpm.positions(1); + assertGt(liquidityFromV3Before, 0); + + // 2. make sure migrator can transfer user's v3 lp token + v3Nfpm.approve(address(migrator), 1); + + // 3. init the pool + migrator.initialize(poolKeyWithoutNativeToken, ACTIVE_BIN_ID, bytes("")); + + IBaseMigrator.V3PoolParams memory v3PoolParams = IBaseMigrator.V3PoolParams({ + nfp: address(v3Nfpm), + tokenId: 1, + liquidity: liquidityFromV3Before, + amount0Min: 0, + amount1Min: 0, + collectFee: false, + deadline: block.timestamp + 100 + }); + + // adding half of the liquidity to the pool + IBinFungiblePositionManager.AddLiquidityParams memory params = _getAddParams( + poolKeyWithoutNativeToken, getBinIds(ACTIVE_BIN_ID, 3), 10 ether, 10 ether, ACTIVE_BIN_ID, address(this) + ); + + int256[] memory deltaIds = new int256[](2); + deltaIds[0] = params.deltaIds[0]; + deltaIds[1] = params.deltaIds[1]; + + uint256[] memory distributionX = new uint256[](2); + distributionX[0] = params.distributionX[0]; + distributionX[1] = params.distributionX[1]; + + uint256[] memory distributionY = new uint256[](2); + distributionY[0] = params.distributionY[0]; + distributionY[1] = params.distributionY[1]; + + // delete the last distribution point so that the refund is triggered + // we expect to get 50% of tokenX back + // (0, 50%) (50%, 50%) (50%, 0) => (0, 50%) (50%, 50%) + IBinMigrator.V4BinPoolParams memory v4BinPoolParams = IBinMigrator.V4BinPoolParams({ + poolKey: params.poolKey, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + activeIdDesired: params.activeIdDesired, + idSlippage: params.idSlippage, + deltaIds: deltaIds, + distributionX: distributionX, + distributionY: distributionY, + to: params.to, + deadline: params.deadline + }); + + uint256 balance0Before = token0.balanceOf(address(this)); + uint256 balance1Before = token1.balanceOf(address(this)); + + // 4. migrate from v3 to v4 + migrator.migrateFromV3(v3PoolParams, v4BinPoolParams, 0, 0); + + // necessary checks + + // refund 5 ether of token0 + assertApproxEqAbs(token0.balanceOf(address(this)) - balance0Before, 5 ether, 0.1 ether); + assertApproxEqAbs(token1.balanceOf(address(this)) - balance1Before, 0 ether, 1); + // WETH balance unchanged + assertEq(weth.balanceOf(address(this)), 100 ether); + + // v3 liqudity should be 0 + (,,,,,,, uint128 liquidityFromV3After,,,,) = v3Nfpm.positions(1); + assertEq(liquidityFromV3After, 0); + + // make sure liuqidty is minted to the correct pool + assertApproxEqAbs(token0.balanceOf(address(vault)), 5 ether, 0.000001 ether); + assertApproxEqAbs(token1.balanceOf(address(vault)), 10 ether, 0.000001 ether); + + uint256 positionId0 = poolKeyWithoutNativeToken.toId().toTokenId(ACTIVE_BIN_ID - 1); + uint256 positionId1 = poolKeyWithoutNativeToken.toId().toTokenId(ACTIVE_BIN_ID); + uint256 positionId2 = poolKeyWithoutNativeToken.toId().toTokenId(ACTIVE_BIN_ID + 1); + uint256 positionId3 = poolKeyWithoutNativeToken.toId().toTokenId(ACTIVE_BIN_ID + 2); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId0), 0); + assertGt(binFungiblePositionManager.balanceOf(address(this), positionId1), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId2), 0); + assertEq(binFungiblePositionManager.balanceOf(address(this), positionId3), 0); + + (PoolId poolId, Currency currency0, Currency currency1, uint24 fee, uint24 binId) = + binFungiblePositionManager.positions(positionId0); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKeyWithoutNativeToken.toId())); + assertEq(Currency.unwrap(currency0), address(token0)); + assertEq(Currency.unwrap(currency1), address(token1)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID - 1); + + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId1); + assertEq(PoolId.unwrap(poolId), PoolId.unwrap(poolKeyWithoutNativeToken.toId())); + assertEq(Currency.unwrap(currency0), address(token0)); + assertEq(Currency.unwrap(currency1), address(token1)); + assertEq(fee, 0); + assertEq(binId, ACTIVE_BIN_ID); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId2); + + vm.expectRevert(IBinFungiblePositionManager.InvalidTokenID.selector); + (poolId, currency0, currency1, fee, binId) = binFungiblePositionManager.positions(positionId3); + } + + function testMigrateFromV3FromNonOwner() public { + // 1. mint some liquidity to the v3 pool + _mintV3Liquidity(address(weth), address(token0)); + assertEq(v3Nfpm.ownerOf(1), address(this)); + (,,,,,,, uint128 liquidityFromV3Before,,,,) = v3Nfpm.positions(1); + assertGt(liquidityFromV3Before, 0); + + // 2. make sure migrator can transfer user's v3 lp token + v3Nfpm.approve(address(migrator), 1); + + // 3. init the pool + migrator.initialize(poolKey, ACTIVE_BIN_ID, bytes("")); + + IBaseMigrator.V3PoolParams memory v3PoolParams = IBaseMigrator.V3PoolParams({ + nfp: address(v3Nfpm), + tokenId: 1, + // half of the liquidity + liquidity: liquidityFromV3Before / 2, + amount0Min: 9.9 ether / 2, + amount1Min: 9.9 ether / 2, + collectFee: false, + deadline: block.timestamp + 100 + }); + + IBinFungiblePositionManager.AddLiquidityParams memory params = + _getAddParams(poolKey, getBinIds(ACTIVE_BIN_ID, 3), 10 ether, 10 ether, ACTIVE_BIN_ID, address(this)); + + int256[] memory deltaIds = new int256[](2); + deltaIds[0] = params.deltaIds[0]; + deltaIds[1] = params.deltaIds[1]; + + uint256[] memory distributionX = new uint256[](2); + distributionX[0] = params.distributionX[0]; + distributionX[1] = params.distributionX[1]; + + uint256[] memory distributionY = new uint256[](2); + distributionY[0] = params.distributionY[0]; + distributionY[1] = params.distributionY[1]; + + // delete the last distribution point so that the refund is triggered + // we expect to get 50% of tokenX back + // (0, 50%) (50%, 50%) (50%, 0) => (0, 50%) (50%, 50%) + IBinMigrator.V4BinPoolParams memory v4BinPoolParams = IBinMigrator.V4BinPoolParams({ + poolKey: params.poolKey, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + activeIdDesired: params.activeIdDesired, + idSlippage: params.idSlippage, + deltaIds: deltaIds, + distributionX: distributionX, + distributionY: distributionY, + to: params.to, + deadline: params.deadline + }); + + // 4. migrate half + migrator.migrateFromV3(v3PoolParams, v4BinPoolParams, 0, 0); + + // make sure there are still liquidity left in v3 position token + (,,,,,,, uint128 liquidityFromV3After,,,,) = v3Nfpm.positions(1); + assertEq(liquidityFromV3After, liquidityFromV3Before - liquidityFromV3Before / 2); + + // 5. make sure non-owner can't migrate the rest + vm.expectRevert(IBaseMigrator.NOT_TOKEN_OWNER.selector); + vm.prank(makeAddr("someone")); + migrator.migrateFromV3(v3PoolParams, v4BinPoolParams, 0, 0); + } + + function _mintV3Liquidity(address _token0, address _token1) internal { + (_token0, _token1) = _token0 < _token1 ? (_token0, _token1) : (_token1, _token0); + v3Nfpm.createAndInitializePoolIfNecessary(_token0, _token1, 500, INIT_SQRT_PRICE); + IV3NonfungiblePositionManager.MintParams memory mintParams = IV3NonfungiblePositionManager.MintParams({ + token0: _token0, + token1: _token1, + fee: 500, + tickLower: -100, + tickUpper: 100, + amount0Desired: 10 ether, + amount1Desired: 10 ether, + amount0Min: 0, + amount1Min: 0, + recipient: address(this), + deadline: block.timestamp + 100 + }); + + v3Nfpm.mint(mintParams); + } + + receive() external payable {} +} diff --git a/test/pool-cl/migrator/CLMigratorFromV3.sol b/test/pool-cl/migrator/CLMigratorFromV3.sol index c087744..f0eb38b 100644 --- a/test/pool-cl/migrator/CLMigratorFromV3.sol +++ b/test/pool-cl/migrator/CLMigratorFromV3.sol @@ -448,7 +448,6 @@ abstract contract CLMigratorFromV3 is OldVersionHelper, GasSnapshot { } function testMigrateFromV3AddExtraAmountThroughWETH() public { - // 1. mint some liquidity to the v2 pair // 1. mint some liquidity to the v3 pool _mintV3Liquidity(address(weth), address(token0)); assertEq(v3Nfpm.ownerOf(1), address(this));