Skip to content
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

Merged
merged 15 commits into from
Jul 18, 2024
Merged

V4 CLMigrator #51

merged 15 commits into from
Jul 18, 2024

Conversation

chefburger
Copy link
Collaborator

@chefburger chefburger commented Jul 2, 2024

The new migrator for cl-pool, support the following source pools:

  • pancakeswap V2 styled pool
  • pancakeswap V3 styled pool
    And it will withdraw liquidity from source pool and pipe it to correspond v4 cl-pool.

Features

  1. No internal swap is involved
  2. Support user add extra funds if desired
  3. WETH source pool (ETH not support for v2 and v3) will be automatically piped into ETH pool in v4 and refund in the form of ETH

@chefburger chefburger marked this pull request as draft July 2, 2024 10:22
@chefburger chefburger force-pushed the feat/migrator branch 2 times, most recently from 9505919 to 326ebd8 Compare July 3, 2024 02:32
src/pool-cl/CLMigrator.sol Outdated Show resolved Hide resolved
src/base/BaseMigrator.sol Outdated Show resolved Hide resolved
src/pool-cl/CLMigrator.sol Outdated Show resolved Hide resolved
src/pool-cl/CLMigrator.sol Outdated Show resolved Hide resolved
@chefburger chefburger force-pushed the feat/migrator branch 3 times, most recently from f5ac980 to b5cfd04 Compare July 3, 2024 12:23
uint256 amount0Min;
uint256 amount1Min;
// decide whether to collect fee
bool collectFee;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest we can remove the collectFee flag.
Since we need to collect the liquidity amount , so it is better to collect the fee together.
Because fee number is always smaller than the liquidity amount.
Or we can keep this in the code, but FE use true as default.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per internal discussion: will keep it to give flexibility to user


function migrateFromV2(
V2PoolParams calldata v2PoolParams,
INonfungiblePositionManager.MintParams calldata v4MintParams,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we updated the v4MintParams.amount0Desired and amount1Desired based on the real withdraw amount from v2 or v3 ?
Because pool state is constantly changing, so maybe the withdraw amount is different from the number FE calculated.
v4MintParams.amount0Desired = amount0Received + extraAmount0;
v4MintParams.amount1Desired = amount1Received + extraAmount1;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice point. I can update to use the actual amount received. Also, do u think we should have a simple check here to see if the expected amount and actual amount way off

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, we can check .
but if so , we need a rule to define the way off

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per internal discussion: The fields amount0Min and amount1Min in v3#decreaseLiquidity should be sufficient if price’s changed a log. We will add same checking rule for migrateFromV2 case

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated, same as the following one.. can take a look

v4MintParams.poolKey.currency0, v4MintParams.poolKey.currency1, extraAmount0, extraAmount1
);

(,, uint256 amount0Consumed, uint256 amount1Consumed) = _addLiquidityToTargetPool(v4MintParams);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we updated the v4MintParams.amount0Desired and amount1Desired based on the real withdraw amount from v2 or v3 ?
Same as the comment above.

approveMax(params.poolKey.currency0, address(nonfungiblePositionManager));
}
approveMax(params.poolKey.currency1, address(nonfungiblePositionManager));

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.
I guess separate call is cheaper 🐶

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, i've initiated call separately

}

// even if user sends native ETH, we still need to unwrap the part from source pool
if (address(token0) == NATIVE) {
Copy link
Collaborator

@ChefMist ChefMist Jul 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(minor, non blocker) can consider using this currency0.isNative()

applies to other places where we check eg. address(token) == NATIVE

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated, thx for reminding

// TODO: compare the gas cost of multicall and two separate call
bytes[] memory data = new bytes[](2);
data[0] = abi.encodeWithSelector(nonfungiblePositionManager.mint.selector, params);
data[1] = abi.encodeWithSelector(nonfungiblePositionManager.refundETH.selector);
Copy link
Collaborator

@ChefMist ChefMist Jul 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(optional) -- could it be more gas efficient if we only add refundEth for nativePair case?

eg.

if (nativePair) {
  bytes[] memory data = new bytes[](2);
} else {
  bytes[] memory data = new bytes[](1);

}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do the gas comparison when start writing test

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated to avoid multi call which consumes more gas

Copy link
Collaborator

@ChefMist ChefMist left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no other feedback in general, structure looks clean and easy to understand for me

@chefburger chefburger marked this pull request as ready for review July 8, 2024 06:29
@chefburger chefburger changed the title V4 Migrator V4 CLMigrator Jul 8, 2024
if (amount1Received > amount1Consumed) {
v4MintParams.poolKey.currency1.transfer(v4MintParams.recipient, amount1Received - amount1Consumed);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add event for migrateFromV2 and migrateFromV3 ?
Not sure whether FE need events to make statistics about migration.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given our v3 doesn't have the event and so far seems fine, i think we can leave it without events for now. (though open if we want to add)

});
(,, uint256 amount0Consumed, uint256 amount1Consumed) = _addLiquidityToTargetPool(mintParams);

// refund if necessary, ETH is supported by CurrencyLib
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this use the (amount0Received + extraAmount0) > amount0Consumed ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice finding! fixed


function approveMax(Currency currency, address to) internal {
ERC20 token = ERC20(Currency.unwrap(currency));
if (token.allowance(address(this), to) == type(uint256).max) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About this check condition , it will only apply for the new ERC20 token.
For old ERC20 tokens which deployed for a long time ago, the approve function did not have the "currentAllowance != type(uint256).max" checker , so it will always reduce the allowance , so for the old tokens will be approved every time when this token allowance was used before.
For example , you can check the Cake Token , no type(uint256).max when spent allowance.
https://bscscan.com/token/0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82#code

so you need to change this condition , just like this :
token.allowance(address(this), to) > 0

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thx for reminding, can take a look again

/// @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
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest we can check extraAmount0 and extraAmount1 at the beginning .
if(extraAmount0 == 0 && extraAmount1 == 0) {return;}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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 IWETH9(WETH9).withdraw(ERC20(WETH9).balanceOf(address(this))); to a separate function ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point.
you can skip this suggestion.

Or add extra logic about the WETH withdraw before return.
Separate function is also a good solution.


(tokenId, liquidity, amount0Consumed, amount1Consumed) =
nonfungiblePositionManager.mint{value: nativePair ? params.amount0Desired : 0}(params);
if (nativePair) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

about this , i suggest we can add more condition.
if(nativePair && amount0Consumed < params.amount0Desired)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thx for reminding, updated

}

// even if user sends native ETH, we still need to unwrap the part from source pool
if (currency0.isNative()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we check the ERC20(WETH9).balanceOf(address(this) > 0 ?
for example , if users try to migrate one v3 position which is out of range , so maybe it will get 0 amount for WETH , in this case , we still will go to call IWETH9(WETH9).withdraw , it will cost extra gas.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, updated

uint256 extraAmount0,
uint256 extraAmount1
) external payable override {
IV3NonfungiblePositionManager.DecreaseLiquidityParams memory decreaseLiquidityParams =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marked

@@ -45,6 +41,11 @@ contract BaseMigrator is IBaseMigrator, PeripheryImmutableState, Multicall, Self
IV3NonfungiblePositionManager.DecreaseLiquidityParams memory decreaseLiquidityParams,
bool collectFee
) internal returns (uint256 amount0Received, uint256 amount1Received) {
///@dev make sure the caller is the owner of the token
/// otherwise once the token is approved to migrator, anyone can still money through this function
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo : still -> steal

@chefburger chefburger mentioned this pull request Jul 9, 2024
ChefSnoopy
ChefSnoopy previously approved these changes Jul 15, 2024
}
}

/// @dev receive extra tokens from user if necessary and normalize all the WETH to native ETH
Copy link
Collaborator

@ChefMist ChefMist Jul 16, 2024

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 integrating

// @param: extraAmount0: if pool token0 is ETH and msg.value == 0, WETH will be taken from sender. Otherwise if pool token0 is ETH and msg.value !=0, method will assume user have sent extraAmount0 in msg.value```

Copy link
Collaborator Author

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 😂

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

ChefMist
ChefMist previously approved these changes Jul 16, 2024
* feat: impl binPool migrator

* refactor: restructure & renaming accordingly

* test: add tests cases for binPool migrator

* chore: renaming as well to align with clMigrator

* fix: add check to prevent token mismatch between source and target pool

* feat: added refundETH function and necessary comments

* optimization: avoid duplicate external call when query v2/v3 pool info

* feat: support selfPermitForERC721 (#62)

* feat: support selfPermitForERC721

* docs: added comments suggesting users to use selfPermitERC721IfNecessary

* test: added tests to prevent ppl from removing payable keyword from external functions
@chefburger chefburger dismissed stale reviews from ChefMist and ChefSnoopy via 3503133 July 18, 2024 07:17
@chefburger chefburger merged commit bcbacc9 into main Jul 18, 2024
1 check passed
@chefburger chefburger deleted the feat/migrator branch July 18, 2024 07:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants