-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
V4 CLMigrator #51
V4 CLMigrator #51
Changes from 9 commits
fbb0d1c
a1825df
9c99091
e2cd758
1553d15
54d6049
a7773d4
3dc30a4
663c018
ca5fd6f
a548dfc
dd3a7ea
8ed8199
3503133
6cb8f80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
735666 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
692529 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
736981 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
790813 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
750228 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
792171 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
735678 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
692541 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
736978 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
788818 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
748233 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
790173 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
// Copyright (C) 2024 PancakeSwap | ||
pragma solidity ^0.8.19; | ||
|
||
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; | ||
import {SafeTransferLib, ERC20} from "solmate/utils/SafeTransferLib.sol"; | ||
import {IPancakePair} from "../interfaces/external/IPancakePair.sol"; | ||
import {IV3NonfungiblePositionManager} from "../interfaces/external/IV3NonfungiblePositionManager.sol"; | ||
import {IWETH9} from "../interfaces/external/IWETH9.sol"; | ||
import {PeripheryImmutableState} from "./PeripheryImmutableState.sol"; | ||
import {Multicall} from "./Multicall.sol"; | ||
import {SelfPermit} from "./SelfPermit.sol"; | ||
import {Currency} from "pancake-v4-core/src/types/Currency.sol"; | ||
import {IBaseMigrator} from "../interfaces/IBaseMigrator.sol"; | ||
|
||
contract BaseMigrator is IBaseMigrator, PeripheryImmutableState, Multicall, SelfPermit { | ||
error INVALID_ETHER_SENDER(); | ||
error INSUFFICIENT_AMOUNTS_RECEIVED(); | ||
|
||
constructor(address _WETH9) PeripheryImmutableState(_WETH9) {} | ||
|
||
function withdrawLiquidityFromV2(V2PoolParams calldata v2PoolParams) | ||
// function withdrawLiquidityFromV2(address pair, uint256 amount, uint256 amount0Min, uint256 amount1Min) | ||
internal | ||
returns (uint256 amount0Received, uint256 amount1Received) | ||
{ | ||
// burn v2 liquidity to this address | ||
IPancakePair(v2PoolParams.pair).transferFrom(msg.sender, v2PoolParams.pair, v2PoolParams.migrateAmount); | ||
(amount0Received, amount1Received) = IPancakePair(v2PoolParams.pair).burn(address(this)); | ||
|
||
// same price slippage check as v3 | ||
if (amount0Received < v2PoolParams.amount0Min || amount1Received < v2PoolParams.amount1Min) { | ||
revert INSUFFICIENT_AMOUNTS_RECEIVED(); | ||
} | ||
|
||
/// @notice the order may mismatch with v4 pool when WETH is invovled | ||
/// the following check makes sure that the output always match the order of v4 pool | ||
if (IPancakePair(v2PoolParams.pair).token1() == WETH9) { | ||
(amount0Received, amount1Received) = (amount1Received, amount0Received); | ||
} | ||
} | ||
|
||
function withdrawLiquidityFromV3( | ||
address nfp, | ||
IV3NonfungiblePositionManager.DecreaseLiquidityParams memory decreaseLiquidityParams, | ||
bool collectFee | ||
) internal returns (uint256 amount0Received, uint256 amount1Received) { | ||
/// @notice decrease liquidity from v3#nfp, make sure migrator has been approved | ||
(amount0Received, amount1Received) = | ||
IV3NonfungiblePositionManager(nfp).decreaseLiquidity(decreaseLiquidityParams); | ||
|
||
IV3NonfungiblePositionManager.CollectParams memory collectParams = IV3NonfungiblePositionManager.CollectParams({ | ||
tokenId: decreaseLiquidityParams.tokenId, | ||
recipient: address(this), | ||
amount0Max: collectFee ? type(uint128).max : SafeCast.toUint128(amount0Received), | ||
amount1Max: collectFee ? type(uint128).max : SafeCast.toUint128(amount1Received) | ||
}); | ||
|
||
(amount0Received, amount1Received) = IV3NonfungiblePositionManager(nfp).collect(collectParams); | ||
|
||
/// @notice the order may mismatch with v4 pool when WETH is invovled | ||
/// the following check makes sure that the output always match the order of v4 pool | ||
(,,, address token1,,,,,,,,) = IV3NonfungiblePositionManager(nfp).positions(decreaseLiquidityParams.tokenId); | ||
if (token1 == WETH9) { | ||
(amount0Received, amount1Received) = (amount1Received, amount0Received); | ||
} | ||
} | ||
|
||
/// @dev receive extra tokens from user if necessary and normalize all the WETH to native ETH | ||
function batchAndNormalizeTokens(Currency currency0, Currency currency1, uint256 extraAmount0, uint256 extraAmount1) | ||
internal | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest we can check extraAmount0 and extraAmount1 at the beginning . There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unwrapping WETH will still be needed because we might receive WETH from source pool. Or do u think i should move There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good point. Or add extra logic about the WETH withdraw before return. |
||
ERC20 token0 = ERC20(Currency.unwrap(currency0)); | ||
ERC20 token1 = ERC20(Currency.unwrap(currency1)); | ||
|
||
if (extraAmount0 > 0) { | ||
if (currency0.isNative() && msg.value == 0) { | ||
// we assume that user wants to send WETH | ||
SafeTransferLib.safeTransferFrom(ERC20(WETH9), msg.sender, address(this), extraAmount0); | ||
} else if (!currency0.isNative()) { | ||
SafeTransferLib.safeTransferFrom(token0, msg.sender, address(this), extraAmount0); | ||
} | ||
} | ||
|
||
/// @dev token1 cant be NATIVE | ||
if (extraAmount1 > 0) { | ||
SafeTransferLib.safeTransferFrom(token1, msg.sender, address(this), extraAmount1); | ||
} | ||
|
||
if (extraAmount0 != 0 || extraAmount1 != 0) { | ||
emit MoreFundsAdded(address(token0), address(token1), extraAmount0, extraAmount1); | ||
} | ||
|
||
// even if user sends native ETH, we still need to unwrap the part from source pool | ||
if (currency0.isNative()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we check the ERC20(WETH9).balanceOf(address(this) > 0 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree, updated |
||
uint256 wethBalance = ERC20(WETH9).balanceOf(address(this)); | ||
if (wethBalance > 0) IWETH9(WETH9).withdraw(wethBalance); | ||
} | ||
} | ||
|
||
function approveMaxIfNeeded(Currency currency, address to, uint256 amount) internal { | ||
ERC20 token = ERC20(Currency.unwrap(currency)); | ||
if (token.allowance(address(this), to) >= amount) { | ||
return; | ||
} | ||
SafeTransferLib.safeApprove(token, to, type(uint256).max); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// 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 {IPeripheryImmutableState} from "./IPeripheryImmutableState.sol"; | ||
import {IMulticall} from "./IMulticall.sol"; | ||
import {ISelfPermit} from "./ISelfPermit.sol"; | ||
|
||
interface IBaseMigrator is IPeripheryImmutableState, IMulticall, ISelfPermit { | ||
event MoreFundsAdded(address currency0, address currency1, uint256 extraAmount0, uint256 extraAmount1); | ||
|
||
struct V2PoolParams { | ||
// the PancakeSwap v2-compatible pair | ||
address pair; | ||
// the amount of v2 lp token to be withdrawn | ||
uint256 migrateAmount; | ||
// the amount of token0 and token1 to be received after burning must be no less than these | ||
uint256 amount0Min; | ||
uint256 amount1Min; | ||
} | ||
|
||
struct V3PoolParams { | ||
// the PancakeSwap v3-compatible NFP | ||
address nfp; | ||
uint256 tokenId; | ||
uint128 liquidity; | ||
uint256 amount0Min; | ||
uint256 amount1Min; | ||
// decide whether to collect fee | ||
bool collectFee; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest we can remove the collectFee flag. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess this works as a checkbox at frontend page. Will double confirm tho. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per internal discussion: will keep it to give flexibility to user |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity >=0.5.0; | ||
|
||
/// @notice Copying from PancakeSwap V2 Pair | ||
/// https://github.com/pancakeswap/pancake-swap-core-v2/blob/master/contracts/interfaces/IPancakePair.sol | ||
interface IPancakePair { | ||
event Approval(address indexed owner, address indexed spender, uint256 value); | ||
event Transfer(address indexed from, address indexed to, uint256 value); | ||
|
||
function name() external pure returns (string memory); | ||
function symbol() external pure returns (string memory); | ||
function decimals() external pure returns (uint8); | ||
function totalSupply() external view returns (uint256); | ||
function balanceOf(address owner) external view returns (uint256); | ||
function allowance(address owner, address spender) external view returns (uint256); | ||
|
||
function approve(address spender, uint256 value) external returns (bool); | ||
function transfer(address to, uint256 value) external returns (bool); | ||
function transferFrom(address from, address to, uint256 value) external returns (bool); | ||
|
||
function DOMAIN_SEPARATOR() external view returns (bytes32); | ||
function PERMIT_TYPEHASH() external pure returns (bytes32); | ||
function nonces(address owner) external view returns (uint256); | ||
|
||
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) | ||
external; | ||
|
||
event Mint(address indexed sender, uint256 amount0, uint256 amount1); | ||
event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); | ||
event Swap( | ||
address indexed sender, | ||
uint256 amount0In, | ||
uint256 amount1In, | ||
uint256 amount0Out, | ||
uint256 amount1Out, | ||
address indexed to | ||
); | ||
event Sync(uint112 reserve0, uint112 reserve1); | ||
|
||
function MINIMUM_LIQUIDITY() external pure returns (uint256); | ||
function factory() external view returns (address); | ||
function token0() external view returns (address); | ||
function token1() external view returns (address); | ||
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); | ||
function price0CumulativeLast() external view returns (uint256); | ||
function price1CumulativeLast() external view returns (uint256); | ||
function kLast() external view returns (uint256); | ||
|
||
function mint(address to) external returns (uint256 liquidity); | ||
function burn(address to) external returns (uint256 amount0, uint256 amount1); | ||
function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external; | ||
function skim(address to) external; | ||
function sync() external; | ||
|
||
function initialize(address, address) external; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(not blocker)
could you add this somewhere? maybe in
ICLMigrator
for those core migrateFromV2 and migrateFromV3 method? feel like this might be good to flag out to external dev who are integratingThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will update this right before i merge all 3 PRs ( avoid annoying rebasing 😂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done