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

feat(ur-sdk): allow migrating to eth position #279

Merged
merged 30 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d45c657
do not allow migrating to weth position
dianakocsis Jan 28, 2025
681e04e
migrate weth to eth by unwrapping
dianakocsis Jan 28, 2025
78fafe0
yarn lint --fix
dianakocsis Jan 28, 2025
7ca1005
use native invariant
dianakocsis Jan 31, 2025
215c555
clearer
dianakocsis Jan 31, 2025
64553d8
sweep weth for good measure when migrating
dianakocsis Jan 31, 2025
057c380
comment
dianakocsis Jan 31, 2025
f157d01
revert useNative to NativeCurrency type
dianakocsis Feb 3, 2025
7b19305
native currency on chain
dianakocsis Feb 3, 2025
076dac7
allow migrating weth position to a weth position
dianakocsis Feb 3, 2025
82bcc91
separate logic
dianakocsis Feb 3, 2025
2a26fa4
native not set constant
dianakocsis Feb 3, 2025
eb42094
suggestion
dianakocsis Feb 4, 2025
72743df
fix tests
dianakocsis Feb 4, 2025
6dd7dfd
const
dianakocsis Feb 4, 2025
50e38c5
Merge branch 'main' into weth-eth
dianakocsis Feb 4, 2025
a9dc080
add erc20-erc20 tests
dianakocsis Feb 4, 2025
3b7dba5
Merge branch 'main' into weth-eth
dianakocsis Feb 4, 2025
c6f8c65
update v4-sdk version
dianakocsis Feb 4, 2025
f479b97
yarn dedupe
dianakocsis Feb 4, 2025
751b27f
add more assertions
dianakocsis Feb 4, 2025
4ab097d
more assertions
dianakocsis Feb 4, 2025
1ac322f
Merge branch 'main' into weth-eth
dianakocsis Feb 4, 2025
081f115
remove file
dianakocsis Feb 4, 2025
cabd18f
re-add vscode
dianakocsis Feb 4, 2025
7380815
settings.json
dianakocsis Feb 4, 2025
244bee4
comment that owner of v3 nft should be receiver of v4 nft
dianakocsis Feb 4, 2025
bc508a3
Merge branch 'main' into weth-eth
dianakocsis Feb 4, 2025
1b12445
update v4-sdk version
dianakocsis Feb 4, 2025
d9dec9d
recipient balance
dianakocsis Feb 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdks/universal-router-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"@uniswap/v2-sdk": "^4.13.0",
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-sdk": "^3.24.0",
"@uniswap/v4-sdk": "^1.18.1",
"@uniswap/v4-sdk": "^1.19.2",
"bignumber.js": "^9.0.2",
"ethers": "^5.7.0"
},
Expand Down
19 changes: 15 additions & 4 deletions sdks/universal-router-sdk/src/swapRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,25 @@ export abstract class SwapRouter {
positionManagerOverride?: string
): MethodParameters {
const v4Pool: V4Pool = options.outputPosition.pool
const token0 = options.inputPosition.pool.token0
const token1 = options.inputPosition.pool.token1
const v3Token0 = options.inputPosition.pool.token0
const v3Token1 = options.inputPosition.pool.token1
const v4PositionManagerAddress =
positionManagerOverride ?? CHAIN_TO_ADDRESSES_MAP[v4Pool.chainId as SupportedChainsType].v4PositionManagerAddress

// owner of the v3 nft must be the receiver of the v4 nft

// validate the parameters
invariant(token0 === v4Pool.token0, 'TOKEN0_MISMATCH')
invariant(token1 === v4Pool.token1, 'TOKEN1_MISMATCH')
if (v4Pool.currency0.isNative) {
invariant(
(v4Pool.currency0.wrapped.equals(v3Token0) && v4Pool.currency1.equals(v3Token1)) ||
(v4Pool.currency0.wrapped.equals(v3Token1) && v4Pool.currency1.equals(v3Token0)),
'TOKEN_MISMATCH'
)
} else {
invariant(v3Token0 === v4Pool.token0, 'TOKEN0_MISMATCH')
invariant(v3Token1 === v4Pool.token1, 'TOKEN1_MISMATCH')
}

invariant(
options.v3RemoveLiquidityOptions.liquidityPercentage.equalTo(new Percent(100, 100)),
'FULL_REMOVAL_REQUIRED'
Expand Down
223 changes: 217 additions & 6 deletions sdks/universal-router-sdk/test/forge/MigratorCallParameters.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ contract MigratorCallParametersTest is Test, Interop, DeployRouter {
vm.deal(from, BALANCE);
}

function test_migrate_withoutPermit() public {
MethodParameters memory params = readFixture(json, "._MIGRATE_WITHOUT_PERMIT");
function test_migrate_toEth_withoutPermit() public {
MethodParameters memory params = readFixture(json, "._MIGRATE_TO_ETH_WITHOUT_PERMIT");

// add the position to v3 so we have something to migrate
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0);
Expand All @@ -42,6 +42,16 @@ contract MigratorCallParametersTest is Test, Interop, DeployRouter {
vm.prank(from);
INonfungiblePositionManager(V3_POSITION_MANAGER).setApprovalForAll(MAINNET_ROUTER, true);

// pool manager balance before
uint256 ethBalanceBefore = address(poolManager).balance;
uint256 usdcBalanceBefore = USDC.balanceOf(address(poolManager));
uint256 wethBalanceBefore = WETH.balanceOf(address(poolManager));

// recipient balance before
uint256 recipientBalanceBefore = address(RECIPIENT).balance;
uint256 recipientUSDCBalanceBefore = USDC.balanceOf(RECIPIENT);
uint256 recipientWETHBalanceBefore = WETH.balanceOf(RECIPIENT);

assertEq(params.value, 0);
vm.prank(from);
(bool success,) = address(router).call(params.data);
Expand All @@ -50,23 +60,49 @@ contract MigratorCallParametersTest is Test, Interop, DeployRouter {
// all funds were swept out of contracts
assertEq(USDC.balanceOf(MAINNET_ROUTER), 0);
assertEq(WETH.balanceOf(MAINNET_ROUTER), 0);
assertEq(address(MAINNET_ROUTER).balance, 0);
assertEq(USDC.balanceOf(address(v4PositionManager)), 0);
assertEq(WETH.balanceOf(address(v4PositionManager)), 0);
assertEq(address(v4PositionManager).balance, 0);

// pool manager balance after, eth and usdc deposited
assertGt(address(poolManager).balance, ethBalanceBefore);
assertGt(USDC.balanceOf(address(poolManager)), usdcBalanceBefore);
assertEq(WETH.balanceOf(address(poolManager)), wethBalanceBefore);

// recipient balance after
assertEq(address(RECIPIENT).balance, recipientBalanceBefore);
assertGe(USDC.balanceOf(RECIPIENT), recipientUSDCBalanceBefore);
assertGe(WETH.balanceOf(RECIPIENT), recipientWETHBalanceBefore);

// old position burned, new position minted
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0, "V3 NOT BURNT");
assertEq(v4PositionManager.balanceOf(RECIPIENT), 1, "V4 NOT MINTED");
}

function test_migrate_withPermit() public {
MethodParameters memory params = readFixture(json, "._MIGRATE_WITH_PERMIT");
function test_migrate_toErc20_withoutPermit() public {
MethodParameters memory params = readFixture(json, "._MIGRATE_TO_ERC20_WITHOUT_PERMIT");

// add the position to v3 so we have something to migrate
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0);
// USDC < WETH
mintV3Position(address(USDC), address(WETH), 3000, 2500e6, 1e18);
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 1);

// approve the UniversalRouter to access the position (instead of permit)
vm.prank(from);
INonfungiblePositionManager(V3_POSITION_MANAGER).setApprovalForAll(MAINNET_ROUTER, true);

// pool manager balance before
uint256 ethBalanceBefore = address(poolManager).balance;
uint256 usdcBalanceBefore = USDC.balanceOf(address(poolManager));
uint256 wethBalanceBefore = WETH.balanceOf(address(poolManager));

// recipient balance before
uint256 recipientBalanceBefore = address(RECIPIENT).balance;
uint256 recipientUSDCBalanceBefore = USDC.balanceOf(RECIPIENT);
uint256 recipientWETHBalanceBefore = WETH.balanceOf(RECIPIENT);

assertEq(params.value, 0);
vm.prank(from);
(bool success,) = address(router).call(params.data);
Expand All @@ -75,23 +111,45 @@ contract MigratorCallParametersTest is Test, Interop, DeployRouter {
// all funds were swept out of contracts
assertEq(USDC.balanceOf(MAINNET_ROUTER), 0);
assertEq(WETH.balanceOf(MAINNET_ROUTER), 0);
assertEq(address(MAINNET_ROUTER).balance, 0);
assertEq(USDC.balanceOf(address(v4PositionManager)), 0);
assertEq(WETH.balanceOf(address(v4PositionManager)), 0);
assertEq(address(v4PositionManager).balance, 0);

// pool manager balance after, weth and usdc deposited
assertEq(address(poolManager).balance, ethBalanceBefore);
assertGt(USDC.balanceOf(address(poolManager)), usdcBalanceBefore);
assertGt(WETH.balanceOf(address(poolManager)), wethBalanceBefore);

// recipient balance after
assertEq(address(RECIPIENT).balance, recipientBalanceBefore);
assertGe(USDC.balanceOf(RECIPIENT), recipientUSDCBalanceBefore);
assertGe(WETH.balanceOf(RECIPIENT), recipientWETHBalanceBefore);

// old position burned, new position minted
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0, "V3 NOT BURNT");
assertEq(v4PositionManager.balanceOf(RECIPIENT), 1, "V4 NOT MINTED");
}

function test_migrate_withPermitAndPoolInitialize() public {
MethodParameters memory params = readFixture(json, "._MIGRATE_WITH_PERMIT_AND_POOL_INITIALIZE");
function test_migrate_toEth_withPermit() public {
MethodParameters memory params = readFixture(json, "._MIGRATE_TO_ETH_WITH_PERMIT");

// add the position to v3 so we have something to migrate
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0);
// USDC < WETH
mintV3Position(address(USDC), address(WETH), 3000, 2500e6, 1e18);
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 1);

// pool manager balance before
uint256 ethBalanceBefore = address(poolManager).balance;
uint256 usdcBalanceBefore = USDC.balanceOf(address(poolManager));
uint256 wethBalanceBefore = WETH.balanceOf(address(poolManager));

// recipient balance before
uint256 recipientBalanceBefore = address(RECIPIENT).balance;
uint256 recipientUSDCBalanceBefore = USDC.balanceOf(RECIPIENT);
uint256 recipientWETHBalanceBefore = WETH.balanceOf(RECIPIENT);

assertEq(params.value, 0);
vm.prank(from);
(bool success,) = address(router).call(params.data);
Expand All @@ -100,8 +158,161 @@ contract MigratorCallParametersTest is Test, Interop, DeployRouter {
// all funds were swept out of contracts
assertEq(USDC.balanceOf(MAINNET_ROUTER), 0);
assertEq(WETH.balanceOf(MAINNET_ROUTER), 0);
assertEq(address(MAINNET_ROUTER).balance, 0);
assertEq(USDC.balanceOf(address(v4PositionManager)), 0);
assertEq(WETH.balanceOf(address(v4PositionManager)), 0);
assertEq(address(v4PositionManager).balance, 0);

// pool manager balance after, eth and usdc deposited
assertGt(address(poolManager).balance, ethBalanceBefore);
assertGt(USDC.balanceOf(address(poolManager)), usdcBalanceBefore);
assertEq(WETH.balanceOf(address(poolManager)), wethBalanceBefore);

// recipient balance after
assertEq(address(RECIPIENT).balance, recipientBalanceBefore);
assertGe(USDC.balanceOf(RECIPIENT), recipientUSDCBalanceBefore);
assertGe(WETH.balanceOf(RECIPIENT), recipientWETHBalanceBefore);

// old position burned, new position minted
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0, "V3 NOT BURNT");
assertEq(v4PositionManager.balanceOf(RECIPIENT), 1, "V4 NOT MINTED");
}

function test_migrate_toErc20_withPermit() public {
MethodParameters memory params = readFixture(json, "._MIGRATE_TO_ERC20_WITH_PERMIT");

// add the position to v3 so we have something to migrate
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0);
// USDC < WETH
mintV3Position(address(USDC), address(WETH), 3000, 2500e6, 1e18);
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 1);

// pool manager balance before
uint256 ethBalanceBefore = address(poolManager).balance;
uint256 usdcBalanceBefore = USDC.balanceOf(address(poolManager));
uint256 wethBalanceBefore = WETH.balanceOf(address(poolManager));

// recipient balance before
uint256 recipientBalanceBefore = address(RECIPIENT).balance;
uint256 recipientUSDCBalanceBefore = USDC.balanceOf(RECIPIENT);
uint256 recipientWETHBalanceBefore = WETH.balanceOf(RECIPIENT);

assertEq(params.value, 0);
vm.prank(from);
(bool success,) = address(router).call(params.data);
require(success, "call failed");

// all funds were swept out of contracts
assertEq(USDC.balanceOf(MAINNET_ROUTER), 0);
assertEq(WETH.balanceOf(MAINNET_ROUTER), 0);
assertEq(address(MAINNET_ROUTER).balance, 0);
assertEq(USDC.balanceOf(address(v4PositionManager)), 0);
assertEq(WETH.balanceOf(address(v4PositionManager)), 0);
assertEq(address(v4PositionManager).balance, 0);

// pool manager balance after, weth and usdc deposited
assertEq(address(poolManager).balance, ethBalanceBefore);
assertGt(USDC.balanceOf(address(poolManager)), usdcBalanceBefore);
assertGt(WETH.balanceOf(address(poolManager)), wethBalanceBefore);

// recipient balance after
assertEq(address(RECIPIENT).balance, recipientBalanceBefore);
assertGe(USDC.balanceOf(RECIPIENT), recipientUSDCBalanceBefore);
assertGe(WETH.balanceOf(RECIPIENT), recipientWETHBalanceBefore);

// old position burned, new position minted
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0, "V3 NOT BURNT");
assertEq(v4PositionManager.balanceOf(RECIPIENT), 1, "V4 NOT MINTED");
dianakocsis marked this conversation as resolved.
Show resolved Hide resolved
}

function test_migrate_toEth_withPermitAndPoolInitialize() public {
MethodParameters memory params = readFixture(json, "._MIGRATE_TO_ETH_WITH_PERMIT_AND_POOL_INITIALIZE");

// add the position to v3 so we have something to migrate
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0);
// USDC < WETH
mintV3Position(address(USDC), address(WETH), 3000, 2500e6, 1e18);
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 1);

// pool manager balance before
uint256 ethBalanceBefore = address(poolManager).balance;
uint256 usdcBalanceBefore = USDC.balanceOf(address(poolManager));
uint256 wethBalanceBefore = WETH.balanceOf(address(poolManager));

// recipient balance before
uint256 recipientBalanceBefore = address(RECIPIENT).balance;
uint256 recipientUSDCBalanceBefore = USDC.balanceOf(RECIPIENT);
uint256 recipientWETHBalanceBefore = WETH.balanceOf(RECIPIENT);

assertEq(params.value, 0);
vm.prank(from);
(bool success,) = address(router).call(params.data);
require(success, "call failed");

// all funds were swept out of contracts
assertEq(USDC.balanceOf(MAINNET_ROUTER), 0);
assertEq(WETH.balanceOf(MAINNET_ROUTER), 0);
assertEq(address(MAINNET_ROUTER).balance, 0);
assertEq(USDC.balanceOf(address(v4PositionManager)), 0);
assertEq(WETH.balanceOf(address(v4PositionManager)), 0);
assertEq(address(v4PositionManager).balance, 0);

// pool manager balance after, eth and usdc deposited
assertGt(address(poolManager).balance, ethBalanceBefore);
assertGt(USDC.balanceOf(address(poolManager)), usdcBalanceBefore);
assertEq(WETH.balanceOf(address(poolManager)), wethBalanceBefore);

// recipient balance after
assertEq(address(RECIPIENT).balance, recipientBalanceBefore);
assertGe(USDC.balanceOf(RECIPIENT), recipientUSDCBalanceBefore);
assertGe(WETH.balanceOf(RECIPIENT), recipientWETHBalanceBefore);

// old position burned, new position minted
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0, "V3 NOT BURNT");
assertEq(v4PositionManager.balanceOf(RECIPIENT), 1, "V4 NOT MINTED");
}

function test_migrate_toErc20_withPermitAndPoolInitialize() public {
MethodParameters memory params = readFixture(json, "._MIGRATE_TO_ERC20_WITH_PERMIT_AND_POOL_INITIALIZE");

// add the position to v3 so we have something to migrate
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0);
// USDC < WETH
mintV3Position(address(USDC), address(WETH), 3000, 2500e6, 1e18);
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 1);

// pool manager balance before
uint256 ethBalanceBefore = address(poolManager).balance;
uint256 usdcBalanceBefore = USDC.balanceOf(address(poolManager));
uint256 wethBalanceBefore = WETH.balanceOf(address(poolManager));

// recipient balance before
uint256 recipientBalanceBefore = address(RECIPIENT).balance;
uint256 recipientUSDCBalanceBefore = USDC.balanceOf(RECIPIENT);
uint256 recipientWETHBalanceBefore = WETH.balanceOf(RECIPIENT);

assertEq(params.value, 0);
vm.prank(from);
(bool success,) = address(router).call(params.data);
require(success, "call failed");

// all funds were swept out of contracts
assertEq(USDC.balanceOf(MAINNET_ROUTER), 0);
assertEq(WETH.balanceOf(MAINNET_ROUTER), 0);
assertEq(address(MAINNET_ROUTER).balance, 0);
assertEq(USDC.balanceOf(address(v4PositionManager)), 0);
assertEq(WETH.balanceOf(address(v4PositionManager)), 0);
assertEq(address(v4PositionManager).balance, 0);

// pool manager balance after, weth and usdc deposited
assertEq(address(poolManager).balance, ethBalanceBefore);
assertGt(USDC.balanceOf(address(poolManager)), usdcBalanceBefore);
assertGt(WETH.balanceOf(address(poolManager)), wethBalanceBefore);

// recipient balance after
assertEq(address(RECIPIENT).balance, recipientBalanceBefore);
assertGe(USDC.balanceOf(RECIPIENT), recipientUSDCBalanceBefore);
assertGe(WETH.balanceOf(RECIPIENT), recipientWETHBalanceBefore);

// old position burned, new position minted
assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0, "V3 NOT BURNT");
Expand Down
Loading
Loading