diff --git a/foundry/src/executors/TokenTransfer.sol b/foundry/src/executors/TokenTransfer.sol new file mode 100644 index 0000000..f529b69 --- /dev/null +++ b/foundry/src/executors/TokenTransfer.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.26; + +import "@interfaces/IExecutor.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@permit2/src/interfaces/IAllowanceTransfer.sol"; + +error TokenTransfer__AddressZero(); + +contract TokenTransfer { + using SafeERC20 for IERC20; + + IAllowanceTransfer public immutable permit2; + + enum TransferType { + // Assume funds are in the TychoRouter - transfer into the pool + TRANSFER, + // Assume funds are in msg.sender's wallet - transferFrom into the pool + TRANSFERFROM, + // Assume funds are in msg.sender's wallet - permit2TransferFrom into the pool + TRANSFERPERMIT2, + // Assume funds have already been transferred into the pool. Do nothing. + NONE + } + + constructor(address _permit2) { + if (_permit2 == address(0)) { + revert TokenTransfer__AddressZero(); + } + permit2 = IAllowanceTransfer(_permit2); + } + + function _transfer( + IERC20 tokenIn, + address sender, + address receiver, + uint256 amount, + TransferType transferType + ) internal { + if (transferType == TransferType.TRANSFER) { + tokenIn.safeTransfer(receiver, amount); + } else if (transferType == TransferType.TRANSFERFROM) { + // slither-disable-next-line arbitrary-send-erc20 + tokenIn.safeTransferFrom(sender, receiver, amount); + } else if (transferType == TransferType.TRANSFERPERMIT2) { + // Permit2.permit is already called from the TychoRouter + permit2.transferFrom( + sender, receiver, uint160(amount), address(tokenIn) + ); + } + } +} diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index 50fb056..2ddcd97 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -4,20 +4,23 @@ pragma solidity ^0.8.26; import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@uniswap-v2/contracts/interfaces/IUniswapV2Pair.sol"; +import "./TokenTransfer.sol"; error UniswapV2Executor__InvalidDataLength(); error UniswapV2Executor__InvalidTarget(); error UniswapV2Executor__InvalidFactory(); error UniswapV2Executor__InvalidInitCode(); -contract UniswapV2Executor is IExecutor { +contract UniswapV2Executor is IExecutor, TokenTransfer { using SafeERC20 for IERC20; address public immutable factory; bytes32 public immutable initCode; address private immutable self; - constructor(address _factory, bytes32 _initCode) { + constructor(address _factory, bytes32 _initCode, address _permit2) + TokenTransfer(_permit2) + { if (_factory == address(0)) { revert UniswapV2Executor__InvalidFactory(); } @@ -35,17 +38,19 @@ contract UniswapV2Executor is IExecutor { payable returns (uint256 calculatedAmount) { + IERC20 tokenIn; address target; address receiver; bool zeroForOne; - IERC20 tokenIn; + TransferType transferType; - (tokenIn, target, receiver, zeroForOne) = _decodeData(data); + (tokenIn, target, receiver, zeroForOne, transferType) = + _decodeData(data); _verifyPairAddress(target); calculatedAmount = _getAmountOut(target, givenAmount, zeroForOne); - tokenIn.safeTransfer(target, givenAmount); + _transfer(tokenIn, msg.sender, target, givenAmount, transferType); IUniswapV2Pair pool = IUniswapV2Pair(target); if (zeroForOne) { @@ -62,16 +67,18 @@ contract UniswapV2Executor is IExecutor { IERC20 inToken, address target, address receiver, - bool zeroForOne + bool zeroForOne, + TransferType transferType ) { - if (data.length != 61) { + if (data.length != 62) { revert UniswapV2Executor__InvalidDataLength(); } inToken = IERC20(address(bytes20(data[0:20]))); target = address(bytes20(data[20:40])); receiver = address(bytes20(data[40:60])); zeroForOne = uint8(data[60]) > 0; + transferType = TransferType(uint8(data[61])); } function _getAmountOut(address target, uint256 amountIn, bool zeroForOne) diff --git a/foundry/src/executors/UniswapV3Executor.sol b/foundry/src/executors/UniswapV3Executor.sol index 7b6383f..dbc794e 100644 --- a/foundry/src/executors/UniswapV3Executor.sol +++ b/foundry/src/executors/UniswapV3Executor.sol @@ -5,13 +5,15 @@ import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import "@interfaces/ICallback.sol"; +import {TokenTransfer} from "./TokenTransfer.sol"; error UniswapV3Executor__InvalidDataLength(); error UniswapV3Executor__InvalidFactory(); error UniswapV3Executor__InvalidTarget(); error UniswapV3Executor__InvalidInitCode(); +error UniswapV3Executor__InvalidTransferType(uint8 transferType); -contract UniswapV3Executor is IExecutor, ICallback { +contract UniswapV3Executor is IExecutor, ICallback, TokenTransfer { using SafeERC20 for IERC20; uint160 private constant MIN_SQRT_RATIO = 4295128739; @@ -22,7 +24,9 @@ contract UniswapV3Executor is IExecutor, ICallback { bytes32 public immutable initCode; address private immutable self; - constructor(address _factory, bytes32 _initCode) { + constructor(address _factory, bytes32 _initCode, address _permit2) + TokenTransfer(_permit2) + { if (_factory == address(0)) { revert UniswapV3Executor__InvalidFactory(); } @@ -46,7 +50,8 @@ contract UniswapV3Executor is IExecutor, ICallback { uint24 fee, address receiver, address target, - bool zeroForOne + bool zeroForOne, + TransferType transferType ) = _decodeData(data); _verifyPairAddress(tokenIn, tokenOut, fee, target); @@ -55,7 +60,8 @@ contract UniswapV3Executor is IExecutor, ICallback { int256 amount1; IUniswapV3Pool pool = IUniswapV3Pool(target); - bytes memory callbackData = _makeV3CallbackData(tokenIn, tokenOut, fee); + bytes memory callbackData = + _makeV3CallbackData(tokenIn, tokenOut, fee, transferType); { (amount0, amount1) = pool.swap( @@ -92,12 +98,21 @@ contract UniswapV3Executor is IExecutor, ICallback { address tokenIn = address(bytes20(msgData[132:152])); + // Transfer type does not exist + if (uint8(msgData[175]) > uint8(TransferType.NONE)) { + revert UniswapV3Executor__InvalidTransferType(uint8(msgData[175])); + } + + TransferType transferType = TransferType(uint8(msgData[175])); + address sender = address(bytes20(msgData[176:196])); + verifyCallback(msgData[132:]); uint256 amountOwed = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); - IERC20(tokenIn).safeTransfer(msg.sender, amountOwed); + _transfer(IERC20(tokenIn), sender, msg.sender, amountOwed, transferType); + return abi.encode(amountOwed, tokenIn); } @@ -132,10 +147,11 @@ contract UniswapV3Executor is IExecutor, ICallback { uint24 fee, address receiver, address target, - bool zeroForOne + bool zeroForOne, + TransferType transferType ) { - if (data.length != 84) { + if (data.length != 85) { revert UniswapV3Executor__InvalidDataLength(); } tokenIn = address(bytes20(data[0:20])); @@ -144,14 +160,18 @@ contract UniswapV3Executor is IExecutor, ICallback { receiver = address(bytes20(data[43:63])); target = address(bytes20(data[63:83])); zeroForOne = uint8(data[83]) > 0; + transferType = TransferType(uint8(data[84])); } - function _makeV3CallbackData(address tokenIn, address tokenOut, uint24 fee) - internal - pure - returns (bytes memory) - { - return abi.encodePacked(tokenIn, tokenOut, fee); + function _makeV3CallbackData( + address tokenIn, + address tokenOut, + uint24 fee, + TransferType transferType + ) internal view returns (bytes memory) { + return abi.encodePacked( + tokenIn, tokenOut, fee, uint8(transferType), msg.sender + ); } function _verifyPairAddress( diff --git a/foundry/test/Permit2TestHelper.sol b/foundry/test/Permit2TestHelper.sol new file mode 100644 index 0000000..912b007 --- /dev/null +++ b/foundry/test/Permit2TestHelper.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.26; + +import "./Constants.sol"; +import "@permit2/src/interfaces/IAllowanceTransfer.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract Permit2TestHelper is Constants { + /** + * @dev Handles the Permit2 approval process for Alice, allowing the TychoRouter contract + * to spend `amount_in` of `tokenIn` on her behalf. + * + * This function approves the Permit2 contract to transfer the specified token amount + * and constructs a `PermitSingle` struct for the approval. It also generates a valid + * EIP-712 signature for the approval using Alice's private key. + * + * @param tokenIn The address of the token being approved. + * @param amount_in The amount of tokens to approve for transfer. + * @return permitSingle The `PermitSingle` struct containing the approval details. + * @return signature The EIP-712 signature for the approval. + */ + function handlePermit2Approval( + address tokenIn, + address spender, + uint256 amount_in + ) internal returns (IAllowanceTransfer.PermitSingle memory, bytes memory) { + IERC20(tokenIn).approve(PERMIT2_ADDRESS, amount_in); + IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer + .PermitSingle({ + details: IAllowanceTransfer.PermitDetails({ + token: tokenIn, + amount: uint160(amount_in), + expiration: uint48(block.timestamp + 1 days), + nonce: 0 + }), + spender: spender, + sigDeadline: block.timestamp + 1 days + }); + + bytes memory signature = signPermit2(permitSingle, ALICE_PK); + return (permitSingle, signature); + } + + /** + * @dev Signs a Permit2 `PermitSingle` struct with the given private key. + * @param permit The `PermitSingle` struct to sign. + * @param privateKey The private key of the signer. + * @return The signature as a `bytes` array. + */ + function signPermit2( + IAllowanceTransfer.PermitSingle memory permit, + uint256 privateKey + ) internal view returns (bytes memory) { + bytes32 _PERMIT_DETAILS_TYPEHASH = keccak256( + "PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" + ); + bytes32 _PERMIT_SINGLE_TYPEHASH = keccak256( + "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" + ); + bytes32 domainSeparator = keccak256( + abi.encode( + keccak256( + "EIP712Domain(string name,uint256 chainId,address verifyingContract)" + ), + keccak256("Permit2"), + block.chainid, + PERMIT2_ADDRESS + ) + ); + bytes32 detailsHash = + keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permit.details)); + bytes32 permitHash = keccak256( + abi.encode( + _PERMIT_SINGLE_TYPEHASH, + detailsHash, + permit.spender, + permit.sigDeadline + ) + ); + + bytes32 digest = + keccak256(abi.encodePacked("\x19\x01", domainSeparator, permitHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + + return abi.encodePacked(r, s, v); + } +} diff --git a/foundry/test/TychoRouterIntegration.t.sol b/foundry/test/TychoRouterIntegration.t.sol index 9042a89..29f2cb9 100644 --- a/foundry/test/TychoRouterIntegration.t.sol +++ b/foundry/test/TychoRouterIntegration.t.sol @@ -4,36 +4,15 @@ pragma solidity ^0.8.26; import "./TychoRouterTestSetup.sol"; contract TychoRouterTestIntegration is TychoRouterTestSetup { - function testSplitSwapSingleIntegration() public { - // Tests swapping WETH -> DAI on a USV2 pool - deal(WETH_ADDR, ALICE, 1 ether); - uint256 balancerBefore = IERC20(DAI_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_simple` - (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000681362ea00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ebdcf2000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041a82e9bdde90314de4b1bf918cc2e8b27da98adcab46e8e99d4e77472a572d6381837e9453095f4cc5e9b25691b678288174e547e040a67d12b36ddfdd1e672d21b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000058005600010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" - ); - - vm.stopPrank(); - - uint256 balancerAfter = IERC20(DAI_ADDR).balanceOf(ALICE); - - assertTrue(success, "Call Failed"); - assertEq(balancerAfter - balancerBefore, 2659881924818443699787); - } - 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 balancerBefore = IERC20(DAI_ADDR).balanceOf(ALICE); - // Encoded solution generated using `test_split_swap_strategy_encoder_simple_route_no_permit2` + // Encoded solution generated using `test_split_swap_strategy_encoder_no_permit2` (bool success,) = tychoRouterAddr.call( - hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae37400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000058005600010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" + hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae37400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000059005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" ); vm.stopPrank(); @@ -128,9 +107,9 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { // Approve permit2 vm.startPrank(ALICE); - // Encoded solution generated using `test_split_swap_strategy_encoder_simple_route_wrap` + // Encoded solution generated using `test_split_swap_strategy_encoder_wrap` (bool success,) = tychoRouterAddr.call{value: 1 ether}( - hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006813638900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ebdd91000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041f6ff7411a7ec76cb7dcafecf5e7f11121b1aa88af505635dc7faae6057e4f44e2859712f58331a14a1624f1e5edf2af80ddd2d90b5453d74df1b1fea10b9a2f91c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000058005600020000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" + hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000681e435000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6bd580000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000415563c90eb0f8a79e234e300520b50879242b42622cabd5d01c19e67aba4854a723e0bd8333774062ae03a22b8c5de4a0dfd70bffd9197f56b2063f390610e5891b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000059005700020000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" ); vm.stopPrank(); @@ -150,9 +129,9 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { // Approve permit2 vm.startPrank(ALICE); IERC20(DAI_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); - // Encoded solution generated using `test_split_swap_strategy_encoder_simple_route_unwrap` + // Encoded solution generated using `test_split_swap_strategy_encoder_unwrap` (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000a2a15d09519be000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000a2a15d09519be00000000000000000000000000000000000000000000000000000000000006813615200000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ebdb5a000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041a7da748b04674485a5da185055affefc85b6d8fe412accce55b6f67842116f0f7f7130de5d74c68c20e1cedcdf93b8741b9171de2e6a3f2567887382a0712e3f1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000058005600010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395010000000000000000" + hex"7c5538460000000000000000000000000000000000000000000000a2a15d09519be000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000a2a15d09519be0000000000000000000000000000000000000000000000000000000000000681e436b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6bd730000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000411150ac5d21c61fcd35b486034a433e44d045950a52e991a7ad4035f3ad52beee5d9ecbc5ed9c370730cd2631a050fbe1219dd606e39c8aca40d588aa3eab03411c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000059005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395010000000000000000" ); vm.stopPrank(); @@ -204,7 +183,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_split_swap_strategy_encoder_complex` (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000681363a300000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ebddab0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000415b7ff43991de10c4c3a0372653891d27eb305ce04228bfe46a7d84a0978063fc4cb05183f19b83511bcb689b002d4f8e170f1d3cd77cf18c638229ccb67e0cac1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000160005600028000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139500005600010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d0139500005602030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fae461ca67b15dc8dc81ce7615e0320da1a9ab8d53ede3eca2a72b3aecc820e955b36f38437d0139501005601030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d0139501" + hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000681e2d8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6a793000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041e9868ce97a4c09488710ce748362ca38418bda44148bb5faf7820445d47efaff66f8f3667a0b3091c9d67d240db4cc2c95db27bdf019e4d5ff76b7b6620514691b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164005700028000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950000005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950000005702030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fae461ca67b15dc8dc81ce7615e0320da1a9ab8d53ede3eca2a72b3aecc820e955b36f38437d013950100005701030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d01395010000000000000000000000000000000000000000000000000000000000" ); vm.stopPrank(); @@ -231,7 +210,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { 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"51bcc7b60000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000068168aea00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ef04f200000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000004154956683effd126a9182e2d82ebd3d778e5283b93d571b13cdbc9dfbf3d9f655057a2332ed566f79bed7514a22ef1c52969132bc71a5a2ef125d78e39ec264511c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a600515615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950000515615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d01395010000000000000000000000000000000000000000000000000000" + hex"51bcc7b60000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000681e493d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6c34500000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000004189757b528c9874627ca7ecd64bd662db5fd7855837ec98cca5cff2cbd599f9af15baff1d389b5de4ca0dc1106b9d3795a1695934cd5fa1158fffcc8d6495dfaa1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d01395000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d013950100000000000000000000000000000000000000000000000000" ); vm.stopPrank(); @@ -255,7 +234,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max); // Encoded solution generated using `test_sequential_swap_strategy_encoder_no_permit2` (bool success,) = tychoRouterAddr.call( - hex"e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000a600515615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950000515615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d01395010000000000000000000000000000000000000000000000000000" + hex"e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d01395000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d013950100000000000000000000000000000000000000000000000000" ); vm.stopPrank(); @@ -275,7 +254,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_cyclic_sequential_swap` (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f4308e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000681363d200000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ebddda0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000418d58a54a3b8afc5d2e228ce6c5a1ab6b342cb5bfd9a00d57b869a4703ca2bb084d10d21f6842be9652a9ff2392673fbdcb961439ccc962de09f6bc64e5e665fe1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de006d00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f564001006d01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d8000000" + hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f4308e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000681e4a0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6c40a0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000416a11f27f0546e9cc9d27e4383a3f860d9822f0d7d0117e73abbf03007b3e235b36c2288ff083a04f51285092ff23422b3e00a5a292d5627172dfd031308fc3a11b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400100006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80000" ); assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99889294); @@ -291,7 +270,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_split_input_cyclic_swap` (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ef619b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006816408300000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067eeba8b0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000416de253b927fdcf110d157372e620e70c7220d3c01f04e01cdffb076edbb8b42052d281dd6c55a2349502742a0a8de58d2d1dbdc452f6c9d695b1c732c023d0561c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000136006d00019999992e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f564001006d00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d801005601000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000" + hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ef619b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000681e2d1600000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6a71e00000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004129478d2bffac3e378054d024ca3461f61e55552e2e8b0c7e0869f38ee834462f157f761e1a67c713f5749a6f6210dc4ac998a0521dda8dcb780b35d774250a141c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139006e00019999992e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400100006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80100005701000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" ); assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99574171); @@ -307,7 +286,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_split_output_cyclic_swap` (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005eea514000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006816418400000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067eebb8c0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000412c44c7de8f7eaaea61e49dbdefdc5606925db6f93db0789e632899ac88d3c7677cc8b69719603ab1b5ecef07d659b7254881d0667a49ebccbf43949b760b041a1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000136005600010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d0139501006d01009999992e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f564000006d01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80000000000000000000000" + hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005eea514000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000681e2d3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6a738000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041196745237299bfab79f038c0c12decec9c46b264621e1f00bbef32b15a22f15543defe95694dd1616aa8785bbb6f91a1557eebc97c0f5ca968c99f250da1b2ce1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d013950100006e01009999992e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400000006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d8000000000000000000" ); assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99525908); diff --git a/foundry/test/TychoRouterSequentialSwap.t.sol b/foundry/test/TychoRouterSequentialSwap.t.sol index c5db19f..46a9936 100644 --- a/foundry/test/TychoRouterSequentialSwap.t.sol +++ b/foundry/test/TychoRouterSequentialSwap.t.sol @@ -51,7 +51,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes[] memory swaps = _getSequentialSwaps(); tychoRouter.sequentialSwapPermit2( @@ -150,7 +150,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes[] memory swaps = _getSequentialSwaps(); @@ -229,7 +229,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(USDC_ADDR, amountIn); + ) = handlePermit2Approval(USDC_ADDR, tychoRouterAddr, amountIn); bytes[] memory swaps = new bytes[](2); diff --git a/foundry/test/TychoRouterSingleSwap.t.sol b/foundry/test/TychoRouterSingleSwap.t.sol index ea33401..246a95c 100644 --- a/foundry/test/TychoRouterSingleSwap.t.sol +++ b/foundry/test/TychoRouterSingleSwap.t.sol @@ -19,7 +19,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap( WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false @@ -230,7 +230,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(DAI_ADDR, amountIn); + ) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap(DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true); @@ -267,7 +267,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max); // Encoded solution generated using `test_single_swap_strategy_encoder_no_permit2` (bool success,) = tychoRouterAddr.call( - hex"20144a070000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae3740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000515615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000" + hex"20144a070000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae3740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000" ); vm.stopPrank(); @@ -286,7 +286,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_single_swap_strategy_encoder` (bool success,) = tychoRouterAddr.call( - hex"30ace1b10000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006817833200000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067effd3a00000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000417efea09004d5d40d8d072e1ce0a425507717ea485c765eb90c170859197d362b502fb039b4f5cdce57318ecfe3ab276d1ac87771eb5d017b253a8f4107e6a20b1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000515615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000" + hex"30ace1b10000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006817833200000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067effd3a00000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000417efea09004d5d40d8d072e1ce0a425507717ea485c765eb90c170859197d362b502fb039b4f5cdce57318ecfe3ab276d1ac87771eb5d017b253a8f4107e6a20b1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000" ); vm.stopPrank(); diff --git a/foundry/test/TychoRouterSplitSwap.t.sol b/foundry/test/TychoRouterSplitSwap.t.sol index 0887500..a9c593a 100644 --- a/foundry/test/TychoRouterSplitSwap.t.sol +++ b/foundry/test/TychoRouterSplitSwap.t.sol @@ -81,7 +81,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes[] memory swaps = _getSplitSwaps(); @@ -191,7 +191,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes[] memory swaps = _getSplitSwaps(); @@ -282,7 +282,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(DAI_ADDR, amountIn); + ) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap(DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true); @@ -325,7 +325,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI bool zeroForOne = false; @@ -377,7 +377,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap( WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false @@ -426,7 +426,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(USDE_ADDR, amountIn); + ) = handlePermit2Approval(USDE_ADDR, tychoRouterAddr, amountIn); UniswapV4Executor.UniswapV4Pool[] memory pools = new UniswapV4Executor.UniswapV4Pool[](1); diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index 1690d3c..961f0f8 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -13,6 +13,7 @@ import "@src/TychoRouter.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; import {WETH} from "../lib/permit2/lib/solmate/src/tokens/WETH.sol"; +import {Permit2TestHelper} from "./Permit2TestHelper.sol"; contract TychoRouterExposed is TychoRouter { constructor(address _permit2, address weth) TychoRouter(_permit2, weth) {} @@ -41,7 +42,7 @@ contract TychoRouterExposed is TychoRouter { } } -contract TychoRouterTestSetup is Constants { +contract TychoRouterTestSetup is Constants, Permit2TestHelper { TychoRouterExposed tychoRouter; address tychoRouterAddr; UniswapV2Executor public usv2Executor; @@ -97,11 +98,14 @@ contract TychoRouterTestSetup is Constants { address ekuboCore = 0xe0e0e08A6A4b9Dc7bD67BCB7aadE5cF48157d444; IPoolManager poolManager = IPoolManager(poolManagerAddress); - usv2Executor = new UniswapV2Executor(factoryV2, initCodeV2); - usv3Executor = new UniswapV3Executor(factoryV3, initCodeV3); + usv2Executor = + new UniswapV2Executor(factoryV2, initCodeV2, PERMIT2_ADDRESS); + usv3Executor = + new UniswapV3Executor(factoryV3, initCodeV3, PERMIT2_ADDRESS); usv4Executor = new UniswapV4Executor(poolManager); - pancakev3Executor = - new UniswapV3Executor(factoryPancakeV3, initCodePancakeV3); + pancakev3Executor = new UniswapV3Executor( + factoryPancakeV3, initCodePancakeV3, PERMIT2_ADDRESS + ); balancerv2Executor = new BalancerV2Executor(); ekuboExecutor = new EkuboExecutor(ekuboCore); curveExecutor = new CurveExecutor(ETH_ADDR_FOR_CURVE); @@ -129,84 +133,6 @@ contract TychoRouterTestSetup is Constants { } } - /** - * @dev Handles the Permit2 approval process for Alice, allowing the TychoRouter contract - * to spend `amount_in` of `tokenIn` on her behalf. - * - * This function approves the Permit2 contract to transfer the specified token amount - * and constructs a `PermitSingle` struct for the approval. It also generates a valid - * EIP-712 signature for the approval using Alice's private key. - * - * @param tokenIn The address of the token being approved. - * @param amount_in The amount of tokens to approve for transfer. - * @return permitSingle The `PermitSingle` struct containing the approval details. - * @return signature The EIP-712 signature for the approval. - */ - function handlePermit2Approval(address tokenIn, uint256 amount_in) - internal - returns (IAllowanceTransfer.PermitSingle memory, bytes memory) - { - IERC20(tokenIn).approve(PERMIT2_ADDRESS, amount_in); - IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer - .PermitSingle({ - details: IAllowanceTransfer.PermitDetails({ - token: tokenIn, - amount: uint160(amount_in), - expiration: uint48(block.timestamp + 1 days), - nonce: 0 - }), - spender: tychoRouterAddr, - sigDeadline: block.timestamp + 1 days - }); - - bytes memory signature = signPermit2(permitSingle, ALICE_PK); - return (permitSingle, signature); - } - - /** - * @dev Signs a Permit2 `PermitSingle` struct with the given private key. - * @param permit The `PermitSingle` struct to sign. - * @param privateKey The private key of the signer. - * @return The signature as a `bytes` array. - */ - function signPermit2( - IAllowanceTransfer.PermitSingle memory permit, - uint256 privateKey - ) internal view returns (bytes memory) { - bytes32 _PERMIT_DETAILS_TYPEHASH = keccak256( - "PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" - ); - bytes32 _PERMIT_SINGLE_TYPEHASH = keccak256( - "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" - ); - bytes32 domainSeparator = keccak256( - abi.encode( - keccak256( - "EIP712Domain(string name,uint256 chainId,address verifyingContract)" - ), - keccak256("Permit2"), - block.chainid, - PERMIT2_ADDRESS - ) - ); - bytes32 detailsHash = - keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permit.details)); - bytes32 permitHash = keccak256( - abi.encode( - _PERMIT_SINGLE_TYPEHASH, - detailsHash, - permit.spender, - permit.sigDeadline - ) - ); - - bytes32 digest = - keccak256(abi.encodePacked("\x19\x01", domainSeparator, permitHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); - - return abi.encodePacked(r, s, v); - } - function pleEncode(bytes[] memory data) public pure @@ -254,7 +180,13 @@ contract TychoRouterTestSetup is Constants { address receiver, bool zero2one ) internal pure returns (bytes memory) { - return abi.encodePacked(tokenIn, target, receiver, zero2one); + return abi.encodePacked( + tokenIn, + target, + receiver, + zero2one, + TokenTransfer.TransferType.TRANSFER + ); } function encodeUniswapV3Swap( @@ -266,7 +198,13 @@ contract TychoRouterTestSetup is Constants { ) internal view returns (bytes memory) { IUniswapV3Pool pool = IUniswapV3Pool(target); return abi.encodePacked( - tokenIn, tokenOut, pool.fee(), receiver, target, zero2one + tokenIn, + tokenOut, + pool.fee(), + receiver, + target, + zero2one, + TokenTransfer.TransferType.TRANSFER ); } } diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index a26a9b7..c993ff9 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -2,12 +2,14 @@ pragma solidity ^0.8.26; import "@src/executors/UniswapV2Executor.sol"; +import "@src/executors/TokenTransfer.sol"; import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; +import {Permit2TestHelper} from "../Permit2TestHelper.sol"; contract UniswapV2ExecutorExposed is UniswapV2Executor { - constructor(address _factory, bytes32 _initCode) - UniswapV2Executor(_factory, _initCode) + constructor(address _factory, bytes32 _initCode, address _permit2) + UniswapV2Executor(_factory, _initCode, _permit2) {} function decodeParams(bytes calldata data) @@ -17,7 +19,8 @@ contract UniswapV2ExecutorExposed is UniswapV2Executor { IERC20 inToken, address target, address receiver, - bool zeroForOne + bool zeroForOne, + TransferType transferType ) { return _decodeData(data); @@ -46,7 +49,7 @@ contract FakeUniswapV2Pool { } } -contract UniswapV2ExecutorTest is Test, Constants { +contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { using SafeERC20 for IERC20; UniswapV2ExecutorExposed uniswapV2Exposed; @@ -54,32 +57,51 @@ contract UniswapV2ExecutorTest is Test, Constants { UniswapV2ExecutorExposed pancakeswapV2Exposed; IERC20 WETH = IERC20(WETH_ADDR); IERC20 DAI = IERC20(DAI_ADDR); + IAllowanceTransfer permit2; function setUp() public { uint256 forkBlock = 17323404; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); uniswapV2Exposed = new UniswapV2ExecutorExposed( - USV2_FACTORY_ETHEREUM, USV2_POOL_CODE_INIT_HASH + USV2_FACTORY_ETHEREUM, USV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS ); sushiswapV2Exposed = new UniswapV2ExecutorExposed( - SUSHISWAPV2_FACTORY_ETHEREUM, SUSHIV2_POOL_CODE_INIT_HASH + SUSHISWAPV2_FACTORY_ETHEREUM, + SUSHIV2_POOL_CODE_INIT_HASH, + PERMIT2_ADDRESS ); pancakeswapV2Exposed = new UniswapV2ExecutorExposed( - PANCAKESWAPV2_FACTORY_ETHEREUM, PANCAKEV2_POOL_CODE_INIT_HASH + PANCAKESWAPV2_FACTORY_ETHEREUM, + PANCAKEV2_POOL_CODE_INIT_HASH, + PERMIT2_ADDRESS ); + permit2 = IAllowanceTransfer(PERMIT2_ADDRESS); } function testDecodeParams() public view { - bytes memory params = - abi.encodePacked(WETH_ADDR, address(2), address(3), false); + bytes memory params = abi.encodePacked( + WETH_ADDR, + address(2), + address(3), + false, + TokenTransfer.TransferType.TRANSFER + ); - (IERC20 tokenIn, address target, address receiver, bool zeroForOne) = - uniswapV2Exposed.decodeParams(params); + ( + IERC20 tokenIn, + address target, + address receiver, + bool zeroForOne, + TokenTransfer.TransferType transferType + ) = uniswapV2Exposed.decodeParams(params); assertEq(address(tokenIn), WETH_ADDR); assertEq(target, address(2)); assertEq(receiver, address(3)); assertEq(zeroForOne, false); + assertEq( + uint8(TokenTransfer.TransferType.TRANSFER), uint8(transferType) + ); } function testDecodeParamsInvalidDataLength() public { @@ -126,12 +148,17 @@ contract UniswapV2ExecutorTest is Test, Constants { assertGe(amountOut, 0); } - function testSwap() public { + function testSwapWithTransfer() public { uint256 amountIn = 10 ** 18; uint256 amountOut = 1847751195973566072891; bool zeroForOne = false; - bytes memory protocolData = - abi.encodePacked(WETH_ADDR, WETH_DAI_POOL, BOB, zeroForOne); + bytes memory protocolData = abi.encodePacked( + WETH_ADDR, + WETH_DAI_POOL, + BOB, + zeroForOne, + uint8(TokenTransfer.TransferType.TRANSFER) + ); deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); uniswapV2Exposed.swap(amountIn, protocolData); @@ -140,24 +167,104 @@ contract UniswapV2ExecutorTest is Test, Constants { assertGe(finalBalance, amountOut); } + function testSwapWithTransferFrom() public { + uint256 amountIn = 10 ** 18; + uint256 amountOut = 1847751195973566072891; + bool zeroForOne = false; + bytes memory protocolData = abi.encodePacked( + WETH_ADDR, + WETH_DAI_POOL, + BOB, + zeroForOne, + uint8(TokenTransfer.TransferType.TRANSFERFROM) + ); + + deal(WETH_ADDR, address(this), amountIn); + IERC20(WETH_ADDR).approve(address(uniswapV2Exposed), amountIn); + + uniswapV2Exposed.swap(amountIn, protocolData); + + uint256 finalBalance = DAI.balanceOf(BOB); + assertGe(finalBalance, amountOut); + } + + function testSwapWithPermit2TransferFrom() public { + uint256 amountIn = 10 ** 18; + uint256 amountOut = 1847751195973566072891; + bool zeroForOne = false; + bytes memory protocolData = abi.encodePacked( + WETH_ADDR, + WETH_DAI_POOL, + ALICE, + zeroForOne, + uint8(TokenTransfer.TransferType.TRANSFERPERMIT2) + ); + + deal(WETH_ADDR, ALICE, amountIn); + vm.startPrank(ALICE); + ( + IAllowanceTransfer.PermitSingle memory permitSingle, + bytes memory signature + ) = handlePermit2Approval( + WETH_ADDR, address(uniswapV2Exposed), amountIn + ); + + // Assume the permit2.approve method will be called from the TychoRouter + // Replicate this scenario in this test. + permit2.permit(ALICE, permitSingle, signature); + + uniswapV2Exposed.swap(amountIn, protocolData); + vm.stopPrank(); + + uint256 finalBalance = DAI.balanceOf(ALICE); + assertGe(finalBalance, amountOut); + } + + function testSwapNoTransfer() public { + uint256 amountIn = 10 ** 18; + uint256 amountOut = 1847751195973566072891; + bool zeroForOne = false; + bytes memory protocolData = abi.encodePacked( + WETH_ADDR, + WETH_DAI_POOL, + BOB, + zeroForOne, + uint8(TokenTransfer.TransferType.NONE) + ); + + deal(WETH_ADDR, address(this), amountIn); + IERC20(WETH_ADDR).transfer(address(WETH_DAI_POOL), amountIn); + uniswapV2Exposed.swap(amountIn, protocolData); + + uint256 finalBalance = DAI.balanceOf(BOB); + assertGe(finalBalance, amountOut); + } + function testDecodeIntegration() public view { // Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode bytes memory protocolData = - hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f5640000000000000000000000000000000000000000100"; + hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f564000000000000000000000000000000000000000010000"; - (IERC20 tokenIn, address target, address receiver, bool zeroForOne) = - uniswapV2Exposed.decodeParams(protocolData); + ( + IERC20 tokenIn, + address target, + address receiver, + bool zeroForOne, + TokenTransfer.TransferType transferType + ) = uniswapV2Exposed.decodeParams(protocolData); assertEq(address(tokenIn), WETH_ADDR); assertEq(target, 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640); assertEq(receiver, 0x0000000000000000000000000000000000000001); assertEq(zeroForOne, false); + // TRANSFER = 0 + assertEq(0, uint8(transferType)); } function testSwapIntegration() public { // Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode bytes memory protocolData = - hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e00"; + hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0000"; uint256 amountIn = 10 ** 18; uint256 amountOut = 1847751195973566072891; deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); @@ -171,8 +278,13 @@ contract UniswapV2ExecutorTest is Test, Constants { uint256 amountIn = 10 ** 18; bool zeroForOne = false; address fakePool = address(new FakeUniswapV2Pool(WETH_ADDR, DAI_ADDR)); - bytes memory protocolData = - abi.encodePacked(WETH_ADDR, fakePool, BOB, zeroForOne); + bytes memory protocolData = abi.encodePacked( + WETH_ADDR, + fakePool, + BOB, + zeroForOne, + uint8(TokenTransfer.TransferType.TRANSFER) + ); deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); vm.expectRevert(UniswapV2Executor__InvalidTarget.selector); @@ -186,8 +298,13 @@ contract UniswapV2ExecutorTest is Test, Constants { vm.rollFork(26857267); uint256 amountIn = 10 * 10 ** 6; bool zeroForOne = true; - bytes memory protocolData = - abi.encodePacked(BASE_USDC, USDC_MAG7_POOL, BOB, zeroForOne); + bytes memory protocolData = abi.encodePacked( + BASE_USDC, + USDC_MAG7_POOL, + BOB, + zeroForOne, + uint8(TokenTransfer.TransferType.TRANSFER) + ); deal(BASE_USDC, address(uniswapV2Exposed), amountIn); diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index 59b03ba..24bfd22 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -2,12 +2,14 @@ pragma solidity ^0.8.26; import "@src/executors/UniswapV3Executor.sol"; +import "@permit2/src/interfaces/IAllowanceTransfer.sol"; import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; +import {Permit2TestHelper} from "../Permit2TestHelper.sol"; contract UniswapV3ExecutorExposed is UniswapV3Executor { - constructor(address _factory, bytes32 _initCode) - UniswapV3Executor(_factory, _initCode) + constructor(address _factory, bytes32 _initCode, address _permit2) + UniswapV3Executor(_factory, _initCode, _permit2) {} function decodeData(bytes calldata data) @@ -19,7 +21,8 @@ contract UniswapV3ExecutorExposed is UniswapV3Executor { uint24 fee, address receiver, address target, - bool zeroForOne + bool zeroForOne, + TransferType transferType ) { return _decodeData(data); @@ -35,30 +38,40 @@ contract UniswapV3ExecutorExposed is UniswapV3Executor { } } -contract UniswapV3ExecutorTest is Test, Constants { +contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { using SafeERC20 for IERC20; UniswapV3ExecutorExposed uniswapV3Exposed; UniswapV3ExecutorExposed pancakeV3Exposed; IERC20 WETH = IERC20(WETH_ADDR); IERC20 DAI = IERC20(DAI_ADDR); + IAllowanceTransfer permit2; function setUp() public { uint256 forkBlock = 17323404; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); uniswapV3Exposed = new UniswapV3ExecutorExposed( - USV3_FACTORY_ETHEREUM, USV3_POOL_CODE_INIT_HASH + USV3_FACTORY_ETHEREUM, USV3_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS ); pancakeV3Exposed = new UniswapV3ExecutorExposed( - PANCAKESWAPV3_DEPLOYER_ETHEREUM, PANCAKEV3_POOL_CODE_INIT_HASH + PANCAKESWAPV3_DEPLOYER_ETHEREUM, + PANCAKEV3_POOL_CODE_INIT_HASH, + PERMIT2_ADDRESS ); + permit2 = IAllowanceTransfer(PERMIT2_ADDRESS); } function testDecodeParams() public view { uint24 expectedPoolFee = 500; bytes memory data = abi.encodePacked( - WETH_ADDR, DAI_ADDR, expectedPoolFee, address(2), address(3), false + WETH_ADDR, + DAI_ADDR, + expectedPoolFee, + address(2), + address(3), + false, + TokenTransfer.TransferType.TRANSFER ); ( @@ -67,7 +80,8 @@ contract UniswapV3ExecutorTest is Test, Constants { uint24 fee, address receiver, address target, - bool zeroForOne + bool zeroForOne, + TokenTransfer.TransferType transferType ) = uniswapV3Exposed.decodeData(data); assertEq(tokenIn, WETH_ADDR); @@ -76,6 +90,9 @@ contract UniswapV3ExecutorTest is Test, Constants { assertEq(receiver, address(2)); assertEq(target, address(3)); assertEq(zeroForOne, false); + assertEq( + uint8(transferType), uint8(TokenTransfer.TransferType.TRANSFER) + ); } function testDecodeParamsInvalidDataLength() public { @@ -105,8 +122,9 @@ contract UniswapV3ExecutorTest is Test, Constants { uint256 initialPoolReserve = IERC20(WETH_ADDR).balanceOf(DAI_WETH_USV3); vm.startPrank(DAI_WETH_USV3); - bytes memory protocolData = - abi.encodePacked(WETH_ADDR, DAI_ADDR, poolFee); + bytes memory protocolData = abi.encodePacked( + WETH_ADDR, DAI_ADDR, poolFee, TokenTransfer.TransferType.TRANSFER + ); uint256 dataOffset = 3; // some offset uint256 dataLength = protocolData.length; @@ -116,7 +134,8 @@ contract UniswapV3ExecutorTest is Test, Constants { int256(0), // amount1Delta dataOffset, dataLength, - protocolData + protocolData, + address(uniswapV3Exposed) // transferFrom sender (irrelevant in this case) ); uniswapV3Exposed.handleCallback(callbackData); vm.stopPrank(); @@ -137,7 +156,8 @@ contract UniswapV3ExecutorTest is Test, Constants { uint24(3000), address(this), fakePool, - zeroForOne + zeroForOne, + TokenTransfer.TransferType.TRANSFER ); vm.expectRevert(UniswapV3Executor__InvalidTarget.selector); @@ -149,11 +169,18 @@ contract UniswapV3ExecutorTest is Test, Constants { address tokenOut, address receiver, address target, - bool zero2one + bool zero2one, + TokenTransfer.TransferType transferType ) internal view returns (bytes memory) { IUniswapV3Pool pool = IUniswapV3Pool(target); return abi.encodePacked( - tokenIn, tokenOut, pool.fee(), receiver, target, zero2one + tokenIn, + tokenOut, + pool.fee(), + receiver, + target, + zero2one, + transferType ); } } diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index d1c683b..3151034 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -709,9 +709,9 @@ mod tests { let expected_swaps = String::from(concat!( // length of ple encoded swaps without padding - "0000000000000000000000000000000000000000000000000000000000000058", + "0000000000000000000000000000000000000000000000000000000000000059", // ple encoded swaps - "0056", + "0057", // Swap header "00", // token in index "01", // token out index @@ -722,7 +722,7 @@ mod tests { "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one - "00", // exact out + "00", // transfer type "00000000000000", // padding )); let hex_calldata = encode(&calldata); @@ -817,8 +817,8 @@ mod tests { // it's hard to assert let expected_swap = String::from(concat!( - // length of ple encoded swaps without padding - "0000000000000000000000000000000000000000000000000000000000000051", + // length of encoded swap without padding + "0000000000000000000000000000000000000000000000000000000000000052", // Swap data "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in @@ -826,7 +826,8 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one "00", // exact out - "0000000000000000000000000000", // padding + "00", // transfer type + "00000000000000000000000000", // padding )); let hex_calldata = encode(&calldata); @@ -1475,7 +1476,7 @@ mod tests { "0000000000000000000000000000000000000000000000000000000000000000", // unwrap "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver "0000000000000000000000000000000000000000000000000000000000000100", // offset of swap bytes - "0000000000000000000000000000000000000000000000000000000000000051", // length of swap bytes without padding + "0000000000000000000000000000000000000000000000000000000000000052", // length of swap bytes without padding // Swap data "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address @@ -1484,7 +1485,8 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one "00", // exact out - "0000000000000000000000000000", // padding + "00", // transfer type + "00000000000000000000000000", // padding ] .join(""); @@ -1554,8 +1556,8 @@ mod tests { "0000000000000000000000000000000000000000000000000000000000000002", // tokens length "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver "0000000000000000000000000000000000000000000000000000000000000120", // offset of ple encoded swaps - "0000000000000000000000000000000000000000000000000000000000000058", // length of ple encoded swaps without padding - "0056", // 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 @@ -1567,7 +1569,8 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one "00", // exact out - "00000000000000", // padding + "00", // transfer type + "000000000000", // padding ] .join(""); @@ -1806,8 +1809,8 @@ mod tests { .join(""); let expected_swaps = [ - "00000000000000000000000000000000000000000000000000000000000000de", // length of ple encoded swaps without padding - "006d", // ple encoded swaps + "00000000000000000000000000000000000000000000000000000000000000e0", // length of ple encoded swaps without padding + "006e", // ple encoded swaps "00", // token in index "01", // token out index "000000", // split @@ -1818,7 +1821,8 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id "01", // zero2one - "006d", // ple encoded swaps + "00", // transfer type + "006e", // ple encoded swaps "01", // token in index "00000000", // split "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address @@ -1827,7 +1831,8 @@ mod tests { "000bb8", // pool fee "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id - "000000", // zero2one + "00", // zero2one + "00", // transfer type ] .join(""); @@ -1958,8 +1963,8 @@ mod tests { ] .join(""); let expected_swaps = [ - "0000000000000000000000000000000000000000000000000000000000000136", // length of ple encoded swaps without padding - "006d", // ple encoded swaps + "0000000000000000000000000000000000000000000000000000000000000139", // length of ple encoded swaps without padding + "006e", // ple encoded swaps "00", // token in index "01", // token out index "999999", // split @@ -1970,7 +1975,8 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id "01", // zero2one - "006d", // ple encoded swaps + "00", // transfer type + "006e", // ple encoded swaps "00", // token in index "01", // token out index "000000", // split @@ -1981,7 +1987,8 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id "01", // zero2one - "0056", // ple encoded swaps + "00", // transfer type + "0057", // ple encoded swaps "01", // token in index "00", // token out index "000000", // split @@ -1990,7 +1997,8 @@ mod tests { "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id, "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "00", // zero2one - "00000000000000000000" // padding + "00", // transfer type + "00000000000000" // padding ] .join(""); assert_eq!(hex_calldata[..520], expected_input); @@ -2117,8 +2125,8 @@ mod tests { .join(""); let expected_swaps = [ - "0000000000000000000000000000000000000000000000000000000000000136", // length of ple encoded swaps without padding - "0056", // ple encoded swaps + "0000000000000000000000000000000000000000000000000000000000000139", // length of ple encoded swaps without padding + "0057", // ple encoded swaps "00", // token in index "01", // token out index "000000", // split @@ -2127,7 +2135,8 @@ mod tests { "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "01", // zero2one - "006d", // ple encoded swaps + "00", // transfer type + "006e", // ple encoded swaps "01", // token in index "00", // token out index "999999", // split @@ -2138,7 +2147,8 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id "00", // zero2one - "006d", // ple encoded swaps + "00", // transfer type + "006e", // ple encoded swaps "01", // token in index "00", // token out index "000000", // split @@ -2149,7 +2159,8 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id "00", // zero2one - "00000000000000000000" // padding + "00", // transfer type + "00000000000000" // padding ] .join(""); diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index a4414b1..bdc170e 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -22,6 +22,15 @@ use crate::encoding::{ swap_encoder::SwapEncoder, }; +#[allow(dead_code)] +#[repr(u8)] +pub enum TransferType { + Transfer = 0, + TransferFrom = 1, + Permit2Transfer = 2, + None = 3, +} + /// Encodes a swap on a Uniswap V2 pool through the given executor address. /// /// # Fields @@ -66,6 +75,7 @@ impl SwapEncoder for UniswapV2SwapEncoder { component_id, bytes_to_address(&encoding_context.receiver)?, zero_to_one, + (TransferType::Transfer as u8).to_be_bytes(), ); Ok(args.abi_encode_packed()) @@ -128,6 +138,7 @@ impl SwapEncoder for UniswapV3SwapEncoder { bytes_to_address(&encoding_context.receiver)?, component_id, zero_to_one, + (TransferType::Transfer as u8).to_be_bytes(), ); Ok(args.abi_encode_packed()) @@ -606,6 +617,8 @@ mod tests { "0000000000000000000000000000000000000001", // zero for one "00", + // transfer type (transfer) + "00", )) ); } @@ -661,6 +674,8 @@ mod tests { "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // zero for one "00", + // transfer type (transfer) + "00", )) ); } diff --git a/src/encoding/evm/tycho_encoders.rs b/src/encoding/evm/tycho_encoders.rs index 2c7f474..92706fc 100644 --- a/src/encoding/evm/tycho_encoders.rs +++ b/src/encoding/evm/tycho_encoders.rs @@ -916,6 +916,8 @@ mod tests { "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", // zero for one "00", + // transfer type + "00", )) ); }