Skip to content

Commit

Permalink
feat: impl binPool migrator
Browse files Browse the repository at this point in the history
  • Loading branch information
chefburger committed Jul 9, 2024
1 parent ca5fd6f commit 4bb0e82
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 1 deletion.
142 changes: 142 additions & 0 deletions src/pool-bin/BinMigrator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// 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, IV3NonfungiblePositionManager} from "../base/BaseMigrator.sol";
import {IBinMigrator, PoolKey} from "./interfaces/IBinMigrator.sol";
import {IBinFungiblePositionManager} from "./interfaces/IBinFungiblePositionManager.sol";
import {Currency} from "pancake-v4-core/src/types/Currency.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";

contract BinMigrator is IBinMigrator, BaseMigrator {
IBinFungiblePositionManager public immutable binFungiblePositionManager;

constructor(address _WETH9, address _binFungiblePositionManager) BaseMigrator(_WETH9) {
binFungiblePositionManager = IBinFungiblePositionManager(_binFungiblePositionManager);
}

function migrateFromV2(
V2PoolParams calldata v2PoolParams,
V4BinPoolParams calldata v4MintParams,
uint256 extraAmount0,
uint256 extraAmount1
) external payable override {
(uint256 amount0Received, uint256 amount1Received) = withdrawLiquidityFromV2(v2PoolParams);

/// @notice if user mannually specify the price range, they might need to send extra token
batchAndNormalizeTokens(
v4MintParams.poolKey.currency0, v4MintParams.poolKey.currency1, extraAmount0, extraAmount1
);

uint256 amount0Input = amount0Received + extraAmount0;
uint256 amount1Input = amount1Received + extraAmount1;
IBinFungiblePositionManager.AddLiquidityParams memory addLiquidityParams = IBinFungiblePositionManager
.AddLiquidityParams({
poolKey: v4MintParams.poolKey,
amount0: SafeCast.toUint128(amount0Input),
amount1: SafeCast.toUint128(amount1Input),
amount0Min: v4MintParams.amount0Min,
amount1Min: v4MintParams.amount1Min,
activeIdDesired: v4MintParams.activeIdDesired,
idSlippage: v4MintParams.idSlippage,
deltaIds: v4MintParams.deltaIds,
distributionX: v4MintParams.distributionX,
distributionY: v4MintParams.distributionY,
to: v4MintParams.to,
deadline: v4MintParams.deadline
});
(uint256 amount0Consumed, uint256 amount1Consumed,,) = _addLiquidityToTargetPool(addLiquidityParams);

// refund if necessary, ETH is supported by CurrencyLib
unchecked {
if (amount0Input > amount0Consumed) {
v4MintParams.poolKey.currency0.transfer(v4MintParams.to, amount0Input - amount0Consumed);
}
if (amount1Input > amount1Consumed) {
v4MintParams.poolKey.currency1.transfer(v4MintParams.to, amount1Input - amount1Consumed);
}
}
}

function migrateFromV3(
V3PoolParams calldata v3PoolParams,
V4BinPoolParams calldata v4MintParams,
uint256 extraAmount0,
uint256 extraAmount1
) external payable override {
IV3NonfungiblePositionManager.DecreaseLiquidityParams memory decreaseLiquidityParams =
IV3NonfungiblePositionManager.DecreaseLiquidityParams({
tokenId: v3PoolParams.tokenId,
liquidity: v3PoolParams.liquidity,
amount0Min: v3PoolParams.amount0Min,
amount1Min: v3PoolParams.amount1Min,
deadline: v4MintParams.deadline
});
(uint256 amount0Received, uint256 amount1Received) =
withdrawLiquidityFromV3(v3PoolParams.nfp, decreaseLiquidityParams, v3PoolParams.collectFee);

/// @notice if user mannually specify the price range, they need to send extra token
batchAndNormalizeTokens(
v4MintParams.poolKey.currency0, v4MintParams.poolKey.currency1, extraAmount0, extraAmount1
);

uint256 amount0Input = amount0Received + extraAmount0;
uint256 amount1Input = amount1Received + extraAmount1;
IBinFungiblePositionManager.AddLiquidityParams memory addLiquidityParams = IBinFungiblePositionManager
.AddLiquidityParams({
poolKey: v4MintParams.poolKey,
amount0: SafeCast.toUint128(amount0Input),
amount1: SafeCast.toUint128(amount1Input),
amount0Min: v4MintParams.amount0Min,
amount1Min: v4MintParams.amount1Min,
activeIdDesired: v4MintParams.activeIdDesired,
idSlippage: v4MintParams.idSlippage,
deltaIds: v4MintParams.deltaIds,
distributionX: v4MintParams.distributionX,
distributionY: v4MintParams.distributionY,
to: v4MintParams.to,
deadline: v4MintParams.deadline
});
(uint256 amount0Consumed, uint256 amount1Consumed,,) = _addLiquidityToTargetPool(addLiquidityParams);

// refund if necessary, ETH is supported by CurrencyLib
unchecked {
if (amount0Input > amount0Consumed) {
v4MintParams.poolKey.currency0.transfer(v4MintParams.to, amount0Input - amount0Consumed);
}
if (amount1Input > amount1Consumed) {
v4MintParams.poolKey.currency1.transfer(v4MintParams.to, amount1Input - amount1Consumed);
}
}
}

function _addLiquidityToTargetPool(IBinFungiblePositionManager.AddLiquidityParams memory params)
internal
returns (uint128 amount0, uint128 amount1, uint256[] memory tokenIds, uint256[] memory liquidityMinted)
{
/// @dev currency1 cant be NATIVE
bool nativePair = params.poolKey.currency0.isNative();
if (!nativePair) {
approveMaxIfNeeded(params.poolKey.currency0, address(binFungiblePositionManager), params.amount0);
}
approveMaxIfNeeded(params.poolKey.currency1, address(binFungiblePositionManager), params.amount1);

(amount0, amount1, tokenIds, liquidityMinted) =
binFungiblePositionManager.addLiquidity{value: nativePair ? params.amount0 : 0}(params);
if (nativePair) {
binFungiblePositionManager.refundETH();
}
}

/// @notice Planned to be batched with migration operations through multicall to save gas
function initialize(PoolKey memory poolKey, uint24 activeId, bytes calldata hookData) external payable override {
return binFungiblePositionManager.initialize(poolKey, activeId, hookData);
}

receive() external payable {
if (msg.sender != address(binFungiblePositionManager) && msg.sender != WETH9) {
revert INVALID_ETHER_SENDER();
}
}
}
4 changes: 3 additions & 1 deletion src/pool-bin/interfaces/IBinFungiblePositionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import {BalanceDelta} from "pancake-v4-core/src/types/BalanceDelta.sol";
import {PoolKey} from "pancake-v4-core/src/types/PoolKey.sol";
import {PoolId} from "pancake-v4-core/src/types/PoolId.sol";
import {IBinFungibleToken} from "./IBinFungibleToken.sol";
import {IPeripheryPayments} from "../../interfaces/IPeripheryPayments.sol";
import {IMulticall} from "../../interfaces/IMulticall.sol";

interface IBinFungiblePositionManager is IBinFungibleToken {
interface IBinFungiblePositionManager is IBinFungibleToken, IPeripheryPayments, IMulticall {
error OnlyVaultCaller();
error IdOverflows(int256);
error IdDesiredOverflows(uint24);
Expand Down
49 changes: 49 additions & 0 deletions src/pool-bin/interfaces/IBinMigrator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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";
import {IBaseMigrator} from "../../interfaces/IBaseMigrator.sol";
import {IV3NonfungiblePositionManager} from "../../interfaces/external/IV3NonfungiblePositionManager.sol";

interface IBinMigrator is IBaseMigrator {
/// @notice same fields as IBinFungiblePositionManager.AddLiquidityParams
/// except amount0/amount1 which will be calculated by migrator
struct V4BinPoolParams {
PoolKey poolKey;
// uint128 amount0;
// uint128 amount1;
uint128 amount0Min;
uint128 amount1Min;
uint256 activeIdDesired;
uint256 idSlippage;
int256[] deltaIds;
uint256[] distributionX;
uint256[] distributionY;
address to;
uint256 deadline;
}

function migrateFromV2(
V2PoolParams calldata v2PoolParams,
V4BinPoolParams calldata v4MintParams,
// extra funds to be added
uint256 extraAmount0,
uint256 extraAmount1
) external payable;

function migrateFromV3(
V3PoolParams calldata v3PoolParams,
V4BinPoolParams calldata v4MintParams,
// extra funds to be added
uint256 extraAmount0,
uint256 extraAmount1
) external payable;

/// @notice Initialize a new pool
/// @dev Call this when the pool does not exist and is not initialized
/// @param poolKey The pool key
/// @param activeId The active id of the pool
/// @param hookData Hook data for the pool
function initialize(PoolKey memory poolKey, uint24 activeId, bytes calldata hookData) external payable;
}

0 comments on commit 4bb0e82

Please sign in to comment.