Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
chefburger committed Jul 3, 2024
1 parent 698cd57 commit bd5b369
Show file tree
Hide file tree
Showing 4 changed files with 260 additions and 0 deletions.
2 changes: 2 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ ds-test/=lib/forge-std/lib/ds-test/src/
pancake-v4-core/=lib/pancake-v4-core/
@openzeppelin/=lib/openzeppelin-contracts/
solmate/=lib/solmate/src/
@uniswap/v2-core/=lib/v2-core/
@uniswap/v3-periphery/=lib/v3-periphery/
53 changes: 53 additions & 0 deletions src/base/BaseMigrator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (C) 2024 PancakeSwap
pragma solidity ^0.8.19;

import {IUniswapV2Pair} from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
import {INonfungiblePositionManager} from "@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";
import {PeripheryImmutableState} from "./PeripheryImmutableState.sol";

contract BaseMigrator is PeripheryImmutableState, Multicall, SelfPermit {
constructor(address _WETH9) PeripheryImmutableState(_WETH9) {}

function withdrawLiquidityFromV2(address pair, uint256 amount)
internal
returns (uint256 amount0Received, uint256 amount1Received)
{
// burn v2 liquidity to this address
IUniswapV2Pair(pair).transferFrom(msg.sender, pair, amount);
return IUniswapV2Pair(pair).burn(address(this));
}

function withdrawLiquidityFromV3(
address nfp,
INonfungiblePositionManager.DecreaseLiquidityParams decreaseLiquidityParams,
bool collectFee
) internal returns (uint256 amount0Received, uint256 amount1Received) {
// TODO: consider batching decreaseLiquidity and collect

/// @notice decrease liquidity from v3#nfp, make sure migrator has been approved
(amount0Received, amount1Received) = INonfungiblePositionManager(nfp).decreaseLiquidity(decreaseLiquidityParams);

INonfungiblePositionManager.CollectParams collectParams = INonfungiblePositionManager.CollectParams({
tokenId: decreaseLiquidityParams.tokenId,
recipient: address(this),
amount0Max: collectFee ? type(uint128).max : amount0Received,
amount1Max: collectFee ? type(uint128).max : amount1Received
});

return INonfungiblePositionManager(nfp).collect(collectParams);
}

function refund(address token, address to, uint256 amount) internal {
if (token == WETH9) {
IWETH9(WETH9).withdraw(amount);
TransferHelper.safeTransferETH(to, amount);
} else {
TransferHelper.safeTransfer(token, to, amount);
}
}

receive() external payable {
require(msg.sender == WETH9, "Not WETH9");
}
}
146 changes: 146 additions & 0 deletions src/pool-cl/CLMigrator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (C) 2024 PancakeSwap
pragma solidity ^0.8.19;

import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {BaseMigrator} from "../base/BaseMigrator.sol";
import {ICLMigrator} from "./interfaces/ICLMigrator.sol";
import {INonfungiblePositionManager} from "./interfaces/INonfungiblePositionManager.sol";

contract CLMigrator is ICLMigrator, BaseMigrator {
using LowGasSafeMath for uint256;

INonfungiblePositionManager public immutable nonfungiblePositionManager;

constructor(address _WETH9, address _nonfungiblePositionManager) BaseMigrator(_WETH9) {
nonfungiblePositionManager = _nonfungiblePositionManager;
}

function migrateFromV2(MigrateFromV2Params calldata params) external override {
// 1. burn v2 liquidity to this address
(uint256 amount0Received, uint256 amount1Received) =
withdrawLiquidityFromV2(params.pair, params.liquidityToMigrate);

if (params.amount0In != 0) {
SafeTransferLib.safeTransferFrom(params.poolKey.currency0, msg.sender, address(this), params.amount0In);
}
if (params.amount1In != 0) {
SafeTransferLib.safeTransferFrom(params.poolKey.currency1, msg.sender, address(this), params.amount1In);
}

// 2. mint v4 position token, token sent to recipient
// TO BE CONFIRMED:
// Consider the case from a WETH pool to a ETH pool (v2 not support ETH pool but v4 does):
// in that case we might need to unwrap WETH to ETH and send to the nfp contract
// but how many token should be sent to the nfp contract ?
// i don't see nfp have refund logic, what if the token consumed is less than the token sent ?
SafeTransferLib.safeApprove(params.poolKey.currency0, address(nonfungiblePositionManager), amount0Desired);
SafeTransferLib.safeApprove(params.poolKey.currency1, address(nonfungiblePositionManager), amount1Desired);

// TODO: init the pool if necessary

(uint256 tokenId, uint128 liquidity, uint256 amount0Consumed, uint256 amount1Consumed) =
nonfungiblePositionManager.mint(
MintParams({
PoolKey: params.poolKey,
tickLower: params.tickLower,
tickUpper: params.tickUpper,
salt: params.salt,
amount0Desired: params.amount0Desired,
amount1Desired: params.amount1Desired,
amount0Min: params.amount0Min,
amount1Min: params.amount1Min,
recipient: params.recipient,
deadline: params.deadline
})
);

// TODO: any other check here?

// 3. clear allowance and refund if necessary
if (params.amount0Desired > amount0Consumed) {
SafeTransferLib.safeApprove(params.poolKey.currency0, address(nonfungiblePositionManager), 0);
}
if (params.amount1Desired > amount1Consumed) {
SafeTransferLib.safeApprove(params.poolKey.currency1, address(nonfungiblePositionManager), 0);
}

if (amount0Received > amount0Consumed) {
refund(params.poolKey.currency0, params.recipient, amount0Received - amount0Consumed);
}

if (amount1Received > amount1Consumed) {
refund(params.poolKey.currency1, params.recipient, amount1Received - amount1Consumed);
}

// TODO: confirm whether we need any events here
}

function migrateFromV3(MigrateFromV3Params calldata params) external override {
// 1. burn v3 liquidity to this address
(uint256 amount0Received, uint256 amount1Received) = withdrawLiquidityFromV3(
params.nfp,
INonfungiblePositionManager.DecreaseLiquidityParams({
tokenId: params.tokenId,
liquidity: params.liquidity,
amount0Min: params.amount0MinForV3,
amount1Min: params.amount1MinForV3
}),
params.collectFee
);

if (params.amount0In != 0) {
SafeTransferLib.safeTransferFrom(params.poolKey.currency0, msg.sender, address(this), params.amount0In);
}
if (params.amount1In != 0) {
SafeTransferLib.safeTransferFrom(params.poolKey.currency1, msg.sender, address(this), params.amount1In);
}

// 2. mint v4 position token, token sent to recipient
// TO BE CONFIRMED:
// Consider the case from a WETH pool to a ETH pool (v3 not support ETH pool but v4 does):
// in that case we might need to unwrap WETH to ETH and send to the nfp contract
// but how many token should be sent to the nfp contract ?
// i don't see nfp have refund logic, what if the token consumed is less than the token sent ?
SafeTransferLib.safeApprove(params.poolKey.currency0, address(nonfungiblePositionManager), amount0Desired);
SafeTransferLib.safeApprove(params.poolKey.currency1, address(nonfungiblePositionManager), amount1Desired);

// TODO: init the pool if necessary

(uint256 tokenId, uint128 liquidity, uint256 amount0Consumed, uint256 amount1Consumed) =
nonfungiblePositionManager.mint(
MintParams({
PoolKey: params.poolKey,
tickLower: params.tickLower,
tickUpper: params.tickUpper,
salt: params.salt,
amount0Desired: params.amount0Desired,
amount1Desired: params.amount1Desired,
amount0Min: params.amount0Min,
amount1Min: params.amount1Min,
recipient: params.recipient,
deadline: params.deadline
})
);

// TODO: any other check here?

// 3. clear allowance and refund if necessary
if (params.amount0Desired > amount0Consumed) {
SafeTransferLib.safeApprove(params.poolKey.currency0, address(nonfungiblePositionManager), 0);
}
if (params.amount1Desired > amount1Consumed) {
SafeTransferLib.safeApprove(params.poolKey.currency1, address(nonfungiblePositionManager), 0);
}

if (amount0Received > amount0Consumed) {
refund(params.poolKey.currency0, params.recipient, amount0Received - amount0Consumed);
}

if (amount1Received > amount1Consumed) {
refund(params.poolKey.currency1, params.recipient, amount1Received - amount1Consumed);
}

// TODO: confirm whether we need any events here
}
}
59 changes: 59 additions & 0 deletions src/pool-cl/interfaces/ICLMigrator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (C) 2024 PancakeSwap
pragma solidity ^0.8.19;

import {PoolKey} from "pancake-v4-core/src/types/PoolKey.sol";

interface ICLMigrator {
struct MigrateFromV2Params {
// source v2 pool params
address pair; // the PancakeSwap v2-compatible pair
uint256 liquidityToMigrate; // the amount of liquidity to migrate
// target v4 pool params
PoolKey poolKey;
int24 tickLower;
int24 tickUpper;
bytes32 salt;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
uint256 deadline;
// amt0, amt1 that will be added to the pool
// usually when ppl specify price range manually
uint256 amount0In;
uint256 amount1In;
bool refundAsETH;
}

struct MigrateFromV3Params {
// source v3 pool params
address nfp; // the PancakeSwap v3-compatible NFP
uint256 tokenId;
uint128 liquidity;
uint256 amount0MinForV3;
uint256 amount1MinForV3;
bool collectFee;
// target v4 pool params
PoolKey poolKey; // the target v4 pool
int24 tickLower;
int24 tickUpper;
bytes32 salt;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
uint256 deadline;
// amt0, amt1 that will be added to the pool
// usually when ppl specify price range manually
uint256 amount0In;
uint256 amount1In;
bool refundAsETH;
}

function migrateFromV2(MigrateFromV2Params calldata params) external;

function migrateFromV3(MigrateFromV3Params calldata params) external;
}

0 comments on commit bd5b369

Please sign in to comment.