diff --git a/foundry/test/TychoRouterIntegration.t.sol b/foundry/test/TychoRouterIntegration.t.sol deleted file mode 100644 index c1ee318..0000000 --- a/foundry/test/TychoRouterIntegration.t.sol +++ /dev/null @@ -1,315 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.26; - -import "./TychoRouterTestSetup.sol"; - -contract TychoRouterTestIntegration is TychoRouterTestSetup { - function testSplitSwapSingleWithoutPermit2Integration() public { - // Tests swapping WETH -> DAI on a USV2 pool without permit2 - deal(WETH_ADDR, ALICE, 1 ether); - vm.startPrank(ALICE); - IERC20(WETH_ADDR).approve(address(tychoRouterAddr), 1 ether); - uint256 balanceBefore = IERC20(DAI_ADDR).balanceOf(ALICE); - // Encoded solution generated using `test_split_swap_strategy_encoder_no_permit2` - (bool success,) = tychoRouterAddr.call( - hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae37400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000059005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000100000000000000" - ); - - vm.stopPrank(); - uint256 balanceAfter = IERC20(DAI_ADDR).balanceOf(ALICE); - assertTrue(success, "Call Failed"); - assertEq(balanceAfter - balanceBefore, 2659881924818443699787); - } - - function testSplitUSV4Integration() public { - // Test created with calldata from our router encoder. - - // Performs a sequential swap from USDC to PEPE though ETH using two - // consecutive USV4 pools - // - // USDC ──(USV4)──> ETH ───(USV4)──> PEPE - // - deal(USDC_ADDR, ALICE, 1 ether); - uint256 balanceBefore = IERC20(PEPE_ADDR).balanceOf(ALICE); - - // Approve permit2 - vm.startPrank(ALICE); - IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); - // Encoded solution generated using `test_split_encoding_strategy_usv4` - (bool success,) = tychoRouterAddr.call( - hex"7c553846000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000006826193a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067fe9342000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041c96ca498573c3c283e270700101c963405003b631ce90988c3690248b5cab66a70a1cb074afa6acf671300c917b8d6ac0149ae538c95a67a844175bfa9ae7d811b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d008b0001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d23119330004cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f400000000000000000000000000000000000000" - ); - - vm.stopPrank(); - - uint256 balanceAfter = IERC20(PEPE_ADDR).balanceOf(ALICE); - - assertTrue(success, "Call Failed"); - assertEq(balanceAfter - balanceBefore, 97191013220606467325121599); - } - - function testSplitUSV4IntegrationInputETH() public { - // Test created with calldata from our router encoder. - - // Performs a single swap from ETH to PEPE without wrapping or unwrapping - // - // ETH ───(USV4)──> PEPE - // - deal(ALICE, 1 ether); - uint256 balanceBefore = IERC20(PEPE_ADDR).balanceOf(ALICE); - - // Encoded solution generated using `test_split_encoding_strategy_usv4_eth_in` - (bool success,) = tychoRouterAddr.call{value: 1 ether}( - hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933000000000000000000000000000000000000000000c87c939ae635f92dc2379c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006826193a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067fe934200000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004197c8f24177a14b631dd3a9226c08a0a376fcaf7894364b5131db4415890fc2c57a48bb5497d36a57a4aa359fd6781788c697f6a85500d7bee3e7a1597f0900b81b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007300710001000000f62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330105cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc26982508145454ce325ddbe47a25d4ec3d23119330061a80001f400000000000000000000000000" - ); - - vm.stopPrank(); - - uint256 balanceAfter = IERC20(PEPE_ADDR).balanceOf(ALICE); - - assertTrue(success, "Call Failed"); - assertEq(balanceAfter - balanceBefore, 242373460199848577067005852); - } - - function testSplitUSV4IntegrationOutputETH() public { - // Test created with calldata from our router encoder. - - // Performs a single swap from USDC to ETH without wrapping or unwrapping - // - // USDC ───(USV4)──> ETH - // - deal(USDC_ADDR, ALICE, 3000_000000); - uint256 balanceBefore = ALICE.balance; - - // Approve permit2 - vm.startPrank(ALICE); - IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); - - // Encoded solution generated using `test_split_encoding_strategy_usv4_eth_out` - (bool success,) = tychoRouterAddr.call( - hex"7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000000000000000000000000000000000006826193a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067fe9342000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041dbb84c68a4293bcf6303ca45327614667c54226086ddcfa2daa9289c1657da9a57268f4d8ceea3c831d43e5a96b1dc54766bc3fda8845d5c7e266981b9d84c651b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007300710001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000004cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c00000000000000000000000000" - ); - - vm.stopPrank(); - - uint256 balanceAfter = ALICE.balance; - - assertTrue(success, "Call Failed"); - console.logUint(balanceAfter - balanceBefore); - assertEq(balanceAfter - balanceBefore, 1117254495486192350); - } - - function testSplitSwapSingleWithWrapIntegration() public { - // Tests swapping WETH -> DAI on a USV2 pool, but ETH is received from the user - // and wrapped before the swap - deal(ALICE, 1 ether); - uint256 balanceBefore = IERC20(DAI_ADDR).balanceOf(ALICE); - - // Approve permit2 - vm.startPrank(ALICE); - // Encoded solution generated using `test_split_swap_strategy_encoder_wrap` - (bool success,) = tychoRouterAddr.call{value: 1 ether}( - hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000068261ff900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067fe9a01000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041eaba97110bc7c3586bf8fa3d3d2c24a3863e84d2e1688689e325e750f860802d745309e3dc818fe029f439c14a52d9a3f48f2873733259c342eb01bdf8ac896b1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000059005700020000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000" - ); - - vm.stopPrank(); - - uint256 balanceAfter = IERC20(DAI_ADDR).balanceOf(ALICE); - - assertTrue(success, "Call Failed"); - assertEq(balanceAfter - balanceBefore, 2659881924818443699787); - } - - function testSplitSwapSingleWithUnwrapIntegration() public { - // Tests swapping DAI -> WETH on a USV2 pool, and WETH is unwrapped to ETH - // before sending back to the user - deal(DAI_ADDR, ALICE, 3000 ether); - uint256 balanceBefore = ALICE.balance; - - // Approve permit2 - vm.startPrank(ALICE); - IERC20(DAI_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); - // Encoded solution generated using `test_split_swap_strategy_encoder_unwrap` - (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000a2a15d09519be000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000a2a15d09519be00000000000000000000000000000000000000000000000000000000000006826193a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067fe93420000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000413c401d4a22c3e0b693b4f9d0807f6107ab6b6d6b716a45978ba175af6e717ae617d4c13b8603db25ee6902b807ee049588ce27030bf3f60833bf26e9561f560c1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000059005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395010200000000000000" - ); - - vm.stopPrank(); - - uint256 balanceAfter = ALICE.balance; - - assertTrue(success, "Call Failed"); - assertEq(balanceAfter - balanceBefore, 1120007305574805922); - } - - function testSplitEkuboIntegration() public { - // Test needs to be run on block 22082754 or later - // notice that the addresses for the tycho router and the executors are different because we are redeploying - vm.rollFork(22082754); - tychoRouter = deployRouter(); - address[] memory executors = deployExecutors(); - vm.startPrank(EXECUTOR_SETTER); - tychoRouter.setExecutors(executors); - vm.stopPrank(); - - deal(ALICE, 1 ether); - uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE); - - // Approve permit2 - vm.startPrank(ALICE); - // Encoded solution generated using `test_split_encoding_strategy_ekubo` - (bool success,) = address(tychoRouter).call{value: 1 ether}( - hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000078007600010000003d7ebc40af7092e3f1c81f2e996cba5cae2090d705cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c0000000000000000000000000000000000000000" - ); - - uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE); - - assertTrue(success, "Call Failed"); - assertGe(balanceAfter - balanceBefore, 26173932); - assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0); - } - - function testSplitSwapIntegration() public { - // Performs a split swap from WETH to USDC though WBTC and DAI using USV2 pools - // - // ┌──(USV2)──> WBTC ───(USV2)──> USDC - // WETH ─┤ - // └──(USV2)──> DAI ───(USV2)──> USDC - deal(WETH_ADDR, ALICE, 1 ether); - uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE); - - // Approve permit2 - vm.startPrank(ALICE); - IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); - // Encoded solution generated using `test_split_swap_strategy_encoder_complex_route` - (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006826193a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067fe9342000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041d137d0776bc16ff9c49bfd3e96103ceb6926654f314489cafcf5a64ab7a9c4f2061ed5ffdef67c33c3c5b78036d28d9eb73da156a0e68d8740235be50e88a3481b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164005700028000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950002005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950002005702030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fae461ca67b15dc8dc81ce7615e0320da1a9ab8d5cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20100005701030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2010000000000000000000000000000000000000000000000000000000000" - ); - - vm.stopPrank(); - - uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE); - - assertTrue(success, "Call Failed"); - assertGe(balanceAfter - balanceBefore, 26173932); - - // All input tokens are transferred to the router at first. Make sure we used - // all of it (and thus our splits are correct). - assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0); - } - - function testSequentialSwapIntegrationPermit2() public { - // Performs a split swap from WETH to USDC though WBTC and DAI using USV2 pools - // - // WETH ──(USV2)──> WBTC ───(USV2)──> USDC - deal(WETH_ADDR, ALICE, 1 ether); - uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE); - - // Approve permit2 - vm.startPrank(ALICE); - IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); - // Encoded solution generated using `test_sequential_swap_strategy_encoder_complex_route` - (bool success,) = tychoRouterAddr.call( - hex"51bcc7b60000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000682714ab00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ff8eb300000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000412fe66c22814eb271e37bb03303bae445eb96aa50fae9680a0ae685ee5795aebf1f5bb7718154c69680bcfc00cc9be525b2b021f57a1bddb4db622139acd425d41b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d940004375dff511095cc5a197a54140a24efef3a416000200525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20105000000000000000000000000000000000000000000000000" - ); - - vm.stopPrank(); - - uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE); - - assertTrue(success, "Call Failed"); - assertEq(balanceAfter - balanceBefore, 2552915143); - assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0); - } - - function testSequentialSwapIntegration() public { - // Performs a split swap from WETH to USDC though WBTC and DAI using USV2 pools - // - // WETH ──(USV2)──> WBTC ───(USV2)──> USDC - deal(WETH_ADDR, ALICE, 1 ether); - uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE); - - // Approve permit2 - vm.startPrank(ALICE); - IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max); - // Encoded solution generated using `test_sequential_swap_strategy_encoder_no_permit2` - (bool success,) = tychoRouterAddr.call( - hex"e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d940004375dff511095cc5a197a54140a24efef3a416000100525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20105000000000000000000000000000000000000000000000000" - ); - - vm.stopPrank(); - - uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE); - - assertTrue(success, "Call Failed"); - assertEq(balanceAfter - balanceBefore, 2552915143); - assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0); - } - - function testCyclicSequentialSwapIntegration() public { - deal(USDC_ADDR, ALICE, 100 * 10 ** 6); - - // Approve permit2 - vm.startPrank(ALICE); - IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); - // Encoded solution generated using `test_cyclic_sequential_swap_split_strategy` - (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f4308e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000682714ab00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ff8eb30000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000417badef4d6a9158869bdd061a492f6ca4eb4e012b9295f5562414f83ccbc37982241b4de1d934b4f9f0d51f792b12019f806953e7a7ba49d6cfe0487458753e871b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400102006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc28ad599c3a0ff1de082011efddc58f1908eb6e6d80000" - ); - - assertTrue(success, "Call Failed"); - assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99889294); - - vm.stopPrank(); - } - - function testSplitInputCyclicSwapIntegration() public { - deal(USDC_ADDR, ALICE, 100 * 10 ** 6); - - // Approve permit2 - vm.startPrank(ALICE); - IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); - // Encoded solution generated using `test_split_input_cyclic_swap` - (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ef619b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006826193a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067fe934200000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004194fc497ac440b520981d23b4713425da21dc1c801e657d218a917b5c51339a660b9a5fe0a346cb0aacc0d67ebf03f8fa3ec9fade437ef1b08ea837b2442931b61b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139006e00019999992e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400102006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80102005701000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2b4e16d0168e52d35cacd2c6185b44281ec28c9dccd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000" - ); - - assertTrue(success, "Call Failed"); - assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99574171); - - vm.stopPrank(); - } - - function testSplitOutputCyclicSwapIntegration() public { - deal(USDC_ADDR, ALICE, 100 * 10 ** 6); - - // Approve permit2 - vm.startPrank(ALICE); - IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); - // Encoded solution generated using `test_split_output_cyclic_swap` - (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005eea514000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006826193a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067fe934200000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004194fc497ac440b520981d23b4713425da21dc1c801e657d218a917b5c51339a660b9a5fe0a346cb0aacc0d67ebf03f8fa3ec9fade437ef1b08ea837b2442931b61b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d013950102006e01009999992e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f4cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc288e6a0c2ddd26feeb64f039a2c41296fcb3f56400000006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc28ad599c3a0ff1de082011efddc58f1908eb6e6d8000000000000000000" - ); - - assertTrue(success, "Call Failed"); - assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99525908); - - vm.stopPrank(); - } - - function testSplitCurveIntegration() public { - deal(UWU_ADDR, ALICE, 1 ether); - - vm.startPrank(ALICE); - IERC20(UWU_ADDR).approve(tychoRouterAddr, type(uint256).max); - // Encoded solution generated using `test_split_encoding_strategy_curve` - (bool success,) = tychoRouterAddr.call( - hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000055c08ca52497e2f1534b59e2917bf524d4765257000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000070006e00010000001d1499e622d69689cdf9004d05ec547d650ff21155c08ca52497e2f1534b59e2917bf524d4765257c02aaa39b223fe8d0a0e5c4f27ead9083c756cc277146b0a1d08b6844376df6d9da99ba7f1b19e710201000103cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000" - ); - - assertTrue(success, "Call Failed"); - assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 4691958787921); - - vm.stopPrank(); - } -} diff --git a/foundry/test/TychoRouterProtocolIntegration.t.sol b/foundry/test/TychoRouterProtocolIntegration.t.sol new file mode 100644 index 0000000..b2710c1 --- /dev/null +++ b/foundry/test/TychoRouterProtocolIntegration.t.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.26; + +import "./TychoRouterTestSetup.sol"; + +contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { + function testSequentialUSV4Integration() public { + // Test created with calldata from our router encoder. + + // Performs a sequential swap from USDC to PEPE though ETH using two + // consecutive USV4 pools + // + // USDC ──(USV4)──> ETH ───(USV4)──> PEPE + // + deal(USDC_ADDR, ALICE, 1 ether); + uint256 balanceBefore = IERC20(PEPE_ADDR).balanceOf(ALICE); + + // Approve permit2 + vm.startPrank(ALICE); + IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); + // Encoded solution generated using `test_sequential_encoding_strategy_usv4` + (bool success,) = tychoRouterAddr.call( + hex"51bcc7b6000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000000000000000682db60700000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000006806300f00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041c5715acd97f16c669ba5b6a15d911e61f9b5a056c1bb4f0576dbf7c1251bddd70ac5e929270186517e593e1c8d1d1ecf5c742576affcd5d64cac409600ad054e1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000880086f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d23119330004cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f4000000000000000000000000000000000000000000000000" + ); + + vm.stopPrank(); + + uint256 balanceAfter = IERC20(PEPE_ADDR).balanceOf(ALICE); + + assertTrue(success, "Call Failed"); + assertEq(balanceAfter - balanceBefore, 97191013220606467325121599); + } + + function testSingleUSV4IntegrationInputETH() public { + // Test created with calldata from our router encoder. + + // Performs a single swap from ETH to PEPE without wrapping or unwrapping + // + // ETH ───(USV4)──> PEPE + // + deal(ALICE, 1 ether); + uint256 balanceBefore = IERC20(PEPE_ADDR).balanceOf(ALICE); + + // Encoded solution generated using `test_single_encoding_strategy_usv4_eth_in` + (bool success,) = tychoRouterAddr.call{value: 1 ether}( + hex"30ace1b10000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933000000000000000000000000000000000000000000c87c939ae635f92dc2379c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000682db9c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000680633c800000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000417d375e095d10a0d69c183082f533f2393e7ec356e4d222d32943ecab59683b013047017436b824fb8d00c2cdda2ab4136da5bc32ea79c6305b237633f6d0978c1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006cf62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330105cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc26982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000000000000000000000000000" + ); + + vm.stopPrank(); + + uint256 balanceAfter = IERC20(PEPE_ADDR).balanceOf(ALICE); + + assertTrue(success, "Call Failed"); + assertEq(balanceAfter - balanceBefore, 242373460199848577067005852); + } + + function testSingleUSV4IntegrationOutputETH() public { + // Test created with calldata from our router encoder. + + // Performs a single swap from USDC to ETH without wrapping or unwrapping + // + // USDC ───(USV4)──> ETH + // + deal(USDC_ADDR, ALICE, 3000_000000); + uint256 balanceBefore = ALICE.balance; + + // Approve permit2 + vm.startPrank(ALICE); + IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); + + // Encoded solution generated using `test_single_encoding_strategy_usv4_eth_out` + (bool success,) = tychoRouterAddr.call( + hex"7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000000000000000000000000000000000006826193a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067fe9342000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041dbb84c68a4293bcf6303ca45327614667c54226086ddcfa2daa9289c1657da9a57268f4d8ceea3c831d43e5a96b1dc54766bc3fda8845d5c7e266981b9d84c651b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007300710001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000004cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c00000000000000000000000000" + ); + + vm.stopPrank(); + + uint256 balanceAfter = ALICE.balance; + + assertTrue(success, "Call Failed"); + console.logUint(balanceAfter - balanceBefore); + assertEq(balanceAfter - balanceBefore, 1117254495486192350); + } + + function testSingleEkuboIntegration() public { + // Test needs to be run on block 22082754 or later + // notice that the addresses for the tycho router and the executors are different because we are redeploying + vm.rollFork(22082754); + tychoRouter = deployRouter(); + address[] memory executors = deployExecutors(); + vm.startPrank(EXECUTOR_SETTER); + tychoRouter.setExecutors(executors); + vm.stopPrank(); + + deal(ALICE, 1 ether); + uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE); + + // Approve permit2 + vm.startPrank(ALICE); + // Encoded solution generated using `test_single_encoding_strategy_ekubo` + (bool success,) = address(tychoRouter).call{value: 1 ether}( + hex"20144a070000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000713d7ebc40af7092e3f1c81f2e996cba5cae2090d705cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c000000000000000000000000000000000000000000000000000000" + ); + + uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE); + + assertTrue(success, "Call Failed"); + assertGe(balanceAfter - balanceBefore, 26173932); + assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0); + } + + function testSingleCurveIntegration() public { + deal(UWU_ADDR, ALICE, 1 ether); + + vm.startPrank(ALICE); + IERC20(UWU_ADDR).approve(tychoRouterAddr, type(uint256).max); + // Encoded solution generated using `test_single_encoding_strategy_curve` + (bool success,) = tychoRouterAddr.call( + hex"20144a070000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000055c08ca52497e2f1534b59e2917bf524d4765257000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000691d1499e622d69689cdf9004d05ec547d650ff21155c08ca52497e2f1534b59e2917bf524d4765257c02aaa39b223fe8d0a0e5c4f27ead9083c756cc277146b0a1d08b6844376df6d9da99ba7f1b19e710201000103cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000" + ); + + assertTrue(success, "Call Failed"); + assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 4691958787921); + + vm.stopPrank(); + } +} diff --git a/foundry/test/TychoRouterSequentialSwap.t.sol b/foundry/test/TychoRouterSequentialSwap.t.sol index f8c0776..88fef17 100644 --- a/foundry/test/TychoRouterSequentialSwap.t.sol +++ b/foundry/test/TychoRouterSequentialSwap.t.sol @@ -341,6 +341,71 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { assertEq(IERC20(USDC_ADDR).balanceOf(tychoRouterAddr), 99889294); } + function testSequentialSwapIntegrationPermit2() public { + // Performs a split swap from WETH to USDC though WBTC and DAI using USV2 pools + // + // WETH ──(USV2)──> WBTC ───(USV2)──> USDC + deal(WETH_ADDR, ALICE, 1 ether); + uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE); + + // Approve permit2 + vm.startPrank(ALICE); + IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); + // Encoded solution generated using `test_sequential_swap_strategy_encoder` + (bool success,) = tychoRouterAddr.call( + hex"51bcc7b60000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000682714ab00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ff8eb300000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000412fe66c22814eb271e37bb03303bae445eb96aa50fae9680a0ae685ee5795aebf1f5bb7718154c69680bcfc00cc9be525b2b021f57a1bddb4db622139acd425d41b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d940004375dff511095cc5a197a54140a24efef3a416000200525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20105000000000000000000000000000000000000000000000000" + ); + + vm.stopPrank(); + + uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE); + + assertTrue(success, "Call Failed"); + assertEq(balanceAfter - balanceBefore, 2552915143); + assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0); + } + + function testSequentialSwapIntegration() public { + // Performs a split swap from WETH to USDC though WBTC and DAI using USV2 pools + // + // WETH ──(USV2)──> WBTC ───(USV2)──> USDC + deal(WETH_ADDR, ALICE, 1 ether); + uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE); + + // Approve permit2 + vm.startPrank(ALICE); + IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max); + // Encoded solution generated using `test_sequential_swap_strategy_encoder_no_permit2` + (bool success,) = tychoRouterAddr.call( + hex"e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d940004375dff511095cc5a197a54140a24efef3a416000100525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20105000000000000000000000000000000000000000000000000" + ); + + vm.stopPrank(); + + uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE); + + assertTrue(success, "Call Failed"); + assertEq(balanceAfter - balanceBefore, 2552915143); + assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0); + } + + function testSequentialCyclicSwapIntegration() public { + deal(USDC_ADDR, ALICE, 100 * 10 ** 6); + + // Approve permit2 + vm.startPrank(ALICE); + IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); + // Encoded solution generated using `test_sequential_strategy_cyclic_swap` + (bool success,) = tychoRouterAddr.call( + hex"51bcc7b60000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f4308e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000682dbba300000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000680635ab00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041adc1487dd76b622c3762cfeb017fc51d2e3513e8e2e2a6a8d8e153d79192474735457ed064158c007ffc2a42cc8ee7ccc256155dbe4ef3b5404c4addbeb5612a1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f5640010200692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc28ad599c3a0ff1de082011efddc58f1908eb6e6d8000000000000000000000000" + ); + + assertTrue(success, "Call Failed"); + assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99889294); + + vm.stopPrank(); + } + function testUSV3USV2Integration() public { // Performs a sequential swap from WETH to USDC though WBTC and DAI using USV3 and USV2 pools // diff --git a/foundry/test/TychoRouterSingleSwap.t.sol b/foundry/test/TychoRouterSingleSwap.t.sol index 93beba0..1a374c7 100644 --- a/foundry/test/TychoRouterSingleSwap.t.sol +++ b/foundry/test/TychoRouterSingleSwap.t.sol @@ -324,4 +324,47 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { assertTrue(success, "Call Failed"); assertEq(balanceAfter - balanceBefore, 2659881924818443699787); } + + function testSingleSwapWithWrapIntegration() public { + // Tests swapping WETH -> DAI on a USV2 pool, but ETH is received from the user + // and wrapped before the swap + deal(ALICE, 1 ether); + uint256 balanceBefore = IERC20(DAI_ADDR).balanceOf(ALICE); + + // Approve permit2 + vm.startPrank(ALICE); + // Encoded solution generated using `test_single_swap_strategy_encoder_wrap` + (bool success,) = tychoRouterAddr.call{value: 1 ether}( + hex"30ace1b10000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000682db3ee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000068062df600000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000412bda9e4c6208c6851db4a383761f0511ace6a071dafcb8c017f312777d11988f50d017cc914ea2db8a8082a469584bff851efc00533b803fcc1aa4ada81c6c9e1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000" + ); + + vm.stopPrank(); + + uint256 balanceAfter = IERC20(DAI_ADDR).balanceOf(ALICE); + + assertTrue(success, "Call Failed"); + assertEq(balanceAfter - balanceBefore, 2659881924818443699787); + } + + function testSingleSwapWithUnwrapIntegration() public { + // Tests swapping DAI -> WETH on a USV2 pool, and WETH is unwrapped to ETH + // before sending back to the user + deal(DAI_ADDR, ALICE, 3000 ether); + uint256 balanceBefore = ALICE.balance; + + // Approve permit2 + vm.startPrank(ALICE); + IERC20(DAI_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); + // Encoded solution generated using `test_single_swap_strategy_encoder_unwrap` + (bool success,) = tychoRouterAddr.call( + hex"30ace1b10000000000000000000000000000000000000000000000a2a15d09519be000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000a2a15d09519be0000000000000000000000000000000000000000000000000000000000000682db45d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000068062e6500000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041de45f1a73e8a22fc958af300f93cff06b49e74667bb29b810aed4254fef0dae6340ceb95265d81f5b158bcade2b5a2e3efa8bfa521a6466c0b1ce0bcfddc19d21c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139501020000000000000000000000000000" + ); + + vm.stopPrank(); + + uint256 balanceAfter = ALICE.balance; + + assertTrue(success, "Call Failed"); + assertEq(balanceAfter - balanceBefore, 1120007305574805922); + } } diff --git a/foundry/test/TychoRouterSplitSwap.t.sol b/foundry/test/TychoRouterSplitSwap.t.sol index 46817c4..b086c6e 100644 --- a/foundry/test/TychoRouterSplitSwap.t.sol +++ b/foundry/test/TychoRouterSplitSwap.t.sol @@ -657,4 +657,67 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tychoRouter.exposedSplitSwap(amountIn, 2, pleEncode(swaps)); assertGt(IERC20(BASE_MAG7).balanceOf(tychoRouterAddr), 1379830606); } + + function testSplitSwapIntegration() public { + // Performs a split swap from WETH to USDC though WBTC and DAI using USV2 pools + // + // ┌──(USV2)──> WBTC ───(USV2)──> USDC + // WETH ─┤ + // └──(USV2)──> DAI ───(USV2)──> USDC + deal(WETH_ADDR, ALICE, 1 ether); + uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE); + + // Approve permit2 + vm.startPrank(ALICE); + IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); + // Encoded solution generated using `test_split_swap_strategy_encoder` + (bool success,) = tychoRouterAddr.call( + hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006826193a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067fe9342000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041d137d0776bc16ff9c49bfd3e96103ceb6926654f314489cafcf5a64ab7a9c4f2061ed5ffdef67c33c3c5b78036d28d9eb73da156a0e68d8740235be50e88a3481b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164005700028000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950002005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950002005702030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fae461ca67b15dc8dc81ce7615e0320da1a9ab8d5cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20100005701030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2010000000000000000000000000000000000000000000000000000000000" + ); + + vm.stopPrank(); + + uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE); + + assertTrue(success, "Call Failed"); + assertGe(balanceAfter - balanceBefore, 26173932); + + // All input tokens are transferred to the router at first. Make sure we used + // all of it (and thus our splits are correct). + assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0); + } + + function testSplitInputCyclicSwapIntegration() public { + deal(USDC_ADDR, ALICE, 100 * 10 ** 6); + + // Approve permit2 + vm.startPrank(ALICE); + IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); + // Encoded solution generated using `test_split_input_cyclic_swap` + (bool success,) = tychoRouterAddr.call( + hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ef619b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006826193a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067fe934200000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004194fc497ac440b520981d23b4713425da21dc1c801e657d218a917b5c51339a660b9a5fe0a346cb0aacc0d67ebf03f8fa3ec9fade437ef1b08ea837b2442931b61b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139006e00019999992e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400102006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80102005701000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2b4e16d0168e52d35cacd2c6185b44281ec28c9dccd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000" + ); + + assertTrue(success, "Call Failed"); + assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99574171); + + vm.stopPrank(); + } + + function testSplitOutputCyclicSwapIntegration() public { + deal(USDC_ADDR, ALICE, 100 * 10 ** 6); + + // Approve permit2 + vm.startPrank(ALICE); + IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); + // Encoded solution generated using `test_split_output_cyclic_swap` + (bool success,) = tychoRouterAddr.call( + hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005eea514000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006826193a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067fe934200000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004194fc497ac440b520981d23b4713425da21dc1c801e657d218a917b5c51339a660b9a5fe0a346cb0aacc0d67ebf03f8fa3ec9fade437ef1b08ea837b2442931b61b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d013950102006e01009999992e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f4cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc288e6a0c2ddd26feeb64f039a2c41296fcb3f56400000006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc28ad599c3a0ff1de082011efddc58f1908eb6e6d8000000000000000000" + ); + + assertTrue(success, "Call Failed"); + assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99525908); + + vm.stopPrank(); + } } diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 85c187d..aa81886 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -691,489 +691,301 @@ mod tests { .unwrap() } - #[rstest] - #[case::with_check_no_slippage( - None, - None, - Some(BigUint::from_str("2659881924818443699787").unwrap()), - U256::from_str("2659881924818443699787").unwrap(), - )] - #[case::no_check_with_slippage( - Some(BigUint::from_str("3_000_000000000000000000").unwrap()), - Some(0.01f64), - None, - U256::from_str("2_970_000000000000000000").unwrap(), - )] - #[case::with_check_and_slippage( - Some(BigUint::from_str("3_000_000000000000000000").unwrap()), - Some(0.01f64), - Some(BigUint::from_str("2_999_000000000000000000").unwrap()), - U256::from_str("2_999_000000000000000000").unwrap(), - )] - fn test_split_swap_strategy_encoder_simple_route( - #[case] expected_amount: Option, - #[case] slippage: Option, - #[case] checked_amount: Option, - #[case] expected_min_amount: U256, - ) { - // Performs a single swap from WETH to DAI on a USV2 pool, with no grouping optimizations. + mod single { + use super::*; + #[rstest] + #[case::with_check_no_slippage( + None, + None, + Some(BigUint::from_str("2659881924818443699787").unwrap()), + U256::from_str("2659881924818443699787").unwrap(), + )] + #[case::no_check_with_slippage( + Some(BigUint::from_str("3_000_000000000000000000").unwrap()), + Some(0.01f64), + None, + U256::from_str("2_970_000000000000000000").unwrap(), + )] + #[case::with_check_and_slippage( + Some(BigUint::from_str("3_000_000000000000000000").unwrap()), + Some(0.01f64), + Some(BigUint::from_str("2_999_000000000000000000").unwrap()), + U256::from_str("2_999_000000000000000000").unwrap(), + )] + fn test_single_swap_strategy_encoder( + #[case] expected_amount: Option, + #[case] slippage: Option, + #[case] checked_amount: Option, + #[case] expected_min_amount: U256, + ) { + // Performs a single swap from WETH to DAI on a USV2 pool, with no grouping + // optimizations. - // Set up a mock private key for signing - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); + // Set up a mock private key for signing + let private_key = + "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); - let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); + let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); - let swap = Swap { - component: ProtocolComponent { - id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), - protocol_system: "uniswap_v2".to_string(), - ..Default::default() - }, - token_in: weth.clone(), - token_out: dai.clone(), - split: 0f64, - }; - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SplitSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - Some(private_key), - Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), - ) - .unwrap(); - let solution = Solution { - exact_out: false, - given_token: weth, - given_amount: BigUint::from_str("1_000000000000000000").unwrap(), - checked_token: dai, - expected_amount, - slippage, - checked_amount, - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap], - ..Default::default() - }; - - let (calldata, _) = encoder - .encode_strategy(solution) + let swap = Swap { + component: ProtocolComponent { + id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0f64, + }; + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = SingleSwapStrategyEncoder::new( + eth_chain(), + swap_encoder_registry, + Some(private_key), + Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + ) .unwrap(); - let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); - let expected_input = [ - "7c553846", // Function selector - "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount out - "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in - "0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out - &expected_min_amount_encoded, // min amount out - "0000000000000000000000000000000000000000000000000000000000000000", // wrap - "0000000000000000000000000000000000000000000000000000000000000000", // unwrap - "0000000000000000000000000000000000000000000000000000000000000002", // tokens length - "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - ] - .join(""); - - // after this there is the permit and because of the deadlines (that depend on block time) - // it's hard to assert - // "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in - // "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in - // "0000000000000000000000000000000000000000000000000000000067c205fe", // expiration - // "0000000000000000000000000000000000000000000000000000000000000000", // nonce - // "0000000000000000000000002c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f4", // spender - // "00000000000000000000000000000000000000000000000000000000679a8006", // deadline - // offset of signature (from start of call data to beginning of length indication) - // "0000000000000000000000000000000000000000000000000000000000000200", - // offset of ple encoded swaps (from start of call data to beginning of length indication) - // "0000000000000000000000000000000000000000000000000000000000000280", - // length of signature without padding - // "0000000000000000000000000000000000000000000000000000000000000041", - // signature + padding - // "a031b63a01ef5d25975663e5d6c420ef498e3a5968b593cdf846c6729a788186", - // "1ddaf79c51453cd501d321ee541d13593e3a266be44103eefdf6e76a032d2870", - // "1b00000000000000000000000000000000000000000000000000000000000000" - - let expected_swaps = String::from(concat!( - // length of ple encoded swaps without padding - "0000000000000000000000000000000000000000000000000000000000000059", - // ple encoded swaps - "0057", - // Swap header - "00", // token in index - "01", // token out index - "000000", // split - // Swap data - "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address - "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in - "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id - "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - "00", // zero2one - "02", // transfer type - "00000000000000", // padding - )); - let hex_calldata = encode(&calldata); - - assert_eq!(hex_calldata[..520], expected_input); - assert_eq!(hex_calldata[1288..], expected_swaps); - } - - #[rstest] - #[case::with_check_no_slippage( - None, - None, - Some(BigUint::from_str("2659881924818443699787").unwrap()), - U256::from_str("2659881924818443699787").unwrap(), - )] - #[case::no_check_with_slippage( - Some(BigUint::from_str("3_000_000000000000000000").unwrap()), - Some(0.01f64), - None, - U256::from_str("2_970_000000000000000000").unwrap(), - )] - #[case::with_check_and_slippage( - Some(BigUint::from_str("3_000_000000000000000000").unwrap()), - Some(0.01f64), - Some(BigUint::from_str("2_999_000000000000000000").unwrap()), - U256::from_str("2_999_000000000000000000").unwrap(), - )] - fn test_single_swap_strategy_encoder( - #[case] expected_amount: Option, - #[case] slippage: Option, - #[case] checked_amount: Option, - #[case] expected_min_amount: U256, - ) { - // Performs a single swap from WETH to DAI on a USV2 pool, with no grouping optimizations. - - // Set up a mock private key for signing - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - - let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); - let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); - - let swap = Swap { - component: ProtocolComponent { - id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), - protocol_system: "uniswap_v2".to_string(), + let solution = Solution { + exact_out: false, + given_token: weth, + given_amount: BigUint::from_str("1_000000000000000000").unwrap(), + checked_token: dai, + expected_amount, + slippage, + checked_amount, + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + swaps: vec![swap], ..Default::default() - }, - token_in: weth.clone(), - token_out: dai.clone(), - split: 0f64, - }; - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SingleSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - Some(private_key), - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), - ) - .unwrap(); - let solution = Solution { - exact_out: false, - given_token: weth, - given_amount: BigUint::from_str("1_000000000000000000").unwrap(), - checked_token: dai, - expected_amount, - slippage, - checked_amount, - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap], - ..Default::default() - }; + }; - let (calldata, _) = encoder - .encode_strategy(solution) + let (calldata, _) = encoder + .encode_strategy(solution) + .unwrap(); + let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); + let expected_input = [ + "30ace1b1", // Function selector + "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount out + "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in + "0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out + &expected_min_amount_encoded, // min amount out + "0000000000000000000000000000000000000000000000000000000000000000", // wrap + "0000000000000000000000000000000000000000000000000000000000000000", // unwrap + "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver + ] + .join(""); + + // after this there is the permit and because of the deadlines (that depend on block + // time) it's hard to assert + + let expected_swap = String::from(concat!( + // length of encoded swap without padding + "0000000000000000000000000000000000000000000000000000000000000052", + // Swap data + "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in + "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id + "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver + "00", // zero2one + "02", // transfer type + "0000000000000000000000000000", // padding + )); + let hex_calldata = encode(&calldata); + println!("test_single_swap_strategy_encoder: {}", hex_calldata); + + assert_eq!(hex_calldata[..456], expected_input); + assert_eq!(hex_calldata[1224..], expected_swap); + } + + #[test] + fn test_single_swap_strategy_encoder_no_permit2() { + // Performs a single swap from WETH to DAI on a USV2 pool, without permit2 and no + // grouping optimizations. + + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); + let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); + + let expected_amount = Some(BigUint::from_str("2_650_000000000000000000").unwrap()); + let slippage = Some(0.01f64); + let checked_amount = Some(BigUint::from_str("2_640_000000000000000000").unwrap()); + let expected_min_amount = U256::from_str("2_640_000000000000000000").unwrap(); + + let swap = Swap { + component: ProtocolComponent { + id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0f64, + }; + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = SingleSwapStrategyEncoder::new( + eth_chain(), + swap_encoder_registry, + None, + Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + ) .unwrap(); - let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); - let expected_input = [ - "30ace1b1", // Function selector - "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount out - "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in - "0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out - &expected_min_amount_encoded, // min amount out - "0000000000000000000000000000000000000000000000000000000000000000", // wrap - "0000000000000000000000000000000000000000000000000000000000000000", // unwrap - "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - ] - .join(""); - - // after this there is the permit and because of the deadlines (that depend on block time) - // it's hard to assert - - let expected_swap = String::from(concat!( - // length of encoded swap without padding - "0000000000000000000000000000000000000000000000000000000000000052", - // Swap data - "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address - "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in - "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id - "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - "00", // zero2one - "02", // transfer type - "0000000000000000000000000000", // padding - )); - let hex_calldata = encode(&calldata); - println!("test_single_swap_strategy_encoder: {}", hex_calldata); - - assert_eq!(hex_calldata[..456], expected_input); - assert_eq!(hex_calldata[1224..], expected_swap); - } - - #[test] - fn test_single_swap_strategy_encoder_wrap() { - // Performs a single swap from WETH to DAI on a USV2 pool, wrapping ETH - // Note: This test does not assert anything. It is only used to obtain integration test - // data for our router solidity test. - - // Set up a mock private key for signing - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - - let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); - - let swap = Swap { - component: ProtocolComponent { - id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), - protocol_system: "uniswap_v2".to_string(), + let solution = Solution { + exact_out: false, + given_token: weth, + given_amount: BigUint::from_str("1_000000000000000000").unwrap(), + checked_token: dai, + expected_amount, + slippage, + checked_amount, + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + swaps: vec![swap], ..Default::default() - }, - token_in: weth(), - token_out: dai.clone(), - split: 0f64, - }; - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SplitSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - Some(private_key), - Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), - ) - .unwrap(); - let solution = Solution { - exact_out: false, - given_token: eth(), - given_amount: BigUint::from_str("1_000000000000000000").unwrap(), - checked_token: dai, - expected_amount: None, - checked_amount: Some(BigUint::from_str("2659881924818443699787").unwrap()), - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap], - native_action: Some(NativeAction::Wrap), - ..Default::default() - }; + }; - let (calldata, _) = encoder - .encode_strategy(solution) + let (calldata, _) = encoder + .encode_strategy(solution) + .unwrap(); + let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); + let expected_input = [ + "20144a07", // Function selector + "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in + "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in + "0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out + &expected_min_amount_encoded, // min amount out + "0000000000000000000000000000000000000000000000000000000000000000", // wrap + "0000000000000000000000000000000000000000000000000000000000000000", // unwrap + "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver + "0000000000000000000000000000000000000000000000000000000000000100", // offset of swap bytes + "0000000000000000000000000000000000000000000000000000000000000052", // length of swap bytes without padding + + // Swap data + "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in + "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id + "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver + "00", // zero2one + "01", // transfer type + "0000000000000000000000000000", // padding + ] + .join(""); + + let hex_calldata = encode(&calldata); + + assert_eq!(hex_calldata, expected_input); + println!("test_single_swap_strategy_encoder_no_permit2: {}", hex_calldata); + } + + #[test] + fn test_single_swap_strategy_encoder_wrap() { + // Performs a single swap from WETH to DAI on a USV2 pool, wrapping ETH + // Note: This test does not assert anything. It is only used to obtain integration test + // data for our router solidity test. + + // Set up a mock private key for signing + let private_key = + "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); + + let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); + + let swap = Swap { + component: ProtocolComponent { + id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth(), + token_out: dai.clone(), + split: 0f64, + }; + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = SingleSwapStrategyEncoder::new( + eth_chain(), + swap_encoder_registry, + Some(private_key), + Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + ) .unwrap(); - - let hex_calldata = encode(&calldata); - println!("test_single_swap_strategy_encoder_wrap: {}", hex_calldata); - } - - #[test] - fn test_split_swap_strategy_encoder_wrap() { - // Performs a single swap from WETH to DAI on a USV2 pool, wrapping ETH - // Note: This test does not assert anything. It is only used to obtain integration test - // data for our router solidity test. - - // Set up a mock private key for signing - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - - let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); - - let swap = Swap { - component: ProtocolComponent { - id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), - protocol_system: "uniswap_v2".to_string(), + let solution = Solution { + exact_out: false, + given_token: eth(), + given_amount: BigUint::from_str("1_000000000000000000").unwrap(), + checked_token: dai, + expected_amount: None, + checked_amount: Some(BigUint::from_str("2659881924818443699787").unwrap()), + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + swaps: vec![swap], + native_action: Some(NativeAction::Wrap), ..Default::default() - }, - token_in: weth(), - token_out: dai.clone(), - split: 0f64, - }; - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SplitSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - Some(private_key), - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), - ) - .unwrap(); - let solution = Solution { - exact_out: false, - given_token: eth(), - given_amount: BigUint::from_str("1_000000000000000000").unwrap(), - checked_token: dai, - expected_amount: None, - checked_amount: Some(BigUint::from_str("2659881924818443699787").unwrap()), - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap], - native_action: Some(NativeAction::Wrap), - ..Default::default() - }; + }; - let (calldata, _) = encoder - .encode_strategy(solution) + let (calldata, _) = encoder + .encode_strategy(solution) + .unwrap(); + + let hex_calldata = encode(&calldata); + println!("test_single_swap_strategy_encoder_wrap: {}", hex_calldata); + } + + #[test] + fn test_single_swap_strategy_encoder_unwrap() { + // Performs a single swap from DAI to WETH on a USV2 pool, unwrapping ETH at the end + // Note: This test does not assert anything. It is only used to obtain integration test + // data for our router solidity test. + + // Set up a mock private key for signing + let private_key = + "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); + + let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); + + let swap = Swap { + component: ProtocolComponent { + id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: dai.clone(), + token_out: weth(), + split: 0f64, + }; + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = SingleSwapStrategyEncoder::new( + eth_chain(), + swap_encoder_registry, + Some(private_key), + Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + ) .unwrap(); - - let hex_calldata = encode(&calldata); - println!("test_split_swap_strategy_encoder_wrap: {}", hex_calldata); - } - - #[test] - fn test_split_swap_strategy_encoder_unwrap() { - // Performs a single swap from DAI to WETH on a USV2 pool, unwrapping ETH at the end - // Note: This test does not assert anything. It is only used to obtain integration test - // data for our router solidity test. - - // Set up a mock private key for signing - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - - let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); - - let swap = Swap { - component: ProtocolComponent { - id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), - protocol_system: "uniswap_v2".to_string(), + let solution = Solution { + exact_out: false, + given_token: dai, + given_amount: BigUint::from_str("3_000_000000000000000000").unwrap(), + checked_token: eth(), + expected_amount: Some(BigUint::from_str("1_000000000000000000").unwrap()), + checked_amount: Some(BigUint::from_str("1_000000000000000000").unwrap()), + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + swaps: vec![swap], + native_action: Some(NativeAction::Unwrap), ..Default::default() - }, - token_in: dai.clone(), - token_out: weth(), - split: 0f64, - }; - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SplitSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - Some(private_key), - Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), - ) - .unwrap(); - let solution = Solution { - exact_out: false, - given_token: dai, - given_amount: BigUint::from_str("3_000_000000000000000000").unwrap(), - checked_token: eth(), - expected_amount: Some(BigUint::from_str("1_000000000000000000").unwrap()), - checked_amount: Some(BigUint::from_str("1_000000000000000000").unwrap()), - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap], - native_action: Some(NativeAction::Unwrap), - ..Default::default() - }; + }; - let (calldata, _) = encoder - .encode_strategy(solution) - .unwrap(); + let (calldata, _) = encoder + .encode_strategy(solution) + .unwrap(); - let hex_calldata = encode(&calldata); - println!("test_split_swap_strategy_encoder_unwrap: {}", hex_calldata); - } - - #[test] - fn test_split_swap_strategy_encoder_complex_route() { - // Note: This test does not assert anything. It is only used to obtain integration test - // data for our router solidity test. - // - // Performs a split swap from WETH to USDC though WBTC and DAI using USV2 pools - // - // ┌──(USV2)──> WBTC ───(USV2)──> USDC - // WETH ─┤ - // └──(USV2)──> DAI ───(USV2)──> USDC - // - - // Set up a mock private key for signing - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - - let weth = weth(); - let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); - let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(); - let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); - - let swap_weth_dai = Swap { - component: ProtocolComponent { - id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), - protocol_system: "uniswap_v2".to_string(), - ..Default::default() - }, - token_in: weth.clone(), - token_out: dai.clone(), - split: 0.5f64, - }; - let swap_weth_wbtc = Swap { - component: ProtocolComponent { - id: "0xBb2b8038a1640196FbE3e38816F3e67Cba72D940".to_string(), - protocol_system: "uniswap_v2".to_string(), - ..Default::default() - }, - token_in: weth.clone(), - token_out: wbtc.clone(), - // This represents the remaining 50%, but to avoid any rounding errors we set this to - // 0 to signify "the remainder of the WETH value". It should still be very close to 50% - split: 0f64, - }; - let swap_dai_usdc = Swap { - component: ProtocolComponent { - id: "0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5".to_string(), - protocol_system: "uniswap_v2".to_string(), - ..Default::default() - }, - token_in: dai.clone(), - token_out: usdc.clone(), - split: 0f64, - }; - let swap_wbtc_usdc = Swap { - component: ProtocolComponent { - id: "0x004375Dff511095CC5A197A54140a24eFEF3A416".to_string(), - protocol_system: "uniswap_v2".to_string(), - ..Default::default() - }, - token_in: wbtc.clone(), - token_out: usdc.clone(), - split: 0f64, - }; - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SplitSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - Some(private_key), - Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), - ) - .unwrap(); - let solution = Solution { - exact_out: false, - given_token: weth, - given_amount: BigUint::from_str("1_000000000000000000").unwrap(), - checked_token: usdc, - expected_amount: None, - checked_amount: Some(BigUint::from_str("26173932").unwrap()), - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap_weth_dai, swap_weth_wbtc, swap_dai_usdc, swap_wbtc_usdc], - ..Default::default() - }; - - let (calldata, _) = encoder - .encode_strategy(solution) - .unwrap(); - - let _hex_calldata = encode(&calldata); - println!("test_split_swap_strategy_encoder_complex_route: {}", _hex_calldata); + let hex_calldata = encode(&calldata); + println!("test_split_swap_strategy_encoder_unwrap: {}", hex_calldata); + } } mod sequential { use super::*; #[test] - fn test_sequential_swap_strategy_encoder_complex_route() { + fn test_sequential_swap_strategy_encoder() { // Note: This test does not assert anything. It is only used to obtain integration test // data for our router solidity test. // @@ -1235,7 +1047,7 @@ mod tests { .unwrap(); let _hex_calldata = encode(&calldata); - println!("test_sequential_swap_strategy_encoder_complex_route: {}", _hex_calldata); + println!("test_sequential_swap_strategy_encoder: {}", _hex_calldata); } #[test] @@ -1331,22 +1143,45 @@ mod tests { } #[test] - fn test_uniswap_v3_uniswap_v2() { - // Note: This test does not assert anything. It is only used to obtain integration test - // data for our router solidity test. - // - // Performs a sequential swap from WETH to USDC though WBTC using USV3 and USV2 - // pools - // - // WETH ───(USV3)──> WBTC ───(USV2)──> USDC + fn test_sequential_strategy_cyclic_swap() { + // This test has start and end tokens that are the same + // The flow is: + // USDC -> WETH -> USDC using two pools - let weth = weth(); - let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(); + // Set up a mock private key for signing (Alice's pk in our router tests) + let private_key = + "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); + + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); - let swap_weth_wbtc = Swap { + // Create two Uniswap V3 pools for the cyclic swap + // USDC -> WETH (Pool 1) + let swap_usdc_weth = Swap { component: ProtocolComponent { - id: "0xCBCdF9626bC03E24f779434178A73a0B4bad62eD".to_string(), + id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3 + * Pool 1 */ + protocol_system: "uniswap_v3".to_string(), + static_attributes: { + let mut attrs = HashMap::new(); + attrs.insert( + "fee".to_string(), + Bytes::from(BigInt::from(500).to_signed_bytes_be()), + ); + attrs + }, + ..Default::default() + }, + token_in: usdc.clone(), + token_out: weth.clone(), + split: 0f64, + }; + + // WETH -> USDC (Pool 2) + let swap_weth_usdc = Swap { + component: ProtocolComponent { + id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3 + * Pool 2 */ protocol_system: "uniswap_v3".to_string(), static_attributes: { let mut attrs = HashMap::new(); @@ -1359,7 +1194,446 @@ mod tests { ..Default::default() }, token_in: weth.clone(), + token_out: usdc.clone(), + split: 0f64, + }; + + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = SequentialSwapStrategyEncoder::new( + eth_chain(), + swap_encoder_registry, + Some(private_key), + Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + ) + .unwrap(); + + let solution = Solution { + exact_out: false, + given_token: usdc.clone(), + given_amount: BigUint::from_str("100000000").unwrap(), // 100 USDC (6 decimals) + checked_token: usdc.clone(), + expected_amount: None, + checked_amount: Some(BigUint::from_str("99889294").unwrap()), /* Expected output + * from + * test */ + slippage: None, + swaps: vec![swap_usdc_weth, swap_weth_usdc], + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + ..Default::default() + }; + + let (calldata, _) = encoder + .encode_strategy(solution) + .unwrap(); + let hex_calldata = hex::encode(&calldata); + let expected_input = [ + "51bcc7b6", // selector + "0000000000000000000000000000000000000000000000000000000005f5e100", // given amount + "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // given token + "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // checked token + "0000000000000000000000000000000000000000000000000000000005f4308e", // min amount out + "0000000000000000000000000000000000000000000000000000000000000000", // wrap action + "0000000000000000000000000000000000000000000000000000000000000000", // unwrap action + "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver + ] + .join(""); + + let expected_swaps = [ + "00000000000000000000000000000000000000000000000000000000000000d6", // length of ple encoded swaps without padding + "0069", // ple encoded swaps + "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address + "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token out + "0001f4", // pool fee + "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver + "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id + "01", // zero2one + "02", // transfer type + "0069", // ple encoded swaps + "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in + "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token out + "000bb8", // pool fee + "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver + "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id + "00", // zero2one + "00", // transfer type + "00000000000000000000", // padding + ] + .join(""); + + assert_eq!(hex_calldata[..456], expected_input); + assert_eq!(hex_calldata[1224..], expected_swaps); + println!("test_cyclic_sequential_swap_split_strategy: {}", hex_calldata); + } + + mod chained_swaps { + // In this module we test the ability to chain swaps or not. Different protocols are + // tested. The encoded data is used for solidity tests as well + use super::*; + + #[test] + fn test_uniswap_v3_uniswap_v2() { + // Note: This test does not assert anything. It is only used to obtain integration + // test data for our router solidity test. + // + // Performs a sequential swap from WETH to USDC though WBTC using USV3 and USV2 + // pools + // + // WETH ───(USV3)──> WBTC ───(USV2)──> USDC + + let weth = weth(); + let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(); + let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); + + let swap_weth_wbtc = Swap { + component: ProtocolComponent { + id: "0xCBCdF9626bC03E24f779434178A73a0B4bad62eD".to_string(), + protocol_system: "uniswap_v3".to_string(), + static_attributes: { + let mut attrs = HashMap::new(); + attrs.insert( + "fee".to_string(), + Bytes::from(BigInt::from(3000).to_signed_bytes_be()), + ); + attrs + }, + ..Default::default() + }, + token_in: weth.clone(), + token_out: wbtc.clone(), + split: 0f64, + }; + let swap_wbtc_usdc = Swap { + component: ProtocolComponent { + id: "0x004375Dff511095CC5A197A54140a24eFEF3A416".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: wbtc.clone(), + token_out: usdc.clone(), + split: 0f64, + }; + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = SequentialSwapStrategyEncoder::new( + eth_chain(), + swap_encoder_registry, + None, + Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + ) + .unwrap(); + let solution = Solution { + exact_out: false, + given_token: weth, + given_amount: BigUint::from_str("1_000000000000000000").unwrap(), + checked_token: usdc, + expected_amount: None, + checked_amount: Some(BigUint::from_str("26173932").unwrap()), + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2") + .unwrap(), + swaps: vec![swap_weth_wbtc, swap_wbtc_usdc], + ..Default::default() + }; + + let (calldata, _) = encoder + .encode_strategy(solution) + .unwrap(); + + let _hex_calldata = encode(&calldata); + println!("test_uniswap_v3_uniswap_v2: {}", _hex_calldata); + } + + #[test] + fn test_uniswap_v3_uniswap_v3() { + // Note: This test does not assert anything. It is only used to obtain integration + // test data for our router solidity test. + // + // Performs a sequential swap from WETH to USDC though WBTC using USV3 pools + // There is no optimization between the two USV3 pools, this is currently not + // supported. + // + // WETH ───(USV3)──> WBTC ───(USV3)──> USDC + + let weth = weth(); + let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(); + let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); + + let swap_weth_wbtc = Swap { + component: ProtocolComponent { + id: "0xCBCdF9626bC03E24f779434178A73a0B4bad62eD".to_string(), + protocol_system: "uniswap_v3".to_string(), + static_attributes: { + let mut attrs = HashMap::new(); + attrs.insert( + "fee".to_string(), + Bytes::from(BigInt::from(3000).to_signed_bytes_be()), + ); + attrs + }, + ..Default::default() + }, + token_in: weth.clone(), + token_out: wbtc.clone(), + split: 0f64, + }; + let swap_wbtc_usdc = Swap { + component: ProtocolComponent { + id: "0x99ac8cA7087fA4A2A1FB6357269965A2014ABc35".to_string(), + protocol_system: "uniswap_v3".to_string(), + static_attributes: { + let mut attrs = HashMap::new(); + attrs.insert( + "fee".to_string(), + Bytes::from(BigInt::from(3000).to_signed_bytes_be()), + ); + attrs + }, + ..Default::default() + }, + token_in: wbtc.clone(), + token_out: usdc.clone(), + split: 0f64, + }; + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = SequentialSwapStrategyEncoder::new( + eth_chain(), + swap_encoder_registry, + None, + Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + ) + .unwrap(); + let solution = Solution { + exact_out: false, + given_token: weth, + given_amount: BigUint::from_str("1_000000000000000000").unwrap(), + checked_token: usdc, + expected_amount: None, + checked_amount: Some(BigUint::from_str("26173932").unwrap()), + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2") + .unwrap(), + swaps: vec![swap_weth_wbtc, swap_wbtc_usdc], + ..Default::default() + }; + + let (calldata, _) = encoder + .encode_strategy(solution) + .unwrap(); + + let _hex_calldata = encode(&calldata); + println!("test_uniswap_v3_uniswap_v3: {}", _hex_calldata); + } + + #[test] + fn test_uniswap_v3_curve() { + // Note: This test does not assert anything. It is only used to obtain integration + // test data for our router solidity test. + // + // Performs a sequential swap from WETH to USDT though WBTC using USV3 and curve + // pools + // + // WETH ───(USV3)──> WBTC ───(curve)──> USDT + + let weth = weth(); + let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(); + let usdt = Bytes::from_str("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap(); + + let swap_weth_wbtc = Swap { + component: ProtocolComponent { + id: "0xCBCdF9626bC03E24f779434178A73a0B4bad62eD".to_string(), + protocol_system: "uniswap_v3".to_string(), + static_attributes: { + let mut attrs = HashMap::new(); + attrs.insert( + "fee".to_string(), + Bytes::from(BigInt::from(3000).to_signed_bytes_be()), + ); + attrs + }, + ..Default::default() + }, + token_in: weth.clone(), + token_out: wbtc.clone(), + split: 0f64, + }; + + let swap_wbtc_usdt = Swap { + component: ProtocolComponent { + id: String::from("0xD51a44d3FaE010294C616388b506AcdA1bfAAE46"), + protocol_system: String::from("vm:curve"), + static_attributes: { + let mut attrs: HashMap = HashMap::new(); + attrs.insert( + "factory".into(), + Bytes::from( + "0x0000000000000000000000000000000000000000" + .as_bytes() + .to_vec(), + ), + ); + attrs + }, + ..Default::default() + }, + token_in: wbtc.clone(), + token_out: usdt.clone(), + split: 0f64, + }; + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = SequentialSwapStrategyEncoder::new( + eth_chain(), + swap_encoder_registry, + None, + Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + ) + .unwrap(); + let solution = Solution { + exact_out: false, + given_token: weth, + given_amount: BigUint::from_str("1_000000000000000000").unwrap(), + checked_token: usdt, + expected_amount: None, + checked_amount: Some(BigUint::from_str("26173932").unwrap()), + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2") + .unwrap(), + swaps: vec![swap_weth_wbtc, swap_wbtc_usdt], + ..Default::default() + }; + + let (calldata, _) = encoder + .encode_strategy(solution) + .unwrap(); + + let _hex_calldata = encode(&calldata); + println!("test_uniswap_v3_curve: {}", _hex_calldata); + } + + #[test] + fn test_balancer_v2_uniswap_v2() { + // Note: This test does not assert anything. It is only used to obtain integration + // test data for our router solidity test. + // + // Performs a sequential swap from WETH to USDC though WBTC using balancer and USV2 + // pools + // + // WETH ───(balancer)──> WBTC ───(USV2)──> USDC + + let weth = weth(); + let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(); + let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); + + let swap_weth_wbtc = Swap { + component: ProtocolComponent { + id: "0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e" + .to_string(), + protocol_system: "vm:balancer_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: wbtc.clone(), + split: 0f64, + }; + + let swap_wbtc_usdc = Swap { + component: ProtocolComponent { + id: "0x004375Dff511095CC5A197A54140a24eFEF3A416".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: wbtc.clone(), + token_out: usdc.clone(), + split: 0f64, + }; + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = SequentialSwapStrategyEncoder::new( + eth_chain(), + swap_encoder_registry, + None, + Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + ) + .unwrap(); + let solution = Solution { + exact_out: false, + given_token: weth, + given_amount: BigUint::from_str("1_000000000000000000").unwrap(), + checked_token: usdc, + expected_amount: None, + checked_amount: Some(BigUint::from_str("26173932").unwrap()), + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2") + .unwrap(), + swaps: vec![swap_weth_wbtc, swap_wbtc_usdc], + ..Default::default() + }; + + let (calldata, _) = encoder + .encode_strategy(solution) + .unwrap(); + + let _hex_calldata = encode(&calldata); + println!("test_balancer_v2_uniswap_v2: {}", _hex_calldata); + } + } + } + + mod split { + use super::*; + + #[test] + fn test_split_swap_strategy_encoder() { + // Note: This test does not assert anything. It is only used to obtain integration test + // data for our router solidity test. + // + // Performs a split swap from WETH to USDC though WBTC and DAI using USV2 pools + // + // ┌──(USV2)──> WBTC ───(USV2)──> USDC + // WETH ─┤ + // └──(USV2)──> DAI ───(USV2)──> USDC + // + + // Set up a mock private key for signing + let private_key = + "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); + + let weth = weth(); + let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); + let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(); + let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); + + let swap_weth_dai = Swap { + component: ProtocolComponent { + id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0.5f64, + }; + let swap_weth_wbtc = Swap { + component: ProtocolComponent { + id: "0xBb2b8038a1640196FbE3e38816F3e67Cba72D940".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), token_out: wbtc.clone(), + // This represents the remaining 50%, but to avoid any rounding errors we set this + // to 0 to signify "the remainder of the WETH value". It should + // still be very close to 50% + split: 0f64, + }; + let swap_dai_usdc = Swap { + component: ProtocolComponent { + id: "0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: dai.clone(), + token_out: usdc.clone(), split: 0f64, }; let swap_wbtc_usdc = Swap { @@ -1373,11 +1647,11 @@ mod tests { split: 0f64, }; let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SequentialSwapStrategyEncoder::new( + let encoder = SplitSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - None, - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + Some(private_key), + Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), ) .unwrap(); let solution = Solution { @@ -1389,7 +1663,7 @@ mod tests { checked_amount: Some(BigUint::from_str("26173932").unwrap()), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap_weth_wbtc, swap_wbtc_usdc], + swaps: vec![swap_weth_dai, swap_weth_wbtc, swap_dai_usdc, swap_wbtc_usdc], ..Default::default() }; @@ -1398,27 +1672,74 @@ mod tests { .unwrap(); let _hex_calldata = encode(&calldata); - println!("test_uniswap_v3_uniswap_v2: {}", _hex_calldata); + println!("test_split_swap_strategy_encoder: {}", _hex_calldata); } #[test] - fn test_uniswap_v3_uniswap_v3() { - // Note: This test does not assert anything. It is only used to obtain integration test - // data for our router solidity test. - // - // Performs a sequential swap from WETH to USDC though WBTC using USV3 pools - // There is no optimization between the two USV3 pools, this is currently not supported. - // - // WETH ───(USV3)──> WBTC ───(USV3)──> USDC + fn test_split_input_cyclic_swap() { + // This test has start and end tokens that are the same + // The flow is: + // ┌─ (USV3, 60% split) ──> WETH ─┐ + // │ │ + // USDC ──────┤ ├──(USV2)──> USDC + // │ │ + // └─ (USV3, 40% split) ──> WETH ─┘ - let weth = weth(); - let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(); + // Set up a mock private key for signing (Alice's pk in our router tests) + let private_key = + "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); + + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); - let swap_weth_wbtc = Swap { + // USDC -> WETH (Pool 1) - 60% of input + let swap_usdc_weth_pool1 = Swap { component: ProtocolComponent { - id: "0xCBCdF9626bC03E24f779434178A73a0B4bad62eD".to_string(), + id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3 + * Pool 1 */ protocol_system: "uniswap_v3".to_string(), + static_attributes: { + let mut attrs = HashMap::new(); + attrs.insert( + "fee".to_string(), + Bytes::from(BigInt::from(500).to_signed_bytes_be()), + ); + attrs + }, + ..Default::default() + }, + token_in: usdc.clone(), + token_out: weth.clone(), + split: 0.6f64, // 60% of input + }; + + // USDC -> WETH (Pool 2) - 40% of input (remaining) + let swap_usdc_weth_pool2 = Swap { + component: ProtocolComponent { + id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3 + * Pool 2 */ + protocol_system: "uniswap_v3".to_string(), + static_attributes: { + let mut attrs = HashMap::new(); + attrs.insert( + "fee".to_string(), + Bytes::from(BigInt::from(3000).to_signed_bytes_be()), + ); + attrs + }, + ..Default::default() + }, + token_in: usdc.clone(), + token_out: weth.clone(), + split: 0f64, // Remaining 40% + }; + + // WETH -> USDC (Pool 2) + let swap_weth_usdc_pool2 = Swap { + component: ProtocolComponent { + id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(), /* USDC-WETH USV2 + * Pool 2 */ + protocol_system: "uniswap_v2".to_string(), static_attributes: { let mut attrs = HashMap::new(); attrs.insert( @@ -1430,45 +1751,32 @@ mod tests { ..Default::default() }, token_in: weth.clone(), - token_out: wbtc.clone(), - split: 0f64, - }; - let swap_wbtc_usdc = Swap { - component: ProtocolComponent { - id: "0x99ac8cA7087fA4A2A1FB6357269965A2014ABc35".to_string(), - protocol_system: "uniswap_v3".to_string(), - static_attributes: { - let mut attrs = HashMap::new(); - attrs.insert( - "fee".to_string(), - Bytes::from(BigInt::from(3000).to_signed_bytes_be()), - ); - attrs - }, - ..Default::default() - }, - token_in: wbtc.clone(), token_out: usdc.clone(), - split: 0f64, + split: 0.0f64, }; + let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SequentialSwapStrategyEncoder::new( + let encoder = SplitSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - None, - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + Some(private_key.clone()), + Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), ) .unwrap(); + let solution = Solution { exact_out: false, - given_token: weth, - given_amount: BigUint::from_str("1_000000000000000000").unwrap(), - checked_token: usdc, + given_token: usdc.clone(), + given_amount: BigUint::from_str("100000000").unwrap(), // 100 USDC (6 decimals) + checked_token: usdc.clone(), expected_amount: None, - checked_amount: Some(BigUint::from_str("26173932").unwrap()), + checked_amount: Some(BigUint::from_str("99574171").unwrap()), /* Expected output + * from + * test */ sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap_weth_wbtc, swap_wbtc_usdc], + slippage: None, + swaps: vec![swap_usdc_weth_pool1, swap_usdc_weth_pool2, swap_weth_usdc_pool2], ..Default::default() }; @@ -1476,27 +1784,123 @@ mod tests { .encode_strategy(solution) .unwrap(); - let _hex_calldata = encode(&calldata); - println!("test_uniswap_v3_uniswap_v3: {}", _hex_calldata); + let hex_calldata = hex::encode(&calldata); + let expected_input = [ + "7c553846", // selector + "0000000000000000000000000000000000000000000000000000000005f5e100", // given amount + "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // given token + "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // checked token + "0000000000000000000000000000000000000000000000000000000005ef619b", // min amount out + "0000000000000000000000000000000000000000000000000000000000000000", // wrap action + "0000000000000000000000000000000000000000000000000000000000000000", // unwrap action + "0000000000000000000000000000000000000000000000000000000000000002", // tokens length + "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver + ] + .join(""); + let expected_swaps = [ + "0000000000000000000000000000000000000000000000000000000000000139", // length of ple encoded swaps without padding + "006e", // ple encoded swaps + "00", // token in index + "01", // token out index + "999999", // split + "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address + "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token out + "0001f4", // pool fee + "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver + "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id + "01", // zero2one + "02", // transfer type + "006e", // ple encoded swaps + "00", // token in index + "01", // token out index + "000000", // split + "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address + "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token out + "000bb8", // pool fee + "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver + "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id + "01", // zero2one + "02", // transfer type + "0057", // ple encoded swaps + "01", // token in index + "00", // token out index + "000000", // split + "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address, + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in + "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id, + "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver + "00", // zero2one + "00", // transfer type + "00000000000000" // padding + ] + .join(""); + assert_eq!(hex_calldata[..520], expected_input); + assert_eq!(hex_calldata[1288..], expected_swaps); + println!("test_split_input_cyclic_swap: {}", hex_calldata); } #[test] - fn test_uniswap_v3_curve() { - // Note: This test does not assert anything. It is only used to obtain integration test - // data for our router solidity test. - // - // Performs a sequential swap from WETH to USDT though WBTC using USV3 and curve - // pools - // - // WETH ───(USV3)──> WBTC ───(curve)──> USDT + fn test_split_output_cyclic_swap() { + // This test has start and end tokens that are the same + // The flow is: + // ┌─── (USV3, 60% split) ───┐ + // │ │ + // USDC ──(USV2) ── WETH──| ├─> USDC + // │ │ + // └─── (USV3, 40% split) ───┘ - let weth = weth(); - let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(); - let usdt = Bytes::from_str("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap(); + // Set up a mock private key for signing (Alice's pk in our router tests) + let private_key = + "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - let swap_weth_wbtc = Swap { + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); + let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); + + let swap_usdc_weth_v2 = Swap { component: ProtocolComponent { - id: "0xCBCdF9626bC03E24f779434178A73a0B4bad62eD".to_string(), + id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(), // USDC-WETH USV2 + protocol_system: "uniswap_v2".to_string(), + static_attributes: { + let mut attrs = HashMap::new(); + attrs.insert( + "fee".to_string(), + Bytes::from(BigInt::from(500).to_signed_bytes_be()), + ); + attrs + }, + ..Default::default() + }, + token_in: usdc.clone(), + token_out: weth.clone(), + split: 0.0f64, + }; + + let swap_weth_usdc_v3_pool1 = Swap { + component: ProtocolComponent { + id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3 + * Pool 1 */ + protocol_system: "uniswap_v3".to_string(), + static_attributes: { + let mut attrs = HashMap::new(); + attrs.insert( + "fee".to_string(), + Bytes::from(BigInt::from(500).to_signed_bytes_be()), + ); + attrs + }, + ..Default::default() + }, + token_in: weth.clone(), + token_out: usdc.clone(), + split: 0.6f64, + }; + + let swap_weth_usdc_v3_pool2 = Swap { + component: ProtocolComponent { + id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3 + * Pool 2 */ protocol_system: "uniswap_v3".to_string(), static_attributes: { let mut attrs = HashMap::new(); @@ -1509,50 +1913,32 @@ mod tests { ..Default::default() }, token_in: weth.clone(), - token_out: wbtc.clone(), - split: 0f64, + token_out: usdc.clone(), + split: 0.0f64, }; - let swap_wbtc_usdt = Swap { - component: ProtocolComponent { - id: String::from("0xD51a44d3FaE010294C616388b506AcdA1bfAAE46"), - protocol_system: String::from("vm:curve"), - static_attributes: { - let mut attrs: HashMap = HashMap::new(); - attrs.insert( - "factory".into(), - Bytes::from( - "0x0000000000000000000000000000000000000000" - .as_bytes() - .to_vec(), - ), - ); - attrs - }, - ..Default::default() - }, - token_in: wbtc.clone(), - token_out: usdt.clone(), - split: 0f64, - }; let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SequentialSwapStrategyEncoder::new( + let encoder = SplitSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - None, - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + Some(private_key.clone()), + Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), ) .unwrap(); + let solution = Solution { exact_out: false, - given_token: weth, - given_amount: BigUint::from_str("1_000000000000000000").unwrap(), - checked_token: usdt, + given_token: usdc.clone(), + given_amount: BigUint::from_str("100000000").unwrap(), // 100 USDC (6 decimals) + checked_token: usdc.clone(), expected_amount: None, - checked_amount: Some(BigUint::from_str("26173932").unwrap()), + checked_amount: Some(BigUint::from_str("99525908").unwrap()), /* Expected output + * from + * test */ sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap_weth_wbtc, swap_wbtc_usdt], + slippage: None, + swaps: vec![swap_usdc_weth_v2, swap_weth_usdc_v3_pool1, swap_weth_usdc_v3_pool2], ..Default::default() }; @@ -1560,64 +1946,255 @@ mod tests { .encode_strategy(solution) .unwrap(); - let _hex_calldata = encode(&calldata); - println!("test_uniswap_v3_curve: {}", _hex_calldata); + let hex_calldata = hex::encode(&calldata); + let expected_input = [ + "7c553846", // selector + "0000000000000000000000000000000000000000000000000000000005f5e100", // given amount + "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // given token + "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // checked token + "0000000000000000000000000000000000000000000000000000000005eea514", // min amount out + "0000000000000000000000000000000000000000000000000000000000000000", // wrap action + "0000000000000000000000000000000000000000000000000000000000000000", // unwrap action + "0000000000000000000000000000000000000000000000000000000000000002", // tokens length + "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver + ] + .join(""); + + let expected_swaps = [ + "0000000000000000000000000000000000000000000000000000000000000139", // length of ple encoded swaps without padding + "0057", // ple encoded swaps + "00", // token in index + "01", // token out index + "000000", // split + "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address + "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in + "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id + "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver + "01", // zero2one + "02", // transfer type + "006e", // ple encoded swaps + "01", // token in index + "00", // token out index + "999999", // split + "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in + "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token out + "0001f4", // pool fee + "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver + "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id + "00", // zero2one + "00", // transfer type + "006e", // ple encoded swaps + "01", // token in index + "00", // token out index + "000000", // split + "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in + "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token out + "000bb8", // pool fee + "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver + "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id + "00", // zero2one + "00", // transfer type + "00000000000000" // padding + ] + .join(""); + + assert_eq!(hex_calldata[..520], expected_input); + assert_eq!(hex_calldata[1288..], expected_swaps); + println!("test_split_output_cyclic_swap: {}", hex_calldata); + } + } + + mod protocol_integration { + // in this module we test protocol specific logic by creating the calldata that then is used + // in the solidity tests + use super::*; + + #[test] + fn test_single_encoding_strategy_ekubo() { + // ETH ──(EKUBO)──> USDC + + let token_in = Bytes::from(Address::ZERO.as_slice()); + let token_out = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC + + let static_attributes = HashMap::from([ + ("fee".to_string(), Bytes::from(0_u64)), + ("tick_spacing".to_string(), Bytes::from(0_u32)), + ( + "extension".to_string(), + Bytes::from("0x51d02a5948496a67827242eabc5725531342527c"), + ), /* Oracle */ + ]); + + let component = ProtocolComponent { + // All Ekubo swaps go through the core contract - not necessary to specify pool id + // for test + protocol_system: "ekubo_v2".to_string(), + static_attributes, + ..Default::default() + }; + + let swap = Swap { + component, + token_in: token_in.clone(), + token_out: token_out.clone(), + split: 0f64, + }; + + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = SingleSwapStrategyEncoder::new( + eth_chain(), + swap_encoder_registry, + None, + Bytes::from_str("0xA4AD4f68d0b91CFD19687c881e50f3A00242828c").unwrap(), + ) + .unwrap(); + + let solution = Solution { + exact_out: false, + given_token: token_in, + given_amount: BigUint::from_str("1_000000000000000000").unwrap(), + checked_token: token_out, + expected_amount: None, + checked_amount: Some(BigUint::from_str("1").unwrap()), + slippage: None, + // Alice + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + swaps: vec![swap], + ..Default::default() + }; + + let (calldata, _) = encoder + .encode_strategy(solution) + .unwrap(); + + let hex_calldata = encode(&calldata); + println!("test_single_encoding_strategy_ekubo: {}", hex_calldata); } #[test] - fn test_balancer_v2_uniswap_v2() { + fn test_single_encoding_strategy_usv4_eth_in() { + // Performs a single swap from ETH to PEPE using a USV4 pool // Note: This test does not assert anything. It is only used to obtain integration test // data for our router solidity test. // - // Performs a sequential swap from WETH to USDC though WBTC using balancer and USV2 - // pools + // ETH ───(USV4)──> PEPE // - // WETH ───(balancer)──> WBTC ───(USV2)──> USDC + // Set up a mock private key for signing (Alice's pk in our router tests) + let private_key = + "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - let weth = weth(); - let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(); - let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); + let eth = eth(); + let pepe = Bytes::from_str("0x6982508145454Ce325dDbE47a25d4ec3d2311933").unwrap(); - let swap_weth_wbtc = Swap { + let pool_fee_eth_pepe = Bytes::from(BigInt::from(25000).to_signed_bytes_be()); + let tick_spacing_eth_pepe = Bytes::from(BigInt::from(500).to_signed_bytes_be()); + let mut static_attributes_eth_pepe: HashMap = HashMap::new(); + static_attributes_eth_pepe.insert("key_lp_fee".into(), pool_fee_eth_pepe); + static_attributes_eth_pepe.insert("tick_spacing".into(), tick_spacing_eth_pepe); + + let swap_eth_pepe = Swap { component: ProtocolComponent { - id: "0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e" + id: "0xecd73ecbf77219f21f129c8836d5d686bbc27d264742ddad620500e3e548e2c9" .to_string(), - protocol_system: "vm:balancer_v2".to_string(), + protocol_system: "uniswap_v4".to_string(), + static_attributes: static_attributes_eth_pepe, ..Default::default() }, - token_in: weth.clone(), - token_out: wbtc.clone(), - split: 0f64, - }; - - let swap_wbtc_usdc = Swap { - component: ProtocolComponent { - id: "0x004375Dff511095CC5A197A54140a24eFEF3A416".to_string(), - protocol_system: "uniswap_v2".to_string(), - ..Default::default() - }, - token_in: wbtc.clone(), - token_out: usdc.clone(), + token_in: eth.clone(), + token_out: pepe.clone(), split: 0f64, }; let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SequentialSwapStrategyEncoder::new( + let encoder = SingleSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - None, - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + Some(private_key), + Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), ) .unwrap(); + let solution = Solution { exact_out: false, - given_token: weth, + given_token: eth, given_amount: BigUint::from_str("1_000000000000000000").unwrap(), - checked_token: usdc, + checked_token: pepe, expected_amount: None, - checked_amount: Some(BigUint::from_str("26173932").unwrap()), + checked_amount: Some(BigUint::from_str("242373460199848577067005852").unwrap()), + slippage: None, sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap_weth_wbtc, swap_wbtc_usdc], + swaps: vec![swap_eth_pepe], + ..Default::default() + }; + + let (calldata, _) = encoder + .encode_strategy(solution) + .unwrap(); + let hex_calldata = encode(&calldata); + + println!("test_single_encoding_strategy_usv4_eth_in: {}", hex_calldata); + } + + #[test] + fn test_single_encoding_strategy_usv4_eth_out() { + // Performs a single swap from USDC to ETH using a USV4 pool + // Note: This test does not assert anything. It is only used to obtain integration test + // data for our router solidity test. + // + // USDC ───(USV4)──> ETH + // + // Set up a mock private key for signing (Alice's pk in our router tests) + let private_key = + "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); + + let eth = eth(); + let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); + + // Fee and tick spacing information for this test is obtained by querying the + // USV4 Position Manager contract: 0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e + // Using the poolKeys function with the first 25 bytes of the pool id + let pool_fee_usdc_eth = Bytes::from(BigInt::from(3000).to_signed_bytes_be()); + let tick_spacing_usdc_eth = Bytes::from(BigInt::from(60).to_signed_bytes_be()); + let mut static_attributes_usdc_eth: HashMap = HashMap::new(); + static_attributes_usdc_eth.insert("key_lp_fee".into(), pool_fee_usdc_eth); + static_attributes_usdc_eth.insert("tick_spacing".into(), tick_spacing_usdc_eth); + + let swap_usdc_eth = Swap { + component: ProtocolComponent { + id: "0xdce6394339af00981949f5f3baf27e3610c76326a700af57e4b3e3ae4977f78d" + .to_string(), + protocol_system: "uniswap_v4".to_string(), + static_attributes: static_attributes_usdc_eth, + ..Default::default() + }, + token_in: usdc.clone(), + token_out: eth.clone(), + split: 0f64, + }; + + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = SplitSwapStrategyEncoder::new( + eth_chain(), + swap_encoder_registry, + Some(private_key), + Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + ) + .unwrap(); + + let solution = Solution { + exact_out: false, + given_token: usdc, + given_amount: BigUint::from_str("3000_000000").unwrap(), + checked_token: eth, + expected_amount: None, + checked_amount: Some(BigUint::from_str("1117254495486192350").unwrap()), + slippage: None, + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + swaps: vec![swap_usdc_eth], ..Default::default() }; @@ -1625,1089 +2202,260 @@ mod tests { .encode_strategy(solution) .unwrap(); - let _hex_calldata = encode(&calldata); - println!("test_balancer_v2_uniswap_v2: {}", _hex_calldata); + let hex_calldata = encode(&calldata); + println!("test_single_encoding_strategy_usv4_eth_out: {}", hex_calldata); } - } - #[test] - fn test_split_encoding_strategy_usv4() { - // Performs a sequential swap from USDC to PEPE though ETH using two consecutive USV4 pools - // - // USDC ──(USV4)──> ETH ───(USV4)──> PEPE - // - // Set up a mock private key for signing (Alice's pk in our router tests) - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); + #[test] + fn test_sequential_encoding_strategy_usv4() { + // Performs a sequential swap from USDC to PEPE though ETH using two consecutive USV4 + // pools + // + // USDC ──(USV4)──> ETH ───(USV4)──> PEPE + // - let eth = eth(); - let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); - let pepe = Bytes::from_str("0x6982508145454Ce325dDbE47a25d4ec3d2311933").unwrap(); + // Set up a mock private key for signing (Alice's pk in our router tests) + let private_key = + "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - // Fee and tick spacing information for this test is obtained by querying the - // USV4 Position Manager contract: 0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e - // Using the poolKeys function with the first 25 bytes of the pool id - let pool_fee_usdc_eth = Bytes::from(BigInt::from(3000).to_signed_bytes_be()); - let tick_spacing_usdc_eth = Bytes::from(BigInt::from(60).to_signed_bytes_be()); - let mut static_attributes_usdc_eth: HashMap = HashMap::new(); - static_attributes_usdc_eth.insert("key_lp_fee".into(), pool_fee_usdc_eth); - static_attributes_usdc_eth.insert("tick_spacing".into(), tick_spacing_usdc_eth); + let eth = eth(); + let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); + let pepe = Bytes::from_str("0x6982508145454Ce325dDbE47a25d4ec3d2311933").unwrap(); - let pool_fee_eth_pepe = Bytes::from(BigInt::from(25000).to_signed_bytes_be()); - let tick_spacing_eth_pepe = Bytes::from(BigInt::from(500).to_signed_bytes_be()); - let mut static_attributes_eth_pepe: HashMap = HashMap::new(); - static_attributes_eth_pepe.insert("key_lp_fee".into(), pool_fee_eth_pepe); - static_attributes_eth_pepe.insert("tick_spacing".into(), tick_spacing_eth_pepe); + // Fee and tick spacing information for this test is obtained by querying the + // USV4 Position Manager contract: 0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e + // Using the poolKeys function with the first 25 bytes of the pool id + let pool_fee_usdc_eth = Bytes::from(BigInt::from(3000).to_signed_bytes_be()); + let tick_spacing_usdc_eth = Bytes::from(BigInt::from(60).to_signed_bytes_be()); + let mut static_attributes_usdc_eth: HashMap = HashMap::new(); + static_attributes_usdc_eth.insert("key_lp_fee".into(), pool_fee_usdc_eth); + static_attributes_usdc_eth.insert("tick_spacing".into(), tick_spacing_usdc_eth); - let swap_usdc_eth = Swap { - component: ProtocolComponent { - id: "0xdce6394339af00981949f5f3baf27e3610c76326a700af57e4b3e3ae4977f78d" - .to_string(), - protocol_system: "uniswap_v4".to_string(), - static_attributes: static_attributes_usdc_eth, - ..Default::default() - }, - token_in: usdc.clone(), - token_out: eth.clone(), - split: 0f64, - }; + let pool_fee_eth_pepe = Bytes::from(BigInt::from(25000).to_signed_bytes_be()); + let tick_spacing_eth_pepe = Bytes::from(BigInt::from(500).to_signed_bytes_be()); + let mut static_attributes_eth_pepe: HashMap = HashMap::new(); + static_attributes_eth_pepe.insert("key_lp_fee".into(), pool_fee_eth_pepe); + static_attributes_eth_pepe.insert("tick_spacing".into(), tick_spacing_eth_pepe); - let swap_eth_pepe = Swap { - component: ProtocolComponent { - id: "0xecd73ecbf77219f21f129c8836d5d686bbc27d264742ddad620500e3e548e2c9" - .to_string(), - protocol_system: "uniswap_v4".to_string(), - static_attributes: static_attributes_eth_pepe, - ..Default::default() - }, - token_in: eth.clone(), - token_out: pepe.clone(), - split: 0f64, - }; - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SplitSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - Some(private_key), - Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), - ) - .unwrap(); - let solution = Solution { - exact_out: false, - given_token: usdc, - given_amount: BigUint::from_str("1000_000000").unwrap(), - checked_token: pepe, - expected_amount: None, - checked_amount: Some(BigUint::from_str("97191013220606467325121599").unwrap()), - slippage: None, - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap_usdc_eth, swap_eth_pepe], - ..Default::default() - }; - - let (calldata, _) = encoder - .encode_strategy(solution) - .unwrap(); - - let expected_input = [ - "7c553846", // Function selector - "000000000000000000000000000000000000000000000000000000003b9aca00", // amount in - "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in - "0000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933", // token out - "0000000000000000000000000000000000000000005064ff624d54346285543f", // min amount out - "0000000000000000000000000000000000000000000000000000000000000000", // wrap - "0000000000000000000000000000000000000000000000000000000000000000", // unwrap - // tokens length (not including intermediary tokens of USV4-optimized swaps) - "0000000000000000000000000000000000000000000000000000000000000002", - "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - ] - .join(""); - - // after this there is the permit and because of the deadlines (that depend on block time) - // it's hard to assert - // "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in - // "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in - // "0000000000000000000000000000000000000000000000000000000067c205fe", // expiration - // "0000000000000000000000000000000000000000000000000000000000000000", // nonce - // "0000000000000000000000002c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f4", // spender - // "00000000000000000000000000000000000000000000000000000000679a8006", // deadline - // offset of signature (from start of call data to beginning of length indication) - // "0000000000000000000000000000000000000000000000000000000000000200", - // offset of ple encoded swaps (from start of call data to beginning of length indication) - // "0000000000000000000000000000000000000000000000000000000000000280", - // length of signature without padding - // "0000000000000000000000000000000000000000000000000000000000000041", - // signature + padding - // "a031b63a01ef5d25975663e5d6c420ef498e3a5968b593cdf846c6729a788186", - // "1ddaf79c51453cd501d321ee541d13593e3a266be44103eefdf6e76a032d2870", - // "1b00000000000000000000000000000000000000000000000000000000000000" - - let expected_swaps = String::from(concat!( - // length of ple encoded swaps without padding - "000000000000000000000000000000000000000000000000000000000000008d", - // ple encoded swaps - "008b", // Swap length - "00", // token in index - "01", // token out index - "000000", // split - // Swap data header - "f62849f9a0b5bf2913b396098f7c7019b51a820a", // executor address - // Protocol data - "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // group token in - "6982508145454ce325ddbe47a25d4ec3d2311933", // group token in - "00", // zero2one - "04", // transfer type (transfer to router) - "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - // First pool params - "0000000000000000000000000000000000000000", // intermediary token (ETH) - "000bb8", // fee - "00003c", // tick spacing - // Second pool params - "6982508145454ce325ddbe47a25d4ec3d2311933", // intermediary token (PEPE) - "0061a8", // fee - "0001f4", // tick spacing - "00000000000000000000000000000000000000" // padding - )); - - let hex_calldata = encode(&calldata); - - assert_eq!(hex_calldata[..520], expected_input); - assert_eq!(hex_calldata[1288..], expected_swaps); - println!("test_split_encoding_strategy_usv4: {}", hex_calldata); - } - - #[test] - fn test_split_encoding_strategy_ekubo() { - // ETH ──(EKUBO)──> USDC - - let token_in = Bytes::from(Address::ZERO.as_slice()); - let token_out = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC - - let static_attributes = HashMap::from([ - ("fee".to_string(), Bytes::from(0_u64)), - ("tick_spacing".to_string(), Bytes::from(0_u32)), - ("extension".to_string(), Bytes::from("0x51d02a5948496a67827242eabc5725531342527c")), /* Oracle */ - ]); - - let component = ProtocolComponent { - // All Ekubo swaps go through the core contract - not necessary to specify pool id - // for test - protocol_system: "ekubo_v2".to_string(), - static_attributes, - ..Default::default() - }; - - let swap = Swap { - component, - token_in: token_in.clone(), - token_out: token_out.clone(), - split: 0f64, - }; - - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SplitSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - None, - Bytes::from_str("0xA4AD4f68d0b91CFD19687c881e50f3A00242828c").unwrap(), - ) - .unwrap(); - - let solution = Solution { - exact_out: false, - given_token: token_in, - given_amount: BigUint::from_str("1_000000000000000000").unwrap(), - checked_token: token_out, - expected_amount: None, - checked_amount: Some(BigUint::from_str("1").unwrap()), - slippage: None, - // Alice - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap], - ..Default::default() - }; - - let (calldata, _) = encoder - .encode_strategy(solution) - .unwrap(); - - let hex_calldata = encode(&calldata); - println!("test_split_encoding_strategy_ekubo: {}", hex_calldata); - } - - #[test] - fn test_single_swap_strategy_encoder_no_permit2() { - // Performs a single swap from WETH to DAI on a USV2 pool, without permit2 and no grouping - // optimizations. - - let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); - let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); - - let expected_amount = Some(BigUint::from_str("2_650_000000000000000000").unwrap()); - let slippage = Some(0.01f64); - let checked_amount = Some(BigUint::from_str("2_640_000000000000000000").unwrap()); - let expected_min_amount = U256::from_str("2_640_000000000000000000").unwrap(); - - let swap = Swap { - component: ProtocolComponent { - id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), - protocol_system: "uniswap_v2".to_string(), - ..Default::default() - }, - token_in: weth.clone(), - token_out: dai.clone(), - split: 0f64, - }; - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SingleSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - None, - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), - ) - .unwrap(); - let solution = Solution { - exact_out: false, - given_token: weth, - given_amount: BigUint::from_str("1_000000000000000000").unwrap(), - checked_token: dai, - expected_amount, - slippage, - checked_amount, - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap], - ..Default::default() - }; - - let (calldata, _) = encoder - .encode_strategy(solution) - .unwrap(); - let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); - let expected_input = [ - "20144a07", // Function selector - "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in - "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in - "0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out - &expected_min_amount_encoded, // min amount out - "0000000000000000000000000000000000000000000000000000000000000000", // wrap - "0000000000000000000000000000000000000000000000000000000000000000", // unwrap - "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - "0000000000000000000000000000000000000000000000000000000000000100", // offset of swap bytes - "0000000000000000000000000000000000000000000000000000000000000052", // length of swap bytes without padding - - // Swap data - "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address - "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in - "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id - "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - "00", // zero2one - "01", // transfer type - "0000000000000000000000000000", // padding - ] - .join(""); - - let hex_calldata = encode(&calldata); - - assert_eq!(hex_calldata, expected_input); - println!("test_single_swap_strategy_encoder_no_permit2: {}", hex_calldata); - } - - #[test] - fn test_split_swap_strategy_encoder_no_permit2() { - // Performs a single swap from WETH to DAI on a USV2 pool, without permit2 and no grouping - // optimizations. - - let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); - let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); - - let expected_amount = Some(BigUint::from_str("2_650_000000000000000000").unwrap()); - let slippage = Some(0.01f64); - let checked_amount = Some(BigUint::from_str("2_640_000000000000000000").unwrap()); - let expected_min_amount = U256::from_str("2_640_000000000000000000").unwrap(); - - let swap = Swap { - component: ProtocolComponent { - id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), - protocol_system: "uniswap_v2".to_string(), - ..Default::default() - }, - token_in: weth.clone(), - token_out: dai.clone(), - split: 0f64, - }; - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SplitSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - None, - Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), - ) - .unwrap(); - let solution = Solution { - exact_out: false, - given_token: weth, - given_amount: BigUint::from_str("1_000000000000000000").unwrap(), - checked_token: dai, - expected_amount, - slippage, - checked_amount, - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap], - ..Default::default() - }; - - let (calldata, _) = encoder - .encode_strategy(solution) - .unwrap(); - let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); - let expected_input = [ - "79b9b93b", // Function selector - "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount out - "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in - "0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out - &expected_min_amount_encoded, // min amount out - "0000000000000000000000000000000000000000000000000000000000000000", // wrap - "0000000000000000000000000000000000000000000000000000000000000000", // unwrap - "0000000000000000000000000000000000000000000000000000000000000002", // tokens length - "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - "0000000000000000000000000000000000000000000000000000000000000120", // offset of ple encoded swaps - "0000000000000000000000000000000000000000000000000000000000000059", // length of ple encoded swaps without padding - "0057", // ple encoded swaps - // Swap header - "00", // token in index - "01", // token out index - "000000", // split - // Swap data - "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address - "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in - "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id - "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - "00", // zero2one - "01", // transfer type - "00000000000000", // padding - ] - .join(""); - - let hex_calldata = encode(&calldata); - - assert_eq!(hex_calldata, expected_input); - println!("test_split_swap_strategy_encoder_no_permit2: {}", hex_calldata); - } - - #[test] - fn test_split_encoding_strategy_usv4_eth_in() { - // Performs a single swap from ETH to PEPE using a USV4 pool - // Note: This test does not assert anything. It is only used to obtain integration test - // data for our router solidity test. - // - // ETH ───(USV4)──> PEPE - // - // Set up a mock private key for signing (Alice's pk in our router tests) - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - - let eth = eth(); - let pepe = Bytes::from_str("0x6982508145454Ce325dDbE47a25d4ec3d2311933").unwrap(); - - let pool_fee_eth_pepe = Bytes::from(BigInt::from(25000).to_signed_bytes_be()); - let tick_spacing_eth_pepe = Bytes::from(BigInt::from(500).to_signed_bytes_be()); - let mut static_attributes_eth_pepe: HashMap = HashMap::new(); - static_attributes_eth_pepe.insert("key_lp_fee".into(), pool_fee_eth_pepe); - static_attributes_eth_pepe.insert("tick_spacing".into(), tick_spacing_eth_pepe); - - let swap_eth_pepe = Swap { - component: ProtocolComponent { - id: "0xecd73ecbf77219f21f129c8836d5d686bbc27d264742ddad620500e3e548e2c9" - .to_string(), - protocol_system: "uniswap_v4".to_string(), - static_attributes: static_attributes_eth_pepe, - ..Default::default() - }, - token_in: eth.clone(), - token_out: pepe.clone(), - split: 0f64, - }; - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SplitSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - Some(private_key), - Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), - ) - .unwrap(); - - let solution = Solution { - exact_out: false, - given_token: eth, - given_amount: BigUint::from_str("1_000000000000000000").unwrap(), - checked_token: pepe, - expected_amount: None, - checked_amount: Some(BigUint::from_str("242373460199848577067005852").unwrap()), - slippage: None, - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap_eth_pepe], - ..Default::default() - }; - - let (calldata, _) = encoder - .encode_strategy(solution) - .unwrap(); - let hex_calldata = encode(&calldata); - - println!("test_split_encoding_strategy_usv4_eth_in: {}", hex_calldata); - } - #[test] - fn test_split_encoding_strategy_usv4_eth_out() { - // Performs a single swap from USDC to ETH using a USV4 pool - // Note: This test does not assert anything. It is only used to obtain integration test - // data for our router solidity test. - // - // USDC ───(USV4)──> ETH - // - // Set up a mock private key for signing (Alice's pk in our router tests) - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - - let eth = eth(); - let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); - - // Fee and tick spacing information for this test is obtained by querying the - // USV4 Position Manager contract: 0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e - // Using the poolKeys function with the first 25 bytes of the pool id - let pool_fee_usdc_eth = Bytes::from(BigInt::from(3000).to_signed_bytes_be()); - let tick_spacing_usdc_eth = Bytes::from(BigInt::from(60).to_signed_bytes_be()); - let mut static_attributes_usdc_eth: HashMap = HashMap::new(); - static_attributes_usdc_eth.insert("key_lp_fee".into(), pool_fee_usdc_eth); - static_attributes_usdc_eth.insert("tick_spacing".into(), tick_spacing_usdc_eth); - - let swap_usdc_eth = Swap { - component: ProtocolComponent { - id: "0xdce6394339af00981949f5f3baf27e3610c76326a700af57e4b3e3ae4977f78d" - .to_string(), - protocol_system: "uniswap_v4".to_string(), - static_attributes: static_attributes_usdc_eth, - ..Default::default() - }, - token_in: usdc.clone(), - token_out: eth.clone(), - split: 0f64, - }; - - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SplitSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - Some(private_key), - Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), - ) - .unwrap(); - - let solution = Solution { - exact_out: false, - given_token: usdc, - given_amount: BigUint::from_str("3000_000000").unwrap(), - checked_token: eth, - expected_amount: None, - checked_amount: Some(BigUint::from_str("1117254495486192350").unwrap()), - slippage: None, - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap_usdc_eth], - ..Default::default() - }; - - let (calldata, _) = encoder - .encode_strategy(solution) - .unwrap(); - - let hex_calldata = encode(&calldata); - println!("test_split_encoding_strategy_usv4_eth_out: {}", hex_calldata); - } - - #[test] - fn test_cyclic_sequential_swap_split_strategy() { - // This test has start and end tokens that are the same - // The flow is: - // USDC -> WETH -> USDC using two pools - - // Set up a mock private key for signing (Alice's pk in our router tests) - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - - let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); - let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); - - // Create two Uniswap V3 pools for the cyclic swap - // USDC -> WETH (Pool 1) - let swap_usdc_weth = Swap { - component: ProtocolComponent { - id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3 - * Pool 1 */ - protocol_system: "uniswap_v3".to_string(), - static_attributes: { - let mut attrs = HashMap::new(); - attrs.insert( - "fee".to_string(), - Bytes::from(BigInt::from(500).to_signed_bytes_be()), - ); - attrs + let swap_usdc_eth = Swap { + component: ProtocolComponent { + id: "0xdce6394339af00981949f5f3baf27e3610c76326a700af57e4b3e3ae4977f78d" + .to_string(), + protocol_system: "uniswap_v4".to_string(), + static_attributes: static_attributes_usdc_eth, + ..Default::default() }, - ..Default::default() - }, - token_in: usdc.clone(), - token_out: weth.clone(), - split: 0f64, - }; + token_in: usdc.clone(), + token_out: eth.clone(), + split: 0f64, + }; - // WETH -> USDC (Pool 2) - let swap_weth_usdc = Swap { - component: ProtocolComponent { - id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3 - * Pool 2 */ - protocol_system: "uniswap_v3".to_string(), - static_attributes: { - let mut attrs = HashMap::new(); - attrs.insert( - "fee".to_string(), - Bytes::from(BigInt::from(3000).to_signed_bytes_be()), - ); - attrs + let swap_eth_pepe = Swap { + component: ProtocolComponent { + id: "0xecd73ecbf77219f21f129c8836d5d686bbc27d264742ddad620500e3e548e2c9" + .to_string(), + protocol_system: "uniswap_v4".to_string(), + static_attributes: static_attributes_eth_pepe, + ..Default::default() }, - ..Default::default() - }, - token_in: weth.clone(), - token_out: usdc.clone(), - split: 0f64, - }; - - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SplitSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - Some(private_key), - Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), - ) - .unwrap(); - - let solution = Solution { - exact_out: false, - given_token: usdc.clone(), - given_amount: BigUint::from_str("100000000").unwrap(), // 100 USDC (6 decimals) - checked_token: usdc.clone(), - expected_amount: None, - checked_amount: Some(BigUint::from_str("99889294").unwrap()), /* Expected output from - * test */ - slippage: None, - swaps: vec![swap_usdc_weth, swap_weth_usdc], - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - ..Default::default() - }; - - let (calldata, _) = encoder - .encode_strategy(solution) + token_in: eth.clone(), + token_out: pepe.clone(), + split: 0f64, + }; + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = SequentialSwapStrategyEncoder::new( + eth_chain(), + swap_encoder_registry, + Some(private_key), + Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + ) .unwrap(); - let hex_calldata = hex::encode(&calldata); - let expected_input = [ - "7c553846", // selector - "0000000000000000000000000000000000000000000000000000000005f5e100", // given amount - "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // given token - "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // checked token - "0000000000000000000000000000000000000000000000000000000005f4308e", // min amount out - "0000000000000000000000000000000000000000000000000000000000000000", // wrap action - "0000000000000000000000000000000000000000000000000000000000000000", // unwrap action - "0000000000000000000000000000000000000000000000000000000000000002", // tokens length - "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - ] - .join(""); - - let expected_swaps = [ - "00000000000000000000000000000000000000000000000000000000000000e0", // length of ple encoded swaps without padding - "006e", // ple encoded swaps - "00", // token in index - "01", // token out index - "000000", // split - "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address - "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in - "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token out - "0001f4", // pool fee - "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver - "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id - "01", // zero2one - "02", // transfer type - "006e", // ple encoded swaps - "01", // token in index - "00000000", // split - "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address - "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in - "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token out - "000bb8", // pool fee - "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id - "00", // zero2one - "00", // transfer type - ] - .join(""); - - assert_eq!(hex_calldata[..520], expected_input); - assert_eq!(hex_calldata[1288..], expected_swaps); - println!("test_cyclic_sequential_swap_split_strategy: {}", hex_calldata); - } - - #[test] - fn test_split_input_cyclic_swap() { - // This test has start and end tokens that are the same - // The flow is: - // ┌─ (USV3, 60% split) ──> WETH ─┐ - // │ │ - // USDC ──────┤ ├──(USV2)──> USDC - // │ │ - // └─ (USV3, 40% split) ──> WETH ─┘ - - // Set up a mock private key for signing (Alice's pk in our router tests) - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - - let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); - let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); - - // USDC -> WETH (Pool 1) - 60% of input - let swap_usdc_weth_pool1 = Swap { - component: ProtocolComponent { - id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3 - * Pool 1 */ - protocol_system: "uniswap_v3".to_string(), - static_attributes: { - let mut attrs = HashMap::new(); - attrs.insert( - "fee".to_string(), - Bytes::from(BigInt::from(500).to_signed_bytes_be()), - ); - attrs - }, + let solution = Solution { + exact_out: false, + given_token: usdc, + given_amount: BigUint::from_str("1000_000000").unwrap(), + checked_token: pepe, + expected_amount: None, + checked_amount: Some(BigUint::from_str("97191013220606467325121599").unwrap()), + slippage: None, + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + swaps: vec![swap_usdc_eth, swap_eth_pepe], ..Default::default() - }, - token_in: usdc.clone(), - token_out: weth.clone(), - split: 0.6f64, // 60% of input - }; + }; - // USDC -> WETH (Pool 2) - 40% of input (remaining) - let swap_usdc_weth_pool2 = Swap { - component: ProtocolComponent { - id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3 - * Pool 2 */ - protocol_system: "uniswap_v3".to_string(), - static_attributes: { - let mut attrs = HashMap::new(); - attrs.insert( - "fee".to_string(), - Bytes::from(BigInt::from(3000).to_signed_bytes_be()), - ); - attrs - }, + let (calldata, _) = encoder + .encode_strategy(solution) + .unwrap(); + + let expected_input = [ + "51bcc7b6", // Function selector + "000000000000000000000000000000000000000000000000000000003b9aca00", // amount in + "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in + "0000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933", // token out + "0000000000000000000000000000000000000000005064ff624d54346285543f", // min amount out + "0000000000000000000000000000000000000000000000000000000000000000", // wrap + "0000000000000000000000000000000000000000000000000000000000000000", // unwrap + "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver + ] + .join(""); + + // after this there is the permit and because of the deadlines (that depend on block + // time) it's hard to assert + + let expected_swaps = String::from(concat!( + // length of ple encoded swaps without padding + "0000000000000000000000000000000000000000000000000000000000000088", + // ple encoded swaps + "0086", // Swap length + // Swap data header + "f62849f9a0b5bf2913b396098f7c7019b51a820a", // executor address + // Protocol data + "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // group token in + "6982508145454ce325ddbe47a25d4ec3d2311933", // group token in + "00", // zero2one + "04", // transfer type (transfer to router) + "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver + // First pool params + "0000000000000000000000000000000000000000", // intermediary token (ETH) + "000bb8", // fee + "00003c", // tick spacing + // Second pool params + "6982508145454ce325ddbe47a25d4ec3d2311933", // intermediary token (PEPE) + "0061a8", // fee + "0001f4", // tick spacing + "000000000000000000000000000000000000000000000000" // padding + )); + + let hex_calldata = encode(&calldata); + + assert_eq!(hex_calldata[..456], expected_input); + assert_eq!(hex_calldata[1224..], expected_swaps); + println!("test_sequential_encoding_strategy_usv4: {}", hex_calldata); + } + + #[test] + fn test_single_encoding_strategy_curve() { + // UWU ──(curve 2 crypto pool)──> WETH + + let token_in = Bytes::from("0x55C08ca52497e2f1534B59E2917BF524D4765257"); // UWU + let token_out = Bytes::from("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); // USDC + + let static_attributes = HashMap::from([( + "factory".to_string(), + Bytes::from( + "0x98ee851a00abee0d95d08cf4ca2bdce32aeaaf7f" + .as_bytes() + .to_vec(), + ), + )]); + + let component = ProtocolComponent { + id: String::from("0x77146B0a1d08B6844376dF6d9da99bA7F1b19e71"), + protocol_system: String::from("vm:curve"), + static_attributes, ..Default::default() - }, - token_in: usdc.clone(), - token_out: weth.clone(), - split: 0f64, // Remaining 40% - }; + }; - // WETH -> USDC (Pool 2) - let swap_weth_usdc_pool2 = Swap { - component: ProtocolComponent { - id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(), /* USDC-WETH USV2 - * Pool 2 */ - protocol_system: "uniswap_v2".to_string(), - static_attributes: { - let mut attrs = HashMap::new(); - attrs.insert( - "fee".to_string(), - Bytes::from(BigInt::from(3000).to_signed_bytes_be()), - ); - attrs - }, - ..Default::default() - }, - token_in: weth.clone(), - token_out: usdc.clone(), - split: 0.0f64, - }; + let swap = Swap { + component, + token_in: token_in.clone(), + token_out: token_out.clone(), + split: 0f64, + }; - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SplitSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - Some(private_key.clone()), - Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), - ) - .unwrap(); - - let solution = Solution { - exact_out: false, - given_token: usdc.clone(), - given_amount: BigUint::from_str("100000000").unwrap(), // 100 USDC (6 decimals) - checked_token: usdc.clone(), - expected_amount: None, - checked_amount: Some(BigUint::from_str("99574171").unwrap()), /* Expected output from - * test */ - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - slippage: None, - swaps: vec![swap_usdc_weth_pool1, swap_usdc_weth_pool2, swap_weth_usdc_pool2], - ..Default::default() - }; - - let (calldata, _) = encoder - .encode_strategy(solution) + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = SingleSwapStrategyEncoder::new( + eth_chain(), + swap_encoder_registry, + None, + Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + ) .unwrap(); - let hex_calldata = hex::encode(&calldata); - let expected_input = [ - "7c553846", // selector - "0000000000000000000000000000000000000000000000000000000005f5e100", // given amount - "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // given token - "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // checked token - "0000000000000000000000000000000000000000000000000000000005ef619b", // min amount out - "0000000000000000000000000000000000000000000000000000000000000000", // wrap action - "0000000000000000000000000000000000000000000000000000000000000000", // unwrap action - "0000000000000000000000000000000000000000000000000000000000000002", // tokens length - "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - ] - .join(""); - let expected_swaps = [ - "0000000000000000000000000000000000000000000000000000000000000139", // length of ple encoded swaps without padding - "006e", // ple encoded swaps - "00", // token in index - "01", // token out index - "999999", // split - "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address - "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in - "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token out - "0001f4", // pool fee - "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver - "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id - "01", // zero2one - "02", // transfer type - "006e", // ple encoded swaps - "00", // token in index - "01", // token out index - "000000", // split - "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address - "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in - "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token out - "000bb8", // pool fee - "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver - "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id - "01", // zero2one - "02", // transfer type - "0057", // ple encoded swaps - "01", // token in index - "00", // token out index - "000000", // split - "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address, - "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in - "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id, - "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - "00", // zero2one - "00", // transfer type - "00000000000000" // padding - ] - .join(""); - assert_eq!(hex_calldata[..520], expected_input); - assert_eq!(hex_calldata[1288..], expected_swaps); - println!("test_split_input_cyclic_swap: {}", hex_calldata); - } - - #[test] - fn test_split_output_cyclic_swap() { - // This test has start and end tokens that are the same - // The flow is: - // ┌─── (USV3, 60% split) ───┐ - // │ │ - // USDC ──(USV2) ── WETH──| ├─> USDC - // │ │ - // └─── (USV3, 40% split) ───┘ - - // Set up a mock private key for signing (Alice's pk in our router tests) - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - - let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); - let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); - - let swap_usdc_weth_v2 = Swap { - component: ProtocolComponent { - id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(), // USDC-WETH USV2 - protocol_system: "uniswap_v2".to_string(), - static_attributes: { - let mut attrs = HashMap::new(); - attrs.insert( - "fee".to_string(), - Bytes::from(BigInt::from(500).to_signed_bytes_be()), - ); - attrs - }, + let solution = Solution { + exact_out: false, + given_token: token_in, + given_amount: BigUint::from_str("1_000000000000000000").unwrap(), + checked_token: token_out, + expected_amount: None, + checked_amount: Some(BigUint::from_str("1").unwrap()), + slippage: None, + // Alice + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + swaps: vec![swap], ..Default::default() - }, - token_in: usdc.clone(), - token_out: weth.clone(), - split: 0.0f64, - }; + }; - let swap_weth_usdc_v3_pool1 = Swap { - component: ProtocolComponent { - id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3 - * Pool 1 */ - protocol_system: "uniswap_v3".to_string(), - static_attributes: { - let mut attrs = HashMap::new(); - attrs.insert( - "fee".to_string(), - Bytes::from(BigInt::from(500).to_signed_bytes_be()), - ); - attrs - }, + let (calldata, _) = encoder + .encode_strategy(solution) + .unwrap(); + + let hex_calldata = encode(&calldata); + println!("test_split_encoding_strategy_curve: {}", hex_calldata); + } + + #[test] + fn test_single_encoding_strategy_curve_st_eth() { + // ETH ──(curve stETH pool)──> STETH + + let token_in = Bytes::from("0x0000000000000000000000000000000000000000"); // ETH + let token_out = Bytes::from("0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"); // STETH + + let static_attributes = HashMap::from([( + "factory".to_string(), + Bytes::from( + "0x0000000000000000000000000000000000000000" + .as_bytes() + .to_vec(), + ), + )]); + + let component = ProtocolComponent { + id: String::from("0xDC24316b9AE028F1497c275EB9192a3Ea0f67022"), + protocol_system: String::from("vm:curve"), + static_attributes, ..Default::default() - }, - token_in: weth.clone(), - token_out: usdc.clone(), - split: 0.6f64, - }; + }; - let swap_weth_usdc_v3_pool2 = Swap { - component: ProtocolComponent { - id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3 - * Pool 2 */ - protocol_system: "uniswap_v3".to_string(), - static_attributes: { - let mut attrs = HashMap::new(); - attrs.insert( - "fee".to_string(), - Bytes::from(BigInt::from(3000).to_signed_bytes_be()), - ); - attrs - }, - ..Default::default() - }, - token_in: weth.clone(), - token_out: usdc.clone(), - split: 0.0f64, - }; + let swap = Swap { + component, + token_in: token_in.clone(), + token_out: token_out.clone(), + split: 0f64, + }; - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SplitSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - Some(private_key.clone()), - Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), - ) - .unwrap(); - - let solution = Solution { - exact_out: false, - given_token: usdc.clone(), - given_amount: BigUint::from_str("100000000").unwrap(), // 100 USDC (6 decimals) - checked_token: usdc.clone(), - expected_amount: None, - checked_amount: Some(BigUint::from_str("99525908").unwrap()), /* Expected output from - * test */ - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - slippage: None, - swaps: vec![swap_usdc_weth_v2, swap_weth_usdc_v3_pool1, swap_weth_usdc_v3_pool2], - ..Default::default() - }; - - let (calldata, _) = encoder - .encode_strategy(solution) + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = SingleSwapStrategyEncoder::new( + eth_chain(), + swap_encoder_registry, + None, + Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + ) .unwrap(); - let hex_calldata = hex::encode(&calldata); - let expected_input = [ - "7c553846", // selector - "0000000000000000000000000000000000000000000000000000000005f5e100", // given amount - "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // given token - "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // checked token - "0000000000000000000000000000000000000000000000000000000005eea514", // min amount out - "0000000000000000000000000000000000000000000000000000000000000000", // wrap action - "0000000000000000000000000000000000000000000000000000000000000000", // unwrap action - "0000000000000000000000000000000000000000000000000000000000000002", // tokens length - "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - ] - .join(""); + let solution = Solution { + exact_out: false, + given_token: token_in, + given_amount: BigUint::from_str("1_000000000000000000").unwrap(), + checked_token: token_out, + expected_amount: None, + checked_amount: Some(BigUint::from_str("1").unwrap()), + slippage: None, + // Alice + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + swaps: vec![swap], + ..Default::default() + }; - let expected_swaps = [ - "0000000000000000000000000000000000000000000000000000000000000139", // length of ple encoded swaps without padding - "0057", // ple encoded swaps - "00", // token in index - "01", // token out index - "000000", // split - "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address - "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in - "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id - "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver - "01", // zero2one - "02", // transfer type - "006e", // ple encoded swaps - "01", // token in index - "00", // token out index - "999999", // split - "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address - "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in - "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token out - "0001f4", // pool fee - "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id - "00", // zero2one - "00", // transfer type - "006e", // ple encoded swaps - "01", // token in index - "00", // token out index - "000000", // split - "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address - "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in - "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token out - "000bb8", // pool fee - "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id - "00", // zero2one - "00", // transfer type - "00000000000000" // padding - ] - .join(""); + let (calldata, _) = encoder + .encode_strategy(solution) + .unwrap(); - assert_eq!(hex_calldata[..520], expected_input); - assert_eq!(hex_calldata[1288..], expected_swaps); - println!("test_split_output_cyclic_swap: {}", hex_calldata); - } - - #[test] - fn test_split_encoding_strategy_curve() { - // UWU ──(curve 2 crypto pool)──> WETH - - let token_in = Bytes::from("0x55C08ca52497e2f1534B59E2917BF524D4765257"); // UWU - let token_out = Bytes::from("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); // USDC - - let static_attributes = HashMap::from([( - "factory".to_string(), - Bytes::from( - "0x98ee851a00abee0d95d08cf4ca2bdce32aeaaf7f" - .as_bytes() - .to_vec(), - ), - )]); - - let component = ProtocolComponent { - id: String::from("0x77146B0a1d08B6844376dF6d9da99bA7F1b19e71"), - protocol_system: String::from("vm:curve"), - static_attributes, - ..Default::default() - }; - - let swap = Swap { - component, - token_in: token_in.clone(), - token_out: token_out.clone(), - split: 0f64, - }; - - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SplitSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - None, - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), - ) - .unwrap(); - - let solution = Solution { - exact_out: false, - given_token: token_in, - given_amount: BigUint::from_str("1_000000000000000000").unwrap(), - checked_token: token_out, - expected_amount: None, - checked_amount: Some(BigUint::from_str("1").unwrap()), - slippage: None, - // Alice - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap], - ..Default::default() - }; - - let (calldata, _) = encoder - .encode_strategy(solution) - .unwrap(); - - let hex_calldata = encode(&calldata); - println!("test_split_encoding_strategy_curve: {}", hex_calldata); - } - - #[test] - fn test_split_encoding_strategy_curve_st_eth() { - // ETH ──(curve stETH pool)──> STETH - - let token_in = Bytes::from("0x0000000000000000000000000000000000000000"); // ETH - let token_out = Bytes::from("0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"); // STETH - - let static_attributes = HashMap::from([( - "factory".to_string(), - Bytes::from( - "0x0000000000000000000000000000000000000000" - .as_bytes() - .to_vec(), - ), - )]); - - let component = ProtocolComponent { - id: String::from("0xDC24316b9AE028F1497c275EB9192a3Ea0f67022"), - protocol_system: String::from("vm:curve"), - static_attributes, - ..Default::default() - }; - - let swap = Swap { - component, - token_in: token_in.clone(), - token_out: token_out.clone(), - split: 0f64, - }; - - let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = SplitSwapStrategyEncoder::new( - eth_chain(), - swap_encoder_registry, - None, - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), - ) - .unwrap(); - - let solution = Solution { - exact_out: false, - given_token: token_in, - given_amount: BigUint::from_str("1_000000000000000000").unwrap(), - checked_token: token_out, - expected_amount: None, - checked_amount: Some(BigUint::from_str("1").unwrap()), - slippage: None, - // Alice - sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - swaps: vec![swap], - ..Default::default() - }; - - let (calldata, _) = encoder - .encode_strategy(solution) - .unwrap(); - - let hex_calldata = encode(&calldata); - println!("test_split_encoding_strategy_curve_st_eth: {}", hex_calldata); + let hex_calldata = encode(&calldata); + println!("test_single_encoding_strategy_curve_st_eth: {}", hex_calldata); + } } } diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index c2e803a..e0db5de 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -577,419 +577,435 @@ mod tests { use super::*; use crate::encoding::models::TransferType; - #[test] - fn test_encode_uniswap_v2() { - let usv2_pool = ProtocolComponent { - id: String::from("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"), - ..Default::default() - }; + mod uniswap_v2 { + use super::*; + #[test] + fn test_encode_uniswap_v2() { + let usv2_pool = ProtocolComponent { + id: String::from("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"), + ..Default::default() + }; - let token_in = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); - let token_out = Bytes::from("0x6b175474e89094c44da98b954eedeac495271d0f"); - let swap = Swap { - component: usv2_pool, - token_in: token_in.clone(), - token_out: token_out.clone(), - split: 0f64, - }; - let encoding_context = EncodingContext { - receiver: Bytes::from("0x0000000000000000000000000000000000000001"), - exact_out: false, - router_address: Some(Bytes::zero(20)), - group_token_in: token_in.clone(), - group_token_out: token_out.clone(), - transfer_type: TransferType::TransferToProtocol, - }; - let encoder = UniswapV2SwapEncoder::new( - String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), - TychoCoreChain::Ethereum.into(), - None, - ) - .unwrap(); - let encoded_swap = encoder - .encode_swap(swap, encoding_context) + let token_in = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); + let token_out = Bytes::from("0x6b175474e89094c44da98b954eedeac495271d0f"); + let swap = Swap { + component: usv2_pool, + token_in: token_in.clone(), + token_out: token_out.clone(), + split: 0f64, + }; + let encoding_context = EncodingContext { + receiver: Bytes::from("0x0000000000000000000000000000000000000001"), + exact_out: false, + router_address: Some(Bytes::zero(20)), + group_token_in: token_in.clone(), + group_token_out: token_out.clone(), + transfer_type: TransferType::TransferToProtocol, + }; + let encoder = UniswapV2SwapEncoder::new( + String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), + TychoCoreChain::Ethereum.into(), + None, + ) .unwrap(); - let hex_swap = encode(&encoded_swap); - assert_eq!( - hex_swap, - String::from(concat!( - // in token - "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - // component id - "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", - // receiver - "0000000000000000000000000000000000000001", - // zero for one - "00", - // transfer type (transfer) - "00", - )) - ); - } - #[test] - fn test_encode_uniswap_v3() { - let fee = BigInt::from(500); - let encoded_pool_fee = Bytes::from(fee.to_signed_bytes_be()); - let mut static_attributes: HashMap = HashMap::new(); - static_attributes.insert("fee".into(), Bytes::from(encoded_pool_fee.to_vec())); - - let usv3_pool = ProtocolComponent { - id: String::from("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"), - static_attributes, - ..Default::default() - }; - let token_in = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); - let token_out = Bytes::from("0x6b175474e89094c44da98b954eedeac495271d0f"); - let swap = Swap { - component: usv3_pool, - token_in: token_in.clone(), - token_out: token_out.clone(), - split: 0f64, - }; - let encoding_context = EncodingContext { - receiver: Bytes::from("0x0000000000000000000000000000000000000001"), - exact_out: false, - router_address: Some(Bytes::zero(20)), - group_token_in: token_in.clone(), - group_token_out: token_out.clone(), - transfer_type: TransferType::TransferToProtocol, - }; - let encoder = UniswapV3SwapEncoder::new( - String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), - TychoCoreChain::Ethereum.into(), - None, - ) - .unwrap(); - let encoded_swap = encoder - .encode_swap(swap, encoding_context) - .unwrap(); - let hex_swap = encode(&encoded_swap); - assert_eq!( - hex_swap, - String::from(concat!( - // in token - "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - // out token - "6b175474e89094c44da98b954eedeac495271d0f", - // fee - "0001f4", - // receiver - "0000000000000000000000000000000000000001", - // pool id - "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", - // zero for one - "00", - // transfer type (transfer) - "00", - )) - ); + let encoded_swap = encoder + .encode_swap(swap, encoding_context) + .unwrap(); + let hex_swap = encode(&encoded_swap); + assert_eq!( + hex_swap, + String::from(concat!( + // in token + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + // component id + "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", + // receiver + "0000000000000000000000000000000000000001", + // zero for one + "00", + // transfer type (transfer) + "00", + )) + ); + } } - #[test] - fn test_encode_balancer_v2() { - let balancer_pool = ProtocolComponent { - id: String::from("0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014"), - protocol_system: String::from("vm:balancer_v2"), - ..Default::default() - }; - let token_in = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); - let token_out = Bytes::from("0xba100000625a3754423978a60c9317c58a424e3D"); - let swap = Swap { - component: balancer_pool, - token_in: token_in.clone(), - token_out: token_out.clone(), - split: 0f64, - }; - let encoding_context = EncodingContext { - // The receiver was generated with `makeAddr("bob") using forge` - receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"), - exact_out: false, - router_address: Some(Bytes::zero(20)), - group_token_in: token_in.clone(), - group_token_out: token_out.clone(), - transfer_type: TransferType::None, - }; - let encoder = BalancerV2SwapEncoder::new( - String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), - TychoCoreChain::Ethereum.into(), - Some(HashMap::from([( - "vault_address".to_string(), - "0xba12222222228d8ba445958a75a0704d566bf2c8".to_string(), - )])), - ) - .unwrap(); - let encoded_swap = encoder - .encode_swap(swap, encoding_context) - .unwrap(); - let hex_swap = encode(&encoded_swap); + mod uniswap_v3 { + use super::*; + #[test] + fn test_encode_uniswap_v3() { + let fee = BigInt::from(500); + let encoded_pool_fee = Bytes::from(fee.to_signed_bytes_be()); + let mut static_attributes: HashMap = HashMap::new(); + static_attributes.insert("fee".into(), Bytes::from(encoded_pool_fee.to_vec())); - assert_eq!( - hex_swap, - String::from(concat!( - // token in - "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - // token out - "ba100000625a3754423978a60c9317c58a424e3d", - // pool id - "5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014", - // receiver - "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", - // approval needed - "01", - // transfer type - "05" - )) - ); + let usv3_pool = ProtocolComponent { + id: String::from("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"), + static_attributes, + ..Default::default() + }; + let token_in = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); + let token_out = Bytes::from("0x6b175474e89094c44da98b954eedeac495271d0f"); + let swap = Swap { + component: usv3_pool, + token_in: token_in.clone(), + token_out: token_out.clone(), + split: 0f64, + }; + let encoding_context = EncodingContext { + receiver: Bytes::from("0x0000000000000000000000000000000000000001"), + exact_out: false, + router_address: Some(Bytes::zero(20)), + group_token_in: token_in.clone(), + group_token_out: token_out.clone(), + transfer_type: TransferType::TransferToProtocol, + }; + let encoder = UniswapV3SwapEncoder::new( + String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), + TychoCoreChain::Ethereum.into(), + None, + ) + .unwrap(); + let encoded_swap = encoder + .encode_swap(swap, encoding_context) + .unwrap(); + let hex_swap = encode(&encoded_swap); + assert_eq!( + hex_swap, + String::from(concat!( + // in token + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + // out token + "6b175474e89094c44da98b954eedeac495271d0f", + // fee + "0001f4", + // receiver + "0000000000000000000000000000000000000001", + // pool id + "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", + // zero for one + "00", + // transfer type (transfer) + "00", + )) + ); + } } - #[test] - fn test_encode_uniswap_v4_simple_swap() { - let fee = BigInt::from(100); - let tick_spacing = BigInt::from(1); - let token_in = Bytes::from("0x4c9EDD5852cd905f086C759E8383e09bff1E68B3"); // USDE - let token_out = Bytes::from("0xdAC17F958D2ee523a2206206994597C13D831ec7"); // USDT + mod balancer_v2 { + use super::*; - let mut static_attributes: HashMap = HashMap::new(); - static_attributes.insert("key_lp_fee".into(), Bytes::from(fee.to_signed_bytes_be())); - static_attributes - .insert("tick_spacing".into(), Bytes::from(tick_spacing.to_signed_bytes_be())); - - let usv4_pool = ProtocolComponent { - // Pool manager - id: String::from("0x000000000004444c5dc75cB358380D2e3dE08A90"), - static_attributes, - ..Default::default() - }; - let swap = Swap { - component: usv4_pool, - token_in: token_in.clone(), - token_out: token_out.clone(), - split: 0f64, - }; - let encoding_context = EncodingContext { - // The receiver is ALICE to match the solidity tests - receiver: Bytes::from("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2"), - exact_out: false, - // Same as the executor address - router_address: Some(Bytes::from("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f")), - - group_token_in: token_in.clone(), - group_token_out: token_out.clone(), - transfer_type: TransferType::TransferToProtocol, - }; - let encoder = UniswapV4SwapEncoder::new( - String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"), - TychoCoreChain::Ethereum.into(), - None, - ) - .unwrap(); - let encoded_swap = encoder - .encode_swap(swap, encoding_context) + #[test] + fn test_encode_balancer_v2() { + let balancer_pool = ProtocolComponent { + id: String::from( + "0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014", + ), + protocol_system: String::from("vm:balancer_v2"), + ..Default::default() + }; + let token_in = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); + let token_out = Bytes::from("0xba100000625a3754423978a60c9317c58a424e3D"); + let swap = Swap { + component: balancer_pool, + token_in: token_in.clone(), + token_out: token_out.clone(), + split: 0f64, + }; + let encoding_context = EncodingContext { + // The receiver was generated with `makeAddr("bob") using forge` + receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"), + exact_out: false, + router_address: Some(Bytes::zero(20)), + group_token_in: token_in.clone(), + group_token_out: token_out.clone(), + transfer_type: TransferType::None, + }; + let encoder = BalancerV2SwapEncoder::new( + String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), + TychoCoreChain::Ethereum.into(), + Some(HashMap::from([( + "vault_address".to_string(), + "0xba12222222228d8ba445958a75a0704d566bf2c8".to_string(), + )])), + ) .unwrap(); - let hex_swap = encode(&encoded_swap); - println!("test_encode_uniswap_v4_simple_swap: {}", hex_swap); + let encoded_swap = encoder + .encode_swap(swap, encoding_context) + .unwrap(); + let hex_swap = encode(&encoded_swap); - assert_eq!( - hex_swap, - String::from(concat!( - // group token in - "4c9edd5852cd905f086c759e8383e09bff1e68b3", - // group token out - "dac17f958d2ee523a2206206994597c13d831ec7", - // zero for one - "01", - // transfer type - "00", - // receiver - "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", - // pool params: - // - intermediary token - "dac17f958d2ee523a2206206994597c13d831ec7", - // - fee - "000064", - // - tick spacing - "000001" - )) - ); + assert_eq!( + hex_swap, + String::from(concat!( + // token in + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + // token out + "ba100000625a3754423978a60c9317c58a424e3d", + // pool id + "5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014", + // receiver + "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", + // approval needed + "01", + // transfer type + "05" + )) + ); + } } - #[test] - fn test_encode_uniswap_v4_second_swap() { - let fee = BigInt::from(3000); - let tick_spacing = BigInt::from(60); - let group_token_in = Bytes::from("0x4c9EDD5852cd905f086C759E8383e09bff1E68B3"); // USDE - let token_in = Bytes::from("0xdAC17F958D2ee523a2206206994597C13D831ec7"); // USDT - let token_out = Bytes::from("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"); // WBTC + mod uniswap_v4 { + use super::*; - let mut static_attributes: HashMap = HashMap::new(); - static_attributes.insert("key_lp_fee".into(), Bytes::from(fee.to_signed_bytes_be())); - static_attributes - .insert("tick_spacing".into(), Bytes::from(tick_spacing.to_signed_bytes_be())); + #[test] + fn test_encode_uniswap_v4_simple_swap() { + let fee = BigInt::from(100); + let tick_spacing = BigInt::from(1); + let token_in = Bytes::from("0x4c9EDD5852cd905f086C759E8383e09bff1E68B3"); // USDE + let token_out = Bytes::from("0xdAC17F958D2ee523a2206206994597C13D831ec7"); // USDT - let usv4_pool = ProtocolComponent { - id: String::from("0x000000000004444c5dc75cB358380D2e3dE08A90"), - static_attributes, - ..Default::default() - }; + let mut static_attributes: HashMap = HashMap::new(); + static_attributes.insert("key_lp_fee".into(), Bytes::from(fee.to_signed_bytes_be())); + static_attributes + .insert("tick_spacing".into(), Bytes::from(tick_spacing.to_signed_bytes_be())); - let swap = Swap { - component: usv4_pool, - token_in: token_in.clone(), - token_out: token_out.clone(), - split: 0f64, - }; + let usv4_pool = ProtocolComponent { + // Pool manager + id: String::from("0x000000000004444c5dc75cB358380D2e3dE08A90"), + static_attributes, + ..Default::default() + }; + let swap = Swap { + component: usv4_pool, + token_in: token_in.clone(), + token_out: token_out.clone(), + split: 0f64, + }; + let encoding_context = EncodingContext { + // The receiver is ALICE to match the solidity tests + receiver: Bytes::from("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2"), + exact_out: false, + // Same as the executor address + router_address: Some(Bytes::from("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f")), - let encoding_context = EncodingContext { - receiver: Bytes::from("0x0000000000000000000000000000000000000001"), - exact_out: false, - router_address: Some(Bytes::zero(20)), - group_token_in: group_token_in.clone(), - // Token out is the same as the group token out - group_token_out: token_out.clone(), - transfer_type: TransferType::TransferToProtocol, - }; - - let encoder = UniswapV4SwapEncoder::new( - String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), - TychoCoreChain::Ethereum.into(), - None, - ) - .unwrap(); - let encoded_swap = encoder - .encode_swap(swap, encoding_context) + group_token_in: token_in.clone(), + group_token_out: token_out.clone(), + transfer_type: TransferType::TransferToProtocol, + }; + let encoder = UniswapV4SwapEncoder::new( + String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"), + TychoCoreChain::Ethereum.into(), + None, + ) .unwrap(); - let hex_swap = encode(&encoded_swap); + let encoded_swap = encoder + .encode_swap(swap, encoding_context) + .unwrap(); + let hex_swap = encode(&encoded_swap); + println!("test_encode_uniswap_v4_simple_swap: {}", hex_swap); - assert_eq!( - hex_swap, - String::from(concat!( - // pool params: - // - intermediary token (20 bytes) - "2260fac5e5542a773aa44fbcfedf7c193bc2c599", - // - fee (3 bytes) - "000bb8", - // - tick spacing (3 bytes) - "00003c" - )) - ); + assert_eq!( + hex_swap, + String::from(concat!( + // group token in + "4c9edd5852cd905f086c759e8383e09bff1e68b3", + // group token out + "dac17f958d2ee523a2206206994597c13d831ec7", + // zero for one + "01", + // transfer type + "00", + // receiver + "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", + // pool params: + // - intermediary token + "dac17f958d2ee523a2206206994597c13d831ec7", + // - fee + "000064", + // - tick spacing + "000001" + )) + ); + } + + #[test] + fn test_encode_uniswap_v4_second_swap() { + let fee = BigInt::from(3000); + let tick_spacing = BigInt::from(60); + let group_token_in = Bytes::from("0x4c9EDD5852cd905f086C759E8383e09bff1E68B3"); // USDE + let token_in = Bytes::from("0xdAC17F958D2ee523a2206206994597C13D831ec7"); // USDT + let token_out = Bytes::from("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"); // WBTC + + let mut static_attributes: HashMap = HashMap::new(); + static_attributes.insert("key_lp_fee".into(), Bytes::from(fee.to_signed_bytes_be())); + static_attributes + .insert("tick_spacing".into(), Bytes::from(tick_spacing.to_signed_bytes_be())); + + let usv4_pool = ProtocolComponent { + id: String::from("0x000000000004444c5dc75cB358380D2e3dE08A90"), + static_attributes, + ..Default::default() + }; + + let swap = Swap { + component: usv4_pool, + token_in: token_in.clone(), + token_out: token_out.clone(), + split: 0f64, + }; + + let encoding_context = EncodingContext { + receiver: Bytes::from("0x0000000000000000000000000000000000000001"), + exact_out: false, + router_address: Some(Bytes::zero(20)), + group_token_in: group_token_in.clone(), + // Token out is the same as the group token out + group_token_out: token_out.clone(), + transfer_type: TransferType::TransferToProtocol, + }; + + let encoder = UniswapV4SwapEncoder::new( + String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), + TychoCoreChain::Ethereum.into(), + None, + ) + .unwrap(); + let encoded_swap = encoder + .encode_swap(swap, encoding_context) + .unwrap(); + let hex_swap = encode(&encoded_swap); + + assert_eq!( + hex_swap, + String::from(concat!( + // pool params: + // - intermediary token (20 bytes) + "2260fac5e5542a773aa44fbcfedf7c193bc2c599", + // - fee (3 bytes) + "000bb8", + // - tick spacing (3 bytes) + "00003c" + )) + ); + } + + #[test] + fn test_encode_uniswap_v4_sequential_swap() { + let usde_address = Bytes::from("0x4c9EDD5852cd905f086C759E8383e09bff1E68B3"); + let usdt_address = Bytes::from("0xdAC17F958D2ee523a2206206994597C13D831ec7"); + let wbtc_address = Bytes::from("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"); + let router_address = Bytes::from("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f"); + + // The context is the same for both swaps, since the group token in and out are the same + let context = EncodingContext { + // The receiver is ALICE to match the solidity tests + receiver: Bytes::from("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2"), + exact_out: false, + router_address: Some(router_address.clone()), + group_token_in: usde_address.clone(), + group_token_out: wbtc_address.clone(), + transfer_type: TransferType::TransferToProtocol, + }; + + // Setup - First sequence: USDE -> USDT + let usde_usdt_fee = BigInt::from(100); + let usde_usdt_tick_spacing = BigInt::from(1); + + let mut usde_usdt_static_attributes: HashMap = HashMap::new(); + usde_usdt_static_attributes + .insert("key_lp_fee".into(), Bytes::from(usde_usdt_fee.to_signed_bytes_be())); + usde_usdt_static_attributes.insert( + "tick_spacing".into(), + Bytes::from(usde_usdt_tick_spacing.to_signed_bytes_be()), + ); + + let usde_usdt_component = ProtocolComponent { + id: String::from("0x000000000004444c5dc75cB358380D2e3dE08A90"), + static_attributes: usde_usdt_static_attributes, + ..Default::default() + }; + + // Setup - Second sequence: USDT -> WBTC + let usdt_wbtc_fee = BigInt::from(3000); + let usdt_wbtc_tick_spacing = BigInt::from(60); + + let mut usdt_wbtc_static_attributes: HashMap = HashMap::new(); + usdt_wbtc_static_attributes + .insert("key_lp_fee".into(), Bytes::from(usdt_wbtc_fee.to_signed_bytes_be())); + usdt_wbtc_static_attributes.insert( + "tick_spacing".into(), + Bytes::from(usdt_wbtc_tick_spacing.to_signed_bytes_be()), + ); + + let usdt_wbtc_component = ProtocolComponent { + id: String::from("0x000000000004444c5dc75cB358380D2e3dE08A90"), + static_attributes: usdt_wbtc_static_attributes, + ..Default::default() + }; + + let initial_swap = Swap { + component: usde_usdt_component, + token_in: usde_address.clone(), + token_out: usdt_address.clone(), + split: 0f64, + }; + + let second_swap = Swap { + component: usdt_wbtc_component, + token_in: usdt_address, + token_out: wbtc_address.clone(), + split: 0f64, + }; + + let encoder = UniswapV4SwapEncoder::new( + String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"), + TychoCoreChain::Ethereum.into(), + None, + ) + .unwrap(); + let initial_encoded_swap = encoder + .encode_swap(initial_swap, context.clone()) + .unwrap(); + let second_encoded_swap = encoder + .encode_swap(second_swap, context) + .unwrap(); + + let combined_hex = + format!("{}{}", encode(&initial_encoded_swap), encode(&second_encoded_swap)); + + println!("test_encode_uniswap_v4_sequential_swap: {}", combined_hex); + assert_eq!( + combined_hex, + String::from(concat!( + // group_token in + "4c9edd5852cd905f086c759e8383e09bff1e68b3", + // group_token out + "2260fac5e5542a773aa44fbcfedf7c193bc2c599", + // zero for one + "01", + // transfer type + "00", + // receiver + "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", + // pool params: + // - intermediary token USDT + "dac17f958d2ee523a2206206994597c13d831ec7", + // - fee + "000064", + // - tick spacing + "000001", + // - intermediary token WBTC + "2260fac5e5542a773aa44fbcfedf7c193bc2c599", + // - fee + "000bb8", + // - tick spacing + "00003c" + )) + ); + println!("{}", combined_hex) + } } - - #[test] - fn test_encode_uniswap_v4_sequential_swap() { - let usde_address = Bytes::from("0x4c9EDD5852cd905f086C759E8383e09bff1E68B3"); - let usdt_address = Bytes::from("0xdAC17F958D2ee523a2206206994597C13D831ec7"); - let wbtc_address = Bytes::from("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"); - let router_address = Bytes::from("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f"); - - // The context is the same for both swaps, since the group token in and out are the same - let context = EncodingContext { - // The receiver is ALICE to match the solidity tests - receiver: Bytes::from("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2"), - exact_out: false, - router_address: Some(router_address.clone()), - group_token_in: usde_address.clone(), - group_token_out: wbtc_address.clone(), - transfer_type: TransferType::TransferToProtocol, - }; - - // Setup - First sequence: USDE -> USDT - let usde_usdt_fee = BigInt::from(100); - let usde_usdt_tick_spacing = BigInt::from(1); - - let mut usde_usdt_static_attributes: HashMap = HashMap::new(); - usde_usdt_static_attributes - .insert("key_lp_fee".into(), Bytes::from(usde_usdt_fee.to_signed_bytes_be())); - usde_usdt_static_attributes.insert( - "tick_spacing".into(), - Bytes::from(usde_usdt_tick_spacing.to_signed_bytes_be()), - ); - - let usde_usdt_component = ProtocolComponent { - id: String::from("0x000000000004444c5dc75cB358380D2e3dE08A90"), - static_attributes: usde_usdt_static_attributes, - ..Default::default() - }; - - // Setup - Second sequence: USDT -> WBTC - let usdt_wbtc_fee = BigInt::from(3000); - let usdt_wbtc_tick_spacing = BigInt::from(60); - - let mut usdt_wbtc_static_attributes: HashMap = HashMap::new(); - usdt_wbtc_static_attributes - .insert("key_lp_fee".into(), Bytes::from(usdt_wbtc_fee.to_signed_bytes_be())); - usdt_wbtc_static_attributes.insert( - "tick_spacing".into(), - Bytes::from(usdt_wbtc_tick_spacing.to_signed_bytes_be()), - ); - - let usdt_wbtc_component = ProtocolComponent { - id: String::from("0x000000000004444c5dc75cB358380D2e3dE08A90"), - static_attributes: usdt_wbtc_static_attributes, - ..Default::default() - }; - - let initial_swap = Swap { - component: usde_usdt_component, - token_in: usde_address.clone(), - token_out: usdt_address.clone(), - split: 0f64, - }; - - let second_swap = Swap { - component: usdt_wbtc_component, - token_in: usdt_address, - token_out: wbtc_address.clone(), - split: 0f64, - }; - - let encoder = UniswapV4SwapEncoder::new( - String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"), - TychoCoreChain::Ethereum.into(), - None, - ) - .unwrap(); - let initial_encoded_swap = encoder - .encode_swap(initial_swap, context.clone()) - .unwrap(); - let second_encoded_swap = encoder - .encode_swap(second_swap, context) - .unwrap(); - - let combined_hex = - format!("{}{}", encode(&initial_encoded_swap), encode(&second_encoded_swap)); - - println!("test_encode_uniswap_v4_sequential_swap: {}", combined_hex); - assert_eq!( - combined_hex, - String::from(concat!( - // group_token in - "4c9edd5852cd905f086c759e8383e09bff1e68b3", - // group_token out - "2260fac5e5542a773aa44fbcfedf7c193bc2c599", - // zero for one - "01", - // transfer type - "00", - // receiver - "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", - // pool params: - // - intermediary token USDT - "dac17f958d2ee523a2206206994597c13d831ec7", - // - fee - "000064", - // - tick spacing - "000001", - // - intermediary token WBTC - "2260fac5e5542a773aa44fbcfedf7c193bc2c599", - // - fee - "000bb8", - // - tick spacing - "00003c" - )) - ); - println!("{}", combined_hex) - } - mod ekubo { use super::*;