diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index 569072f..9a8c1e6 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.26; import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "./TokenTransfer.sol"; error CurveExecutor__AddressZero(); @@ -32,12 +33,14 @@ interface CryptoPoolETH { // slither-disable-end naming-convention } -contract CurveExecutor is IExecutor { +contract CurveExecutor is IExecutor, TokenTransfer { using SafeERC20 for IERC20; address public immutable nativeToken; - constructor(address _nativeToken) { + constructor(address _nativeToken, address _permit2) + TokenTransfer(_permit2) + { if (_nativeToken == address(0)) { revert CurveExecutor__AddressZero(); } @@ -57,9 +60,12 @@ contract CurveExecutor is IExecutor { uint8 poolType, int128 i, int128 j, - bool tokenApprovalNeeded + bool tokenApprovalNeeded, + TransferType transferType ) = _decodeData(data); + _transfer(tokenIn, msg.sender, pool, amountIn, transferType); + if (tokenApprovalNeeded && tokenIn != nativeToken) { // slither-disable-next-line unused-return IERC20(tokenIn).approve(address(pool), type(uint256).max); @@ -105,7 +111,8 @@ contract CurveExecutor is IExecutor { uint8 poolType, int128 i, int128 j, - bool tokenApprovalNeeded + bool tokenApprovalNeeded, + TransferType transferType ) { tokenIn = address(bytes20(data[0:20])); @@ -115,6 +122,7 @@ contract CurveExecutor is IExecutor { i = int128(uint128(uint8(data[61]))); j = int128(uint128(uint8(data[62]))); tokenApprovalNeeded = data[63] != 0; + transferType = TransferType(uint8(data[64])); } receive() external payable { diff --git a/foundry/test/TychoRouterIntegration.t.sol b/foundry/test/TychoRouterIntegration.t.sol index 55e0ae3..0f25861 100644 --- a/foundry/test/TychoRouterIntegration.t.sol +++ b/foundry/test/TychoRouterIntegration.t.sol @@ -257,6 +257,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f4308e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006821647000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9de780000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000413c8b048dc7b7614106a5aa1fa13e48c02a6a9714dfa07d2c424f68b81a5f828c39ace62f2dd57d7bfad10910ae44f77d68aec5c079fce456028b1bd7f72053151c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400102006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80000" ); + assertTrue(success, "Call Failed"); assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99889294); vm.stopPrank(); @@ -273,6 +274,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ef619b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006821659d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9dfa5000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041dd84c5cdc51719e377598eccd8eac0aae036e7e0745a7c65b5d44cc817071a7460ccc73934363f33cc7af71dc07545aeff1d92f8c2f0b2973e1fc37e7b2de3551c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139006e00019999992e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400102006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80102005701000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" ); + assertTrue(success, "Call Failed"); assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99574171); vm.stopPrank(); @@ -289,26 +291,27 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005eea514000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000682165ac00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9dfb400000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004107f2b0f9c2e4e308ab43b288d69de30d84b10c8075e4dd9a2cf66594f97a52fb34de2534b89bf1887da74c92fd03464f45baff700dd32e213e3add1a3f351e891b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d013950102006e01009999992e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400000006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d8000000000000000000" ); + assertTrue(success, "Call Failed"); assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99525908); vm.stopPrank(); } - // TODO uncomment when Curve TokenTransfer is implemented (next commits) - // 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"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000055c08ca52497e2f1534b59e2917bf524d4765257000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000005b005900010000001d1499e622d69689cdf9004d05ec547d650ff21155c08ca52497e2f1534b59e2917bf524d4765257c02aaa39b223fe8d0a0e5c4f27ead9083c756cc277146b0a1d08b6844376df6d9da99ba7f1b19e71020100010000000000" - // ); - // - // assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 4691958787921); - // - // 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"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000055c08ca52497e2f1534b59e2917bf524d4765257000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000005c005a00010000001d1499e622d69689cdf9004d05ec547d650ff21155c08ca52497e2f1534b59e2917bf524d4765257c02aaa39b223fe8d0a0e5c4f27ead9083c756cc277146b0a1d08b6844376df6d9da99ba7f1b19e71020100010300000000" + ); + + assertTrue(success, "Call Failed"); + assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 4691958787921); + + vm.stopPrank(); + } function testSplitCurveIntegrationStETH() public { deal(ALICE, 1 ether); @@ -316,9 +319,10 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { vm.startPrank(ALICE); // Encoded solution generated using `test_split_encoding_strategy_curve_st_eth` (bool success,) = tychoRouterAddr.call{value: 1 ether}( - hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe840000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000005b005900010000001d1499e622d69689cdf9004d05ec547d650ff211eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeae7ab96520de3a18e5e111b5eaab095312d7fe84dc24316b9ae028f1497c275eb9192a3ea0f67022010001000000000000" + hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe840000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000005c005a00010000001d1499e622d69689cdf9004d05ec547d650ff211eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeae7ab96520de3a18e5e111b5eaab095312d7fe84dc24316b9ae028f1497c275eb9192a3ea0f67022010001000500000000" ); + assertTrue(success, "Call Failed"); assertEq(IERC20(STETH_ADDR).balanceOf(ALICE), 1000754689941529590); vm.stopPrank(); diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index d556e35..d5b9651 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -108,7 +108,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper { ); balancerv2Executor = new BalancerV2Executor(); ekuboExecutor = new EkuboExecutor(ekuboCore); - curveExecutor = new CurveExecutor(ETH_ADDR_FOR_CURVE); + curveExecutor = new CurveExecutor(ETH_ADDR_FOR_CURVE, PERMIT2_ADDRESS); address[] memory executors = new address[](7); executors[0] = address(usv2Executor); diff --git a/foundry/test/executors/CurveExecutor.t.sol b/foundry/test/executors/CurveExecutor.t.sol index cd15079..a49a2dc 100644 --- a/foundry/test/executors/CurveExecutor.t.sol +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -22,7 +22,9 @@ interface MetaRegistry { } contract CurveExecutorExposed is CurveExecutor { - constructor(address _nativeToken) CurveExecutor(_nativeToken) {} + constructor(address _nativeToken, address _permit2) + CurveExecutor(_nativeToken, _permit2) + {} function decodeData(bytes calldata data) external @@ -34,7 +36,8 @@ contract CurveExecutorExposed is CurveExecutor { uint8 poolType, int128 i, int128 j, - bool tokenApprovalNeeded + bool tokenApprovalNeeded, + TokenTransfer.TransferType transferType ) { return _decodeData(data); @@ -50,7 +53,8 @@ contract CurveExecutorTest is Test, Constants { function setUp() public { uint256 forkBlock = 22031795; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); - curveExecutorExposed = new CurveExecutorExposed(ETH_ADDR_FOR_CURVE); + curveExecutorExposed = + new CurveExecutorExposed(ETH_ADDR_FOR_CURVE, PERMIT2_ADDRESS); metaRegistry = MetaRegistry(CURVE_META_REGISTRY); } @@ -62,7 +66,8 @@ contract CurveExecutorTest is Test, Constants { uint8(3), uint8(2), uint8(0), - true + true, + TokenTransfer.TransferType.NONE ); ( @@ -72,7 +77,8 @@ contract CurveExecutorTest is Test, Constants { uint8 poolType, int128 i, int128 j, - bool tokenApprovalNeeded + bool tokenApprovalNeeded, + TokenTransfer.TransferType transferType ) = curveExecutorExposed.decodeData(data); assertEq(tokenIn, WETH_ADDR); @@ -82,6 +88,7 @@ contract CurveExecutorTest is Test, Constants { assertEq(i, 2); assertEq(j, 0); assertEq(tokenApprovalNeeded, true); + assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE)); } function testTriPool() public { @@ -311,7 +318,8 @@ contract CurveExecutorTest is Test, Constants { poolType, uint8(uint256(uint128(i))), uint8(uint256(uint128(j))), - true + true, + TokenTransfer.TransferType.NONE ); } diff --git a/src/encoding/evm/constants.rs b/src/encoding/evm/constants.rs index 2e6ad96..4eadb35 100644 --- a/src/encoding/evm/constants.rs +++ b/src/encoding/evm/constants.rs @@ -31,7 +31,7 @@ pub static IN_TRANSFER_OPTIMIZABLE_PROTOCOLS: LazyLock> = pub static PROTOCOLS_EXPECTING_FUNDS_IN_ROUTER: LazyLock> = LazyLock::new(|| { let mut set = HashSet::new(); - set.insert("curve"); + set.insert("vm:curve"); set.insert("balancer_v2"); // TODO remove uniswap_v4 when we add callback support for transfer optimizations set.insert("uniswap_v4"); diff --git a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs index ff43ed1..77c23d4 100644 --- a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs +++ b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs @@ -27,11 +27,11 @@ pub trait TransferOptimization { let is_first_swap = (swap.token_in == given_token) || ((swap.token_in == wrapped_token) && wrap); - if is_first_swap && send_funds_to_pool { - if swap.token_in == native_token { - // Funds are already in router. Protocol takes care of native transfer. - TransferType::None - } else if permit2 { + if swap.token_in == native_token { + // Funds are already in router. All protocols currently take care of native transfers. + TransferType::None + } else if is_first_swap && send_funds_to_pool { + if permit2 { // Transfer from swapper to pool using permit2. TransferType::Permit2Transfer } else { @@ -39,10 +39,7 @@ pub trait TransferOptimization { TransferType::TransferFrom } } else if is_first_swap && funds_expected_in_router { - if swap.token_in == native_token { - // Funds already in router. Do nothing. - TransferType::None - } else if permit2 { + if permit2 { // Transfer from swapper to router using permit2. TransferType::Permit2TransferToRouter } else { diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 3fd1453..2ea8954 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -545,6 +545,7 @@ impl SwapEncoder for CurveSwapEncoder { i.to_be_bytes::<1>(), j.to_be_bytes::<1>(), approval_needed, + (encoding_context.transfer_type as u8).to_be_bytes(), ); Ok(args.abi_encode_packed()) @@ -1263,6 +1264,8 @@ mod tests { "01", // approval needed "01", + // transfer type + "05", )) ); } @@ -1329,6 +1332,8 @@ mod tests { "00", // approval needed "01", + // transfer type + "05", )) ); } @@ -1405,6 +1410,8 @@ mod tests { "01", // approval needed "01", + // transfer type + "05", )) ); }