-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
698cd57
commit bd5b369
Showing
4 changed files
with
260 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |