From 27dfde3118a76320b6c51958cae23e0136324802 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Wed, 14 May 2025 20:42:19 -0400 Subject: [PATCH] feat: Support new transfer logic in all executors TODO: - Fix failing tests - Remove permit2 from initialization of contracts --- foundry/src/OneTransferFromOnly.sol | 7 +- foundry/src/TychoRouter.sol | 46 ++++++- foundry/src/executors/BalancerV2Executor.sol | 21 +--- foundry/src/executors/CurveExecutor.sol | 23 +--- foundry/src/executors/EkuboExecutor.sol | 52 ++++---- foundry/src/executors/MaverickV2Executor.sol | 24 ++-- foundry/src/executors/UniswapV2Executor.sol | 8 +- foundry/src/executors/UniswapV3Executor.sol | 34 +++-- foundry/src/executors/UniswapV4Executor.sol | 80 ++++++------ .../test/TychoRouterProtocolIntegration.t.sol | 24 +++- foundry/test/TychoRouterSequentialSwap.t.sol | 74 +++++------ foundry/test/TychoRouterSingleSwap.t.sol | 55 ++++---- foundry/test/TychoRouterSplitSwap.t.sol | 106 +++++++--------- foundry/test/TychoRouterTestSetup.sol | 13 +- .../test/executors/BalancerV2Executor.t.sol | 22 +--- foundry/test/executors/CurveExecutor.t.sol | 9 +- foundry/test/executors/EkuboExecutor.t.sol | 8 +- .../test/executors/MaverickV2Executor.t.sol | 44 ++----- .../test/executors/UniswapV2Executor.t.sol | 117 +++--------------- .../test/executors/UniswapV3Executor.t.sol | 27 ++-- .../test/executors/UniswapV4Executor.t.sol | 36 +++--- foundry/test/executors/UniswapV4Utils.sol | 10 +- 22 files changed, 378 insertions(+), 462 deletions(-) diff --git a/foundry/src/OneTransferFromOnly.sol b/foundry/src/OneTransferFromOnly.sol index 4cc26f0..78d29bc 100644 --- a/foundry/src/OneTransferFromOnly.sol +++ b/foundry/src/OneTransferFromOnly.sol @@ -27,7 +27,7 @@ contract OneTransferFromOnly { function tstoreTransferFromInfo( address tokenIn, - address amountIn, + uint256 amountIn, bool isPermit2, address sender ) internal { @@ -40,10 +40,7 @@ contract OneTransferFromOnly { } } - function _transfer(address receiver) - // we could pass the amount and address too and compare to what is in the slots? - internal - { + function _transfer(address receiver) internal { address tokenIn; uint256 amount; bool isPermit2; diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 2d193d6..655a6a0 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -122,6 +122,8 @@ contract TychoRouter is * @param unwrapEth If true, unwraps the resulting WETH into native ETH and sends it to the receiver. * @param nTokens The total number of tokens involved in the swap graph (used to initialize arrays for internal calculations). * @param receiver The address to receive the output tokens. + * @param transferFromRequired If true, the contract will transfer the input token from the caller to the receiver. Otherwise, assume funds are already in router. + * @param tokenInReceiver The address to receive the input tokens. This is used when `transferFromRequired` is true. * @param swaps Encoded swap graph data containing details of each swap. * * @return amountOut The total amount of the output token received by the receiver. @@ -135,9 +137,14 @@ contract TychoRouter is bool unwrapEth, uint256 nTokens, address receiver, + bool transferFromRequired, + address tokenInReceiver, bytes calldata swaps ) public payable whenNotPaused nonReentrant returns (uint256 amountOut) { tstoreTransferFromInfo(tokenIn, amountIn, false, msg.sender); + if (transferFromRequired) { + _transfer(tokenInReceiver); + } return _splitSwapChecked( amountIn, tokenIn, @@ -173,6 +180,8 @@ contract TychoRouter is * @param receiver The address to receive the output tokens. * @param permitSingle A Permit2 structure containing token approval details for the input token. Ignored if `wrapEth` is true. * @param signature A valid signature authorizing the Permit2 approval. Ignored if `wrapEth` is true. + * @param transferFromRequired If true, the contract will transfer the input token from the caller to the receiver. Otherwise, assume funds are already in router. + * @param tokenInReceiver The address to receive the input tokens. This is used when `transferFromRequired` is true. * @param swaps Encoded swap graph data containing details of each swap. * * @return amountOut The total amount of the output token received by the receiver. @@ -188,14 +197,18 @@ contract TychoRouter is address receiver, IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature, + bool transferFromRequired, + address tokenInReceiver, bytes calldata swaps ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { // For native ETH, assume funds already in our router. Else, handle approval. if (tokenIn != address(0)) { permit2.permit(msg.sender, permitSingle, signature); } - tstoreTransferFromInfo(tokenIn, amountIn, true, msg.sender); + if (transferFromRequired) { + _transfer(tokenInReceiver); + } return _splitSwapChecked( amountIn, @@ -228,6 +241,8 @@ contract TychoRouter is * @param wrapEth If true, wraps the input token (native ETH) into WETH. * @param unwrapEth If true, unwraps the resulting WETH into native ETH and sends it to the receiver. * @param receiver The address to receive the output tokens. + * @param transferFromRequired If true, the contract will transfer the input token from the caller to the receiver. Otherwise, assume funds are already in router. + * @param tokenInReceiver The address to receive the input tokens. This is used when `transferFromRequired` is true. * @param swaps Encoded swap graph data containing details of each swap. * * @return amountOut The total amount of the output token received by the receiver. @@ -240,9 +255,14 @@ contract TychoRouter is bool wrapEth, bool unwrapEth, address receiver, + bool transferFromRequired, + address tokenInReceiver, bytes calldata swaps ) public payable whenNotPaused nonReentrant returns (uint256 amountOut) { tstoreTransferFromInfo(tokenIn, amountIn, false, msg.sender); + if (transferFromRequired) { + _transfer(tokenInReceiver); + } return _sequentialSwapChecked( amountIn, tokenIn, @@ -275,6 +295,8 @@ contract TychoRouter is * @param receiver The address to receive the output tokens. * @param permitSingle A Permit2 structure containing token approval details for the input token. Ignored if `wrapEth` is true. * @param signature A valid signature authorizing the Permit2 approval. Ignored if `wrapEth` is true. + * @param transferFromRequired If true, the contract will transfer the input token from the caller to the receiver. Otherwise, assume funds are already in router. + * @param tokenInReceiver The address to receive the input tokens. This is used when `transferFromRequired` is true. * @param swaps Encoded swap graph data containing details of each swap. * * @return amountOut The total amount of the output token received by the receiver. @@ -289,6 +311,8 @@ contract TychoRouter is address receiver, IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature, + bool transferFromRequired, + address tokenInReceiver, bytes calldata swaps ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { // For native ETH, assume funds already in our router. Else, handle approval. @@ -297,6 +321,9 @@ contract TychoRouter is } tstoreTransferFromInfo(tokenIn, amountIn, true, msg.sender); + if (transferFromRequired) { + _transfer(tokenInReceiver); + } return _sequentialSwapChecked( amountIn, tokenIn, @@ -325,6 +352,8 @@ contract TychoRouter is * @param wrapEth If true, wraps the input token (native ETH) into WETH. * @param unwrapEth If true, unwraps the resulting WETH into native ETH and sends it to the receiver. * @param receiver The address to receive the output tokens. + * @param transferFromRequired If true, the contract will transfer the input token from the caller to the receiver. Otherwise, assume funds are already in router. + * @param tokenInReceiver The address to receive the input tokens. This is used when `transferFromRequired` is true. * @param swapData Encoded swap details. * * @return amountOut The total amount of the output token received by the receiver. @@ -337,13 +366,13 @@ contract TychoRouter is bool wrapEth, bool unwrapEth, address receiver, - bool inTransferNeeded, - address fundsReceiver, + bool transferFromRequired, + address tokenInReceiver, bytes calldata swapData ) public payable whenNotPaused nonReentrant returns (uint256 amountOut) { tstoreTransferFromInfo(tokenIn, amountIn, false, msg.sender); - if (inTransferNeeded) { - _transfer(fundsReceiver); + if (transferFromRequired) { + _transfer(tokenInReceiver); } return _singleSwap( amountIn, @@ -377,6 +406,8 @@ contract TychoRouter is * @param receiver The address to receive the output tokens. * @param permitSingle A Permit2 structure containing token approval details for the input token. Ignored if `wrapEth` is true. * @param signature A valid signature authorizing the Permit2 approval. Ignored if `wrapEth` is true. + * @param transferFromRequired If true, the contract will transfer the input token from the caller to the receiver. Otherwise, assume funds are already in router. + * @param tokenInReceiver The address to receive the input tokens. This is used when `transferFromRequired` is true. * @param swapData Encoded swap details. * * @return amountOut The total amount of the output token received by the receiver. @@ -391,6 +422,8 @@ contract TychoRouter is address receiver, IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature, + bool transferFromRequired, + address tokenInReceiver, bytes calldata swapData ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { // For native ETH, assume funds already in our router. Else, handle approval. @@ -398,6 +431,9 @@ contract TychoRouter is permit2.permit(msg.sender, permitSingle, signature); } tstoreTransferFromInfo(tokenIn, amountIn, true, msg.sender); + if (transferFromRequired) { + _transfer(tokenInReceiver); + } return _singleSwap( amountIn, tokenIn, diff --git a/foundry/src/executors/BalancerV2Executor.sol b/foundry/src/executors/BalancerV2Executor.sol index 00b772a..016163b 100644 --- a/foundry/src/executors/BalancerV2Executor.sol +++ b/foundry/src/executors/BalancerV2Executor.sol @@ -10,16 +10,15 @@ import { import {IAsset} from "@balancer-labs/v2-interfaces/contracts/vault/IAsset.sol"; // slither-disable-next-line solc-version import {IVault} from "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol"; -import {TokenTransfer} from "./TokenTransfer.sol"; error BalancerV2Executor__InvalidDataLength(); -contract BalancerV2Executor is IExecutor, TokenTransfer { +contract BalancerV2Executor is IExecutor { using SafeERC20 for IERC20; address private constant VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8; - constructor(address _permit2) TokenTransfer(_permit2) {} + constructor(address _permit2) {} // slither-disable-next-line locked-ether function swap(uint256 givenAmount, bytes calldata data) @@ -33,19 +32,9 @@ contract BalancerV2Executor is IExecutor, TokenTransfer { bytes32 poolId, address receiver, bool needsApproval, - TransferType transferType + bool transferNeeded ) = _decodeData(data); - _transfer( - address(tokenIn), - msg.sender, - // Receiver can never be the pool, since the pool expects funds in the router contract - // Thus, this call will only ever be used to transfer funds from the user into the router. - address(this), - givenAmount, - transferType - ); - if (needsApproval) { // slither-disable-next-line unused-return tokenIn.forceApprove(VAULT, type(uint256).max); @@ -82,7 +71,7 @@ contract BalancerV2Executor is IExecutor, TokenTransfer { bytes32 poolId, address receiver, bool needsApproval, - TransferType transferType + bool transferNeeded ) { if (data.length != 94) { @@ -94,6 +83,6 @@ contract BalancerV2Executor is IExecutor, TokenTransfer { poolId = bytes32(data[40:72]); receiver = address(bytes20(data[72:92])); needsApproval = uint8(data[92]) > 0; - transferType = TransferType(uint8(data[93])); + transferNeeded = uint8(data[93]) > 0; } } diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index e10a213..f953a63 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.26; import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "./TokenTransfer.sol"; import "@openzeppelin/contracts/utils/Address.sol"; error CurveExecutor__AddressZero(); @@ -35,14 +34,12 @@ interface CryptoPoolETH { // slither-disable-end naming-convention } -contract CurveExecutor is IExecutor, TokenTransfer { +contract CurveExecutor is IExecutor { using SafeERC20 for IERC20; address public immutable nativeToken; - constructor(address _nativeToken, address _permit2) - TokenTransfer(_permit2) - { + constructor(address _nativeToken, address _permit2) { if (_nativeToken == address(0)) { revert CurveExecutor__AddressZero(); } @@ -65,20 +62,10 @@ contract CurveExecutor is IExecutor, TokenTransfer { int128 i, int128 j, bool tokenApprovalNeeded, - TransferType transferType, + bool transferNeeded, // TODO remove this with the encoding address receiver ) = _decodeData(data); - _transfer( - tokenIn, - msg.sender, - // Receiver can never be the pool, since the pool expects funds in the router contract - // Thus, this call will only ever be used to transfer funds from the user into the router. - address(this), - amountIn, - transferType - ); - if (tokenApprovalNeeded && tokenIn != nativeToken) { // slither-disable-next-line unused-return IERC20(tokenIn).forceApprove(address(pool), type(uint256).max); @@ -134,7 +121,7 @@ contract CurveExecutor is IExecutor, TokenTransfer { int128 i, int128 j, bool tokenApprovalNeeded, - TransferType transferType, + bool transferNeeded, address receiver ) { @@ -145,7 +132,7 @@ contract CurveExecutor is IExecutor, TokenTransfer { i = int128(uint128(uint8(data[61]))); j = int128(uint128(uint8(data[62]))); tokenApprovalNeeded = data[63] != 0; - transferType = TransferType(uint8(data[64])); + transferNeeded = data[64] != 0; receiver = address(bytes20(data[65:85])); } diff --git a/foundry/src/executors/EkuboExecutor.sol b/foundry/src/executors/EkuboExecutor.sol index 25d40ab..692626d 100644 --- a/foundry/src/executors/EkuboExecutor.sol +++ b/foundry/src/executors/EkuboExecutor.sol @@ -11,14 +11,14 @@ import {SafeTransferLib} from "@solady/utils/SafeTransferLib.sol"; import {LibBytes} from "@solady/utils/LibBytes.sol"; import {Config, EkuboPoolKey} from "@ekubo/types/poolKey.sol"; import {MAX_SQRT_RATIO, MIN_SQRT_RATIO} from "@ekubo/types/sqrtRatio.sol"; -import {TokenTransfer} from "./TokenTransfer.sol"; +import "../OneTransferFromOnly.sol"; contract EkuboExecutor is IExecutor, ILocker, IPayer, ICallback, - TokenTransfer + OneTransferFromOnly { error EkuboExecutor__InvalidDataLength(); error EkuboExecutor__CoreOnly(); @@ -32,7 +32,9 @@ contract EkuboExecutor is bytes4 constant LOCKED_SELECTOR = 0xb45a3c0e; // locked(uint256) bytes4 constant PAY_CALLBACK_SELECTOR = 0x599d0714; // payCallback(uint256,address) - constructor(address _core, address _permit2) TokenTransfer(_permit2) { + constructor(address _core, address _permit2) + OneTransferFromOnly(_permit2) + { core = ICore(_core); } @@ -44,13 +46,8 @@ contract EkuboExecutor is if (data.length < 93) revert EkuboExecutor__InvalidDataLength(); // amountIn must be at most type(int128).MAX - calculatedAmount = uint256( - _lock( - bytes.concat( - bytes16(uint128(amountIn)), bytes20(msg.sender), data - ) - ) - ); + calculatedAmount = + uint256(_lock(bytes.concat(bytes16(uint128(amountIn)), data))); } function handleCallback(bytes calldata raw) @@ -126,10 +123,11 @@ contract EkuboExecutor is int128 nextAmountIn = int128(uint128(bytes16(swapData[0:16]))); uint128 tokenInDebtAmount = uint128(nextAmountIn); address sender = address(bytes20(swapData[16:36])); - uint8 transferType = uint8(swapData[36]); + bool transferFromNeeded = (swapData[36] != 0); + bool transferNeeded = (swapData[37] != 0); - address receiver = address(bytes20(swapData[37:57])); - address tokenIn = address(bytes20(swapData[57:77])); + address receiver = address(bytes20(swapData[38:58])); + address tokenIn = address(bytes20(swapData[58:78])); address nextTokenIn = tokenIn; @@ -163,7 +161,7 @@ contract EkuboExecutor is offset += HOP_BYTE_LEN; } - _pay(tokenIn, tokenInDebtAmount, sender, transferType); + _pay(tokenIn, tokenInDebtAmount, transferFromNeeded, transferNeeded); core.withdraw(nextTokenIn, receiver, uint128(nextAmountIn)); return nextAmountIn; } @@ -171,8 +169,8 @@ contract EkuboExecutor is function _pay( address token, uint128 amount, - address sender, - uint8 transferType + bool transferFromNeeded, + bool transferNeeded ) internal { address target = address(core); @@ -186,11 +184,11 @@ contract EkuboExecutor is mstore(free, shl(224, 0x0c11dedd)) mstore(add(free, 4), token) mstore(add(free, 36), shl(128, amount)) - mstore(add(free, 52), shl(96, sender)) - mstore(add(free, 72), shl(248, transferType)) + mstore(add(free, 52), shl(248, transferFromNeeded)) + mstore(add(free, 53), shl(248, transferNeeded)) - // 4 (selector) + 32 (token) + 16 (amount) + 20 (sender) + 1 (transferType) = 73 - if iszero(call(gas(), target, 0, free, 73, 0, 0)) { + // 4 (selector) + 32 (token) + 16 (amount) + 1 (transferFromNeeded) + 1 (transferNeeded) = 54 + if iszero(call(gas(), target, 0, free, 54, 0, 0)) { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } @@ -201,9 +199,17 @@ contract EkuboExecutor is function _payCallback(bytes calldata payData) internal { address token = address(bytes20(payData[12:32])); // This arg is abi-encoded uint128 amount = uint128(bytes16(payData[32:48])); - address sender = address(bytes20(payData[48:68])); - TransferType transferType = TransferType(uint8(payData[68])); - _transfer(token, sender, address(core), amount, transferType); + bool transferFromNeeded = (payData[48] != 0); + bool transferNeeded = (payData[49] != 0); + if (transferFromNeeded) { + _transfer(msg.sender); + } else if (transferNeeded) { + if (token == address(0)) { + payable(msg.sender).transfer(amount); + } else { + IERC20(token).transfer(msg.sender, amount); + } + } } // To receive withdrawals from Core diff --git a/foundry/src/executors/MaverickV2Executor.sol b/foundry/src/executors/MaverickV2Executor.sol index caf8ff0..b2dfabe 100644 --- a/foundry/src/executors/MaverickV2Executor.sol +++ b/foundry/src/executors/MaverickV2Executor.sol @@ -9,12 +9,12 @@ error MaverickV2Executor__InvalidDataLength(); error MaverickV2Executor__InvalidTarget(); error MaverickV2Executor__InvalidFactory(); -contract MaverickV2Executor is IExecutor, TokenTransfer { +contract MaverickV2Executor is IExecutor { using SafeERC20 for IERC20; address public immutable factory; - constructor(address _factory, address _permit2) TokenTransfer(_permit2) { + constructor(address _factory, address _permit2) { if (_factory == address(0)) { revert MaverickV2Executor__InvalidFactory(); } @@ -30,9 +30,9 @@ contract MaverickV2Executor is IExecutor, TokenTransfer { address target; address receiver; IERC20 tokenIn; - TransferType transferType; + bool transferNeeded; - (tokenIn, target, receiver, transferType) = _decodeData(data); + (tokenIn, target, receiver, transferNeeded) = _decodeData(data); _verifyPairAddress(target); IMaverickV2Pool pool = IMaverickV2Pool(target); @@ -47,9 +47,15 @@ contract MaverickV2Executor is IExecutor, TokenTransfer { tickLimit: tickLimit }); - _transfer( - address(tokenIn), msg.sender, target, givenAmount, transferType - ); + if (transferNeeded) { + if (address(tokenIn) == address(0)) { + payable(target).transfer(givenAmount); + } else { + // slither-disable-next-line arbitrary-send-erc20 + tokenIn.safeTransferFrom(msg.sender, target, givenAmount); + } + } + // slither-disable-next-line unused-return (, calculatedAmount) = pool.swap(receiver, swapParams, ""); } @@ -61,7 +67,7 @@ contract MaverickV2Executor is IExecutor, TokenTransfer { IERC20 inToken, address target, address receiver, - TransferType transferType + bool transferNeeded ) { if (data.length != 61) { @@ -70,7 +76,7 @@ contract MaverickV2Executor is IExecutor, TokenTransfer { inToken = IERC20(address(bytes20(data[0:20]))); target = address(bytes20(data[20:40])); receiver = address(bytes20(data[40:60])); - transferType = TransferType(uint8(data[60])); + transferNeeded = (data[60] != 0); } function _verifyPairAddress(address target) internal view { diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index 31b6db7..d1f25d9 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -59,8 +59,8 @@ contract UniswapV2Executor is IExecutor { calculatedAmount = _getAmountOut(target, givenAmount, zeroForOne); - if (transferNeeded){ - if (tokenIn == address(0)) { + if (transferNeeded) { + if (address(tokenIn) == address(0)) { payable(target).transfer(givenAmount); } else { // slither-disable-next-line arbitrary-send-erc20 @@ -93,8 +93,8 @@ contract UniswapV2Executor is IExecutor { inToken = IERC20(address(bytes20(data[0:20]))); target = address(bytes20(data[20:40])); receiver = address(bytes20(data[40:60])); - zeroForOne = uint8(data[60]) > 0; - transferNeeded = bool(data[61]); + zeroForOne = data[60] != 0; + transferNeeded = data[61] != 0; } function _getAmountOut(address target, uint256 amountIn, bool zeroForOne) diff --git a/foundry/src/executors/UniswapV3Executor.sol b/foundry/src/executors/UniswapV3Executor.sol index 7585384..23ecc27 100644 --- a/foundry/src/executors/UniswapV3Executor.sol +++ b/foundry/src/executors/UniswapV3Executor.sol @@ -51,8 +51,8 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly { address receiver, address target, bool zeroForOne, - bool inTransferNeeded, - bool inBetweenSwapsTransferNeeded + bool transferFromNeeded, + bool transferNeeded ) = _decodeData(data); _verifyPairAddress(tokenIn, tokenOut, fee, target); @@ -62,11 +62,7 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly { IUniswapV3Pool pool = IUniswapV3Pool(target); bytes memory callbackData = _makeV3CallbackData( - tokenIn, - tokenOut, - fee, - inTransferNeeded, - inBetweenSwapsTransferNeeded + tokenIn, tokenOut, fee, transferFromNeeded, transferNeeded ); { @@ -104,8 +100,8 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly { address tokenIn = address(bytes20(msgData[132:152])); - bool inTransferNeeded = bool(msgData[175]); - bool inBetweenSwapsTransferNeeded = bool(msgData[176]); + bool transferFromNeeded = msgData[175] != 0; + bool transferNeeded = msgData[176] != 0; address sender = address(bytes20(msgData[176:196])); verifyCallback(msgData[132:]); @@ -113,9 +109,9 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly { uint256 amountOwed = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); - if (inTransferNeeded) { + if (transferFromNeeded) { _transfer(msg.sender); - } else if (inBetweenSwapsTransferNeeded) { + } else if (transferNeeded) { if (tokenIn == address(0)) { payable(msg.sender).transfer(amountOwed); } else { @@ -152,8 +148,8 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly { address receiver, address target, bool zeroForOne, - bool inTransferNeeded, - bool inBetweenSwapsTransferNeeded + bool transferFromNeeded, + bool transferNeeded ) { if (data.length != 85) { @@ -165,23 +161,23 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly { receiver = address(bytes20(data[43:63])); target = address(bytes20(data[63:83])); zeroForOne = uint8(data[83]) > 0; - inTransferNeeded = bool(data[84]); - inBetweenSwapsTransferNeeded = bool(data[85]); + transferFromNeeded = uint8(data[84]) > 0; + transferNeeded = uint8(data[85]) > 0; } function _makeV3CallbackData( address tokenIn, address tokenOut, uint24 fee, - bool inTransferNeeded, - bool inBetweenSwapsTransferNeeded + bool transferFromNeeded, + bool transferNeeded ) internal view returns (bytes memory) { return abi.encodePacked( tokenIn, tokenOut, fee, - inTransferNeeded, - inBetweenSwapsTransferNeeded, + transferFromNeeded, + transferNeeded, msg.sender ); } diff --git a/foundry/src/executors/UniswapV4Executor.sol b/foundry/src/executors/UniswapV4Executor.sol index 9b878ad..0d00ce9 100644 --- a/foundry/src/executors/UniswapV4Executor.sol +++ b/foundry/src/executors/UniswapV4Executor.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.26; import "@interfaces/IExecutor.sol"; import {ICallback} from "@interfaces/ICallback.sol"; -import {TokenTransfer} from "./TokenTransfer.sol"; import { IERC20, SafeERC20 @@ -23,6 +22,7 @@ import {IUnlockCallback} from import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; +import "../OneTransferFromOnly.sol"; error UniswapV4Executor__InvalidDataLength(); error UniswapV4Executor__NotPoolManager(); @@ -37,7 +37,7 @@ contract UniswapV4Executor is IExecutor, IUnlockCallback, ICallback, - TokenTransfer + OneTransferFromOnly { using SafeERC20 for IERC20; using CurrencyLibrary for Currency; @@ -57,7 +57,7 @@ contract UniswapV4Executor is } constructor(IPoolManager _poolManager, address _permit2) - TokenTransfer(_permit2) + OneTransferFromOnly(_permit2) { poolManager = _poolManager; _self = address(this); @@ -82,7 +82,8 @@ contract UniswapV4Executor is address tokenIn, address tokenOut, bool zeroForOne, - TransferType transferType, + bool transferFromNeeded, + bool transferNeeded, address receiver, UniswapV4Executor.UniswapV4Pool[] memory pools ) = _decodeData(data); @@ -100,8 +101,8 @@ contract UniswapV4Executor is key, zeroForOne, amountIn, - msg.sender, - transferType, + transferFromNeeded, + transferNeeded, receiver, bytes("") ); @@ -123,8 +124,8 @@ contract UniswapV4Executor is currencyIn, path, amountIn, - msg.sender, - transferType, + transferFromNeeded, + transferNeeded, receiver ); } @@ -142,24 +143,26 @@ contract UniswapV4Executor is address tokenIn, address tokenOut, bool zeroForOne, - TransferType transferType, + bool transferFromNeeded, + bool transferNeeded, address receiver, UniswapV4Pool[] memory pools ) { - if (data.length < 88) { + if (data.length < 89) { revert UniswapV4Executor__InvalidDataLength(); } tokenIn = address(bytes20(data[0:20])); tokenOut = address(bytes20(data[20:40])); zeroForOne = (data[40] != 0); - transferType = TransferType(uint8(data[41])); - receiver = address(bytes20(data[42:62])); + transferFromNeeded = (data[41] != 0); + transferNeeded = (data[42] != 0); + receiver = address(bytes20(data[43:63])); - uint256 poolsLength = (data.length - 62) / 26; // 26 bytes per pool object + uint256 poolsLength = (data.length - 63) / 26; // 26 bytes per pool object pools = new UniswapV4Pool[](poolsLength); - bytes memory poolsData = data[62:]; + bytes memory poolsData = data[63:]; uint256 offset = 0; for (uint256 i = 0; i < poolsLength; i++) { address intermediaryToken; @@ -239,8 +242,8 @@ contract UniswapV4Executor is * @param poolKey The key of the pool to swap in. * @param zeroForOne Whether the swap is from token0 to token1 (true) or vice versa (false). * @param amountIn The amount of tokens to swap in. - * @param sender The address of the sender. - * @param transferType The type of transfer in to use. + * @param transferFromNeeded Whether to transferFrom input tokens into the core contract from the swapper's wallet . + * @param transferNeeded Whether to transfer input tokens into the core contract from the router contract * @param receiver The address of the receiver. * @param hookData Additional data for hook contracts. */ @@ -248,8 +251,8 @@ contract UniswapV4Executor is PoolKey memory poolKey, bool zeroForOne, uint128 amountIn, - address sender, - TransferType transferType, + bool transferFromNeeded, + bool transferNeeded, address receiver, bytes calldata hookData ) external returns (uint128) { @@ -262,7 +265,7 @@ contract UniswapV4Executor is if (amount > amountIn) { revert UniswapV4Executor__V4TooMuchRequested(amountIn, amount); } - _settle(currencyIn, amount, sender, transferType); + _settle(currencyIn, amount, transferFromNeeded, transferNeeded); Currency currencyOut = zeroForOne ? poolKey.currency1 : poolKey.currency0; @@ -275,16 +278,16 @@ contract UniswapV4Executor is * @param currencyIn The currency of the input token. * @param path The path to swap along. * @param amountIn The amount of tokens to swap in. - * @param sender The address of the sender. - * @param transferType The type of transfer in to use. + * @param transferFromNeeded Whether to transferFrom input tokens into the core contract from the swapper's wallet . + * @param transferNeeded Whether to transfer input tokens into the core contract from the router contract * @param receiver The address of the receiver. */ function swapExactInput( Currency currencyIn, PathKey[] calldata path, uint128 amountIn, - address sender, - TransferType transferType, + bool transferFromNeeded, + bool transferNeeded, address receiver ) external returns (uint128) { uint128 amountOut = 0; @@ -315,7 +318,7 @@ contract UniswapV4Executor is if (amount > amountIn) { revert UniswapV4Executor__V4TooMuchRequested(amountIn, amount); } - _settle(currencyIn, amount, sender, transferType); + _settle(currencyIn, amount, transferFromNeeded, transferNeeded); _take( swapCurrencyIn, // at the end of the loop this is actually currency out @@ -387,15 +390,17 @@ contract UniswapV4Executor is * @dev The implementing contract must ensure that the `payer` is a secure address. * @param currency The currency to settle. * @param amount The amount to send. - * @param sender The address of the payer. - * @param transferType The type of transfer to use. + * @param transferFromNeeded Whether to manually transferFrom input tokens into the + * core contract from the swapper. + * @param transferNeeded Whether to manually transfer input tokens into the + * core contract from the router. * @dev Returns early if the amount is 0. */ function _settle( Currency currency, uint256 amount, - address sender, - TransferType transferType + bool transferFromNeeded, + bool transferNeeded ) internal { if (amount == 0) return; poolManager.sync(currency); @@ -403,13 +408,18 @@ contract UniswapV4Executor is // slither-disable-next-line unused-return poolManager.settle{value: amount}(); } else { - _transfer( - Currency.unwrap(currency), - sender, - address(poolManager), - amount, - transferType - ); + if (transferFromNeeded) { + // transferFrom swapper's wallet into the core contract + _transfer(msg.sender); + } else if (transferNeeded) { + address tokenIn = Currency.unwrap(currency); + // transfer from router contract into the core contract + if (tokenIn == address(0)) { + payable(msg.sender).transfer(amount); + } else { + IERC20(tokenIn).safeTransfer(msg.sender, amount); + } + } // slither-disable-next-line unused-return poolManager.settle(); } diff --git a/foundry/test/TychoRouterProtocolIntegration.t.sol b/foundry/test/TychoRouterProtocolIntegration.t.sol index 04af1f0..e1cd732 100644 --- a/foundry/test/TychoRouterProtocolIntegration.t.sol +++ b/foundry/test/TychoRouterProtocolIntegration.t.sol @@ -26,7 +26,8 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { USDE_ADDR, USDT_ADDR, true, - TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL, + true, // permit2 transferFrom to protocol + false, // transfer to protocol ALICE, pools ); @@ -44,6 +45,8 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { ALICE, permitSingle, signature, + false, + address(0), swap ); @@ -74,7 +77,8 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { USDE_ADDR, WBTC_ADDR, true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL, + true, // permit2 transferFrom to protocol + false, // transfer to protocol ALICE, pools ); @@ -83,7 +87,16 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { encodeSingleSwap(address(usv4Executor), protocolData); tychoRouter.singleSwap( - amountIn, USDE_ADDR, WBTC_ADDR, 118280, false, false, ALICE, swap + amountIn, + USDE_ADDR, + WBTC_ADDR, + 118280, + false, + false, + ALICE, + false, + address(0), + swap ); assertEq(IERC20(WBTC_ADDR).balanceOf(ALICE), 118281); @@ -262,7 +275,8 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { ALICE, DAI_WETH_USV3, zeroForOne, - TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL + true, // permit2 transferFrom to protocol + false // transfer to protocol ); bytes memory swap = encodeSingleSwap(address(usv3Executor), protocolData); @@ -277,6 +291,8 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { ALICE, permitSingle, signature, + false, + address(0), swap ); diff --git a/foundry/test/TychoRouterSequentialSwap.t.sol b/foundry/test/TychoRouterSequentialSwap.t.sol index 1d44076..7037e80 100644 --- a/foundry/test/TychoRouterSequentialSwap.t.sol +++ b/foundry/test/TychoRouterSequentialSwap.t.sol @@ -8,25 +8,21 @@ import "./executors/UniswapV4Utils.sol"; import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol"; contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { - function _getSequentialSwaps(bool permit2) - internal - view - returns (bytes[] memory) - { + function _getSequentialSwaps() internal view returns (bytes[] memory) { // Trade 1 WETH for USDC through DAI with 2 swaps on Uniswap V2 // 1 WETH -> DAI -> USDC // (univ2) (univ2) - TokenTransfer.TransferType transferType = permit2 - ? TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL - : TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL; - bytes[] memory swaps = new bytes[](2); // WETH -> DAI swaps[0] = encodeSequentialSwap( address(usv2Executor), encodeUniswapV2Swap( - WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false, transferType + WETH_ADDR, + WETH_DAI_POOL, + DAI_USDC_POOL, // receiver (direct to next pool) + false, + false // transfer to protocol from router ) ); @@ -38,7 +34,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { DAI_USDC_POOL, ALICE, true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + false // transfer to protocol from router ) ); return swaps; @@ -55,7 +51,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { bytes memory signature ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSequentialSwaps(true); + bytes[] memory swaps = _getSequentialSwaps(); tychoRouter.sequentialSwapPermit2( amountIn, WETH_ADDR, @@ -66,6 +62,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { ALICE, permitSingle, signature, + true, + WETH_DAI_POOL, pleEncode(swaps) ); @@ -82,7 +80,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSequentialSwaps(false); + bytes[] memory swaps = _getSequentialSwaps(); tychoRouter.sequentialSwap( amountIn, WETH_ADDR, @@ -91,6 +89,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { false, false, ALICE, + true, + WETH_DAI_POOL, pleEncode(swaps) ); @@ -107,7 +107,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSequentialSwaps(false); + bytes[] memory swaps = _getSequentialSwaps(); vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector); tychoRouter.sequentialSwap( amountIn, @@ -117,6 +117,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { false, false, ALICE, + true, + WETH_DAI_POOL, pleEncode(swaps) ); } @@ -129,7 +131,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn - 1); - bytes[] memory swaps = _getSequentialSwaps(false); + bytes[] memory swaps = _getSequentialSwaps(); vm.expectRevert(); tychoRouter.sequentialSwap( amountIn, @@ -139,6 +141,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { false, false, ALICE, + true, + WETH_DAI_POOL, pleEncode(swaps) ); } @@ -154,7 +158,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { bytes memory signature ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSequentialSwaps(true); + bytes[] memory swaps = _getSequentialSwaps(); uint256 minAmountOut = 3000 * 1e18; @@ -175,6 +179,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { ALICE, permitSingle, signature, + true, + WETH_DAI_POOL, pleEncode(swaps) ); vm.stopPrank(); @@ -202,24 +208,14 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { swaps[0] = encodeSequentialSwap( address(usv2Executor), encodeUniswapV2Swap( - WETH_ADDR, - WETH_DAI_POOL, - tychoRouterAddr, - false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false, true ) ); // DAI -> USDC swaps[1] = encodeSequentialSwap( address(usv2Executor), - encodeUniswapV2Swap( - DAI_ADDR, - DAI_USDC_POOL, - ALICE, - true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL - ) + encodeUniswapV2Swap(DAI_ADDR, DAI_USDC_POOL, ALICE, true, true) ); uint256 amountOut = tychoRouter.sequentialSwapPermit2{value: amountIn}( @@ -232,6 +228,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { ALICE, emptyPermitSingle, "", + true, + tychoRouterAddr, pleEncode(swaps) ); uint256 expectedAmount = 2005810530; @@ -262,11 +260,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { swaps[0] = encodeSequentialSwap( address(usv2Executor), encodeUniswapV2Swap( - USDC_ADDR, - DAI_USDC_POOL, - tychoRouterAddr, - false, - TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL + USDC_ADDR, DAI_USDC_POOL, tychoRouterAddr, false, false ) ); @@ -274,11 +268,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { swaps[1] = encodeSequentialSwap( address(usv2Executor), encodeUniswapV2Swap( - DAI_ADDR, - WETH_DAI_POOL, - tychoRouterAddr, - true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true, true ) ); @@ -292,6 +282,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { ALICE, permitSingle, signature, + true, + DAI_USDC_POOL, pleEncode(swaps) ); @@ -315,7 +307,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3, true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + false, + true ); bytes memory usdcWethV3Pool2OneZeroData = encodeUniswapV3Swap( @@ -324,7 +317,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3_2, false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + false, // permit2 transferFrom to protocol + true // transfer to protocol ); bytes[] memory swaps = new bytes[](2); diff --git a/foundry/test/TychoRouterSingleSwap.t.sol b/foundry/test/TychoRouterSingleSwap.t.sol index bf49fbd..84b452e 100644 --- a/foundry/test/TychoRouterSingleSwap.t.sol +++ b/foundry/test/TychoRouterSingleSwap.t.sol @@ -26,7 +26,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, ALICE, false, - TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL + false // funds already in WETH_DAI_POOL, no transfer necessary ); bytes memory swap = @@ -42,6 +42,8 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { ALICE, permitSingle, signature, + true, // transferFrom to WETH_DAI_POOL + WETH_DAI_POOL, // receiver of input tokens swap ); @@ -67,7 +69,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, ALICE, false, - TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL + false // funds already in WETH_DAI_POOL, no transfer necessary ); bytes memory swap = @@ -82,6 +84,8 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { false, false, ALICE, + true, + WETH_DAI_POOL, swap ); @@ -103,20 +107,24 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn); - bytes memory protocolData = encodeUniswapV2Swap( - WETH_ADDR, - WETH_DAI_POOL, - ALICE, - false, - TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL - ); + bytes memory protocolData = + encodeUniswapV2Swap(WETH_ADDR, WETH_DAI_POOL, ALICE, false, false); bytes memory swap = encodeSingleSwap(address(usv2Executor), protocolData); vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector); tychoRouter.singleSwap( - amountIn, WETH_ADDR, DAI_ADDR, 0, false, false, ALICE, swap + amountIn, + WETH_ADDR, + DAI_ADDR, + 0, + false, + false, + ALICE, + true, + WETH_DAI_POOL, + swap ); } @@ -134,7 +142,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, ALICE, false, - TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL + false // funds already in WETH_DAI_POOL, no transfer necessary ); bytes memory swap = @@ -150,6 +158,8 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { false, false, ALICE, + true, + WETH_DAI_POOL, swap ); } @@ -169,7 +179,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, ALICE, false, - TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL + false // funds already in WETH_DAI_POOL, no transfer necessary ); bytes memory swap = @@ -192,6 +202,8 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { false, false, ALICE, + true, + WETH_DAI_POOL, swap ); } @@ -213,13 +225,8 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { sigDeadline: 0 }); - bytes memory protocolData = encodeUniswapV2Swap( - WETH_ADDR, - WETH_DAI_POOL, - ALICE, - false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL - ); + bytes memory protocolData = + encodeUniswapV2Swap(WETH_ADDR, WETH_DAI_POOL, ALICE, false, true); bytes memory swap = encodeSingleSwap(address(usv2Executor), protocolData); @@ -234,6 +241,8 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { ALICE, emptyPermitSingle, "", + true, + tychoRouterAddr, swap ); uint256 expectedAmount = 2018817438608734439722; @@ -257,11 +266,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { ) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap( - DAI_ADDR, - WETH_DAI_POOL, - tychoRouterAddr, - true, - TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL + DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true, false ); bytes memory swap = @@ -277,6 +282,8 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { ALICE, permitSingle, signature, + true, // transferFrom to WETH_DAI_POOL + WETH_DAI_POOL, // receiver of input tokens swap ); diff --git a/foundry/test/TychoRouterSplitSwap.t.sol b/foundry/test/TychoRouterSplitSwap.t.sol index 417b350..91f845a 100644 --- a/foundry/test/TychoRouterSplitSwap.t.sol +++ b/foundry/test/TychoRouterSplitSwap.t.sol @@ -8,11 +8,7 @@ import "./executors/UniswapV4Utils.sol"; import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol"; contract TychoRouterSplitSwapTest is TychoRouterTestSetup { - function _getSplitSwaps(bool permit2) - private - view - returns (bytes[] memory) - { + function _getSplitSwaps() private view returns (bytes[] memory) { // Trade 1 WETH for USDC through DAI and WBTC with 4 swaps on Uniswap V2 // -> DAI -> // 1 WETH USDC @@ -20,10 +16,6 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { // (univ2) (univ2) bytes[] memory swaps = new bytes[](4); - TokenTransfer.TransferType inTransferType = permit2 - ? TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL - : TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL; - // WETH -> WBTC (60%) swaps[0] = encodeSplitSwap( uint8(0), @@ -31,11 +23,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { (0xffffff * 60) / 100, // 60% address(usv2Executor), encodeUniswapV2Swap( - WETH_ADDR, - WETH_WBTC_POOL, - tychoRouterAddr, - false, - inTransferType + WETH_ADDR, WETH_WBTC_POOL, tychoRouterAddr, false, true ) ); // WBTC -> USDC @@ -44,13 +32,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { uint8(2), uint24(0), address(usv2Executor), - encodeUniswapV2Swap( - WBTC_ADDR, - USDC_WBTC_POOL, - ALICE, - true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL - ) + encodeUniswapV2Swap(WBTC_ADDR, USDC_WBTC_POOL, ALICE, true, true) ); // WETH -> DAI swaps[2] = encodeSplitSwap( @@ -59,7 +41,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { uint24(0), address(usv2Executor), encodeUniswapV2Swap( - WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false, inTransferType + WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false, true ) ); @@ -69,13 +51,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { uint8(2), uint24(0), address(usv2Executor), - encodeUniswapV2Swap( - DAI_ADDR, - DAI_USDC_POOL, - ALICE, - true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL - ) + encodeUniswapV2Swap(DAI_ADDR, DAI_USDC_POOL, ALICE, true, true) ); return swaps; @@ -85,10 +61,9 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { // Trade 1 WETH for USDC through DAI and WBTC - see _getSplitSwaps for more info uint256 amountIn = 1 ether; - deal(WETH_ADDR, ALICE, amountIn); + deal(WETH_ADDR, address(tychoRouterAddr), amountIn); vm.startPrank(ALICE); - IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn); - bytes[] memory swaps = _getSplitSwaps(false); + bytes[] memory swaps = _getSplitSwaps(); tychoRouter.exposedSplitSwap(amountIn, 4, pleEncode(swaps)); vm.stopPrank(); @@ -109,7 +84,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { bytes memory signature ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSplitSwaps(true); + bytes[] memory swaps = _getSplitSwaps(); tychoRouter.splitSwapPermit2( amountIn, @@ -122,6 +97,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ALICE, permitSingle, signature, + true, + tychoRouterAddr, pleEncode(swaps) ); @@ -138,7 +115,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSplitSwaps(false); + bytes[] memory swaps = _getSplitSwaps(); tychoRouter.splitSwap( amountIn, @@ -149,6 +126,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { false, 4, ALICE, + true, + tychoRouterAddr, pleEncode(swaps) ); @@ -165,7 +144,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn); - bytes[] memory swaps = _getSplitSwaps(false); + bytes[] memory swaps = _getSplitSwaps(); vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector); tychoRouter.splitSwap( @@ -177,6 +156,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { false, 4, ALICE, + true, + tychoRouterAddr, pleEncode(swaps) ); vm.stopPrank(); @@ -190,7 +171,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); // Approve less than the amountIn IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn - 1); - bytes[] memory swaps = _getSplitSwaps(false); + bytes[] memory swaps = _getSplitSwaps(); vm.expectRevert(); tychoRouter.splitSwap( @@ -202,6 +183,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { false, 2, ALICE, + true, + tychoRouterAddr, pleEncode(swaps) ); @@ -219,7 +202,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { bytes memory signature ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSplitSwaps(true); + bytes[] memory swaps = _getSplitSwaps(); uint256 minAmountOut = 3000 * 1e18; @@ -241,6 +224,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ALICE, permitSingle, signature, + true, + tychoRouterAddr, pleEncode(swaps) ); vm.stopPrank(); @@ -265,13 +250,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { spender: address(0), sigDeadline: 0 }); - bytes memory protocolData = encodeUniswapV2Swap( - WETH_ADDR, - WETH_DAI_POOL, - ALICE, - false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL - ); + bytes memory protocolData = + encodeUniswapV2Swap(WETH_ADDR, WETH_DAI_POOL, ALICE, false, true); bytes memory swap = encodeSplitSwap( uint8(0), uint8(1), uint24(0), address(usv2Executor), protocolData @@ -290,6 +270,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ALICE, emptyPermitSingle, "", + false, + tychoRouterAddr, pleEncode(swaps) ); uint256 expectedAmount = 2018817438608734439722; @@ -315,11 +297,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap( - DAI_ADDR, - WETH_DAI_POOL, - tychoRouterAddr, - true, - TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL + DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true, true ); bytes memory swap = encodeSplitSwap( @@ -339,6 +317,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ALICE, permitSingle, signature, + true, + tychoRouterAddr, pleEncode(swaps) ); @@ -365,10 +345,10 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { // │ │ // └─ (USV3, 40% split) ──> WETH ─┘ uint256 amountIn = 100 * 10 ** 6; - deal(USDC_ADDR, ALICE, amountIn); + + // Assume funds have already been transferred to tychoRouter + deal(USDC_ADDR, tychoRouterAddr, amountIn); vm.startPrank(ALICE); - // Approve the TychoRouter to spend USDC - IERC20(USDC_ADDR).approve(tychoRouterAddr, amountIn); bytes memory usdcWethV3Pool1ZeroOneData = encodeUniswapV3Swap( USDC_ADDR, @@ -376,7 +356,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3, true, - TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL + false, // transferFrom swapper required + true // transfer from tycho router to protocol ); bytes memory usdcWethV3Pool2ZeroOneData = encodeUniswapV3Swap( @@ -385,7 +366,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3_2, true, - TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL + false, // transferFrom swapper required + true // transfer from tycho router to protocol ); bytes memory wethUsdcV2OneZeroData = encodeUniswapV2Swap( @@ -393,7 +375,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { USDC_WETH_USV2, tychoRouterAddr, false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + true // transfer from tycho router to protocol ); bytes[] memory swaps = new bytes[](3); @@ -443,7 +425,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { USDC_WETH_USV2, tychoRouterAddr, true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + true // transfer required ); bytes memory usdcWethV3Pool1OneZeroData = encodeUniswapV3Swap( @@ -452,7 +434,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3, false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + false, // transferFrom required + true // transfer required ); bytes memory usdcWethV3Pool2OneZeroData = encodeUniswapV3Swap( @@ -461,7 +444,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3_2, false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + false, // transferFrom required + true // transfer required ); bytes[] memory swaps = new bytes[](3); @@ -500,11 +484,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { deal(BASE_USDC, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap( - BASE_USDC, - USDC_MAG7_POOL, - tychoRouterAddr, - true, - TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL + BASE_USDC, USDC_MAG7_POOL, tychoRouterAddr, true, true ); bytes memory swap = encodeSplitSwap( diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index 540027c..31dd507 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -185,10 +185,11 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils { address target, address receiver, bool zero2one, - TokenTransfer.TransferType transferType + bool transferNeeded ) internal pure returns (bytes memory) { - return - abi.encodePacked(tokenIn, target, receiver, zero2one, transferType); + return abi.encodePacked( + tokenIn, target, receiver, zero2one, transferNeeded + ); } function encodeUniswapV3Swap( @@ -197,7 +198,8 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils { address receiver, address target, bool zero2one, - TokenTransfer.TransferType transferType + bool transferFromNeeded, + bool transferNeeded ) internal view returns (bytes memory) { IUniswapV3Pool pool = IUniswapV3Pool(target); return abi.encodePacked( @@ -207,7 +209,8 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils { receiver, target, zero2one, - transferType + transferFromNeeded, + transferNeeded ); } } diff --git a/foundry/test/executors/BalancerV2Executor.t.sol b/foundry/test/executors/BalancerV2Executor.t.sol index 1ad790e..10cf988 100644 --- a/foundry/test/executors/BalancerV2Executor.t.sol +++ b/foundry/test/executors/BalancerV2Executor.t.sol @@ -17,7 +17,7 @@ contract BalancerV2ExecutorExposed is BalancerV2Executor { bytes32 poolId, address receiver, bool needsApproval, - TransferType transferType + bool transferNeeded ) { return _decodeData(data); @@ -41,12 +41,7 @@ contract BalancerV2ExecutorTest is Constants, TestUtils { function testDecodeParams() public view { bytes memory params = abi.encodePacked( - WETH_ADDR, - BAL_ADDR, - WETH_BAL_POOL_ID, - address(2), - true, - TokenTransfer.TransferType.NONE + WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, address(2), true, false ); ( @@ -55,7 +50,7 @@ contract BalancerV2ExecutorTest is Constants, TestUtils { bytes32 poolId, address receiver, bool needsApproval, - TokenTransfer.TransferType transferType + bool transferNeeded ) = balancerV2Exposed.decodeParams(params); assertEq(address(tokenIn), WETH_ADDR); @@ -63,7 +58,6 @@ contract BalancerV2ExecutorTest is Constants, TestUtils { assertEq(poolId, WETH_BAL_POOL_ID); assertEq(receiver, address(2)); assertEq(needsApproval, true); - assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE)); } function testDecodeParamsInvalidDataLength() public { @@ -77,12 +71,7 @@ contract BalancerV2ExecutorTest is Constants, TestUtils { function testSwap() public { uint256 amountIn = 10 ** 18; bytes memory protocolData = abi.encodePacked( - WETH_ADDR, - BAL_ADDR, - WETH_BAL_POOL_ID, - BOB, - true, - TokenTransfer.TransferType.NONE + WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, BOB, true, false ); deal(WETH_ADDR, address(balancerV2Exposed), amountIn); @@ -104,7 +93,7 @@ contract BalancerV2ExecutorTest is Constants, TestUtils { bytes32 poolId, address receiver, bool needsApproval, - TokenTransfer.TransferType transferType + bool transferNeeded ) = balancerV2Exposed.decodeParams(protocolData); assertEq(address(tokenIn), WETH_ADDR); @@ -112,7 +101,6 @@ contract BalancerV2ExecutorTest is Constants, TestUtils { assertEq(poolId, WETH_BAL_POOL_ID); assertEq(receiver, BOB); assertEq(needsApproval, true); - assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE)); } function testSwapIntegration() public { diff --git a/foundry/test/executors/CurveExecutor.t.sol b/foundry/test/executors/CurveExecutor.t.sol index 629ce79..3d61d60 100644 --- a/foundry/test/executors/CurveExecutor.t.sol +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -37,7 +37,7 @@ contract CurveExecutorExposed is CurveExecutor { int128 i, int128 j, bool tokenApprovalNeeded, - TokenTransfer.TransferType transferType, + bool transferNeeded, address receiver ) { @@ -68,7 +68,7 @@ contract CurveExecutorTest is Test, Constants { uint8(2), uint8(0), true, - TokenTransfer.TransferType.NONE, + false, ALICE ); @@ -80,7 +80,7 @@ contract CurveExecutorTest is Test, Constants { int128 i, int128 j, bool tokenApprovalNeeded, - TokenTransfer.TransferType transferType, + bool transferNeeded, address receiver ) = curveExecutorExposed.decodeData(data); @@ -91,7 +91,6 @@ contract CurveExecutorTest is Test, Constants { assertEq(i, 2); assertEq(j, 0); assertEq(tokenApprovalNeeded, true); - assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE)); assertEq(receiver, ALICE); } @@ -295,7 +294,7 @@ contract CurveExecutorTest is Test, Constants { uint8(uint256(uint128(i))), uint8(uint256(uint128(j))), true, - TokenTransfer.TransferType.NONE, + false, receiver ); } diff --git a/foundry/test/executors/EkuboExecutor.t.sol b/foundry/test/executors/EkuboExecutor.t.sol index 23996f9..8b2026b 100644 --- a/foundry/test/executors/EkuboExecutor.t.sol +++ b/foundry/test/executors/EkuboExecutor.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.26; import "../TestUtils.sol"; import {Constants} from "../Constants.sol"; -import {EkuboExecutor, TokenTransfer} from "@src/executors/EkuboExecutor.sol"; +import {EkuboExecutor} from "@src/executors/EkuboExecutor.sol"; import {ICore} from "@ekubo/interfaces/ICore.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {NATIVE_TOKEN_ADDRESS} from "@ekubo/math/constants.sol"; @@ -45,7 +45,7 @@ contract EkuboExecutorTest is Constants, TestUtils { uint256 usdcBalanceBeforeExecutor = USDC.balanceOf(address(executor)); bytes memory data = abi.encodePacked( - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType (transfer from executor to core) + true, // transferNeeded (transfer from executor to core) address(executor), // receiver NATIVE_TOKEN_ADDRESS, // tokenIn USDC_ADDR, // tokenOut @@ -82,7 +82,7 @@ contract EkuboExecutorTest is Constants, TestUtils { uint256 ethBalanceBeforeExecutor = address(executor).balance; bytes memory data = abi.encodePacked( - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType (transfer from executor to core) + true, // transferNeeded (transfer from executor to core) address(executor), // receiver USDC_ADDR, // tokenIn NATIVE_TOKEN_ADDRESS, // tokenOut @@ -140,7 +140,7 @@ contract EkuboExecutorTest is Constants, TestUtils { // Same test case as in swap_encoder::tests::ekubo::test_encode_swap_multi function testMultiHopSwap() public { bytes memory data = abi.encodePacked( - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType + true, // transferNeeded (transfer from executor to core) address(executor), // receiver NATIVE_TOKEN_ADDRESS, // tokenIn USDC_ADDR, // tokenOut of 1st swap diff --git a/foundry/test/executors/MaverickV2Executor.t.sol b/foundry/test/executors/MaverickV2Executor.t.sol index 755570d..241d265 100644 --- a/foundry/test/executors/MaverickV2Executor.t.sol +++ b/foundry/test/executors/MaverickV2Executor.t.sol @@ -17,7 +17,7 @@ contract MaverickV2ExecutorExposed is MaverickV2Executor { IERC20 tokenIn, address target, address receiver, - TransferType transferType + bool transferNeeded ) { return _decodeData(data); @@ -39,27 +39,16 @@ contract MaverickV2ExecutorTest is TestUtils, Constants { } function testDecodeParams() public view { - bytes memory params = abi.encodePacked( - GHO_ADDR, - GHO_USDC_POOL, - address(2), - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL - ); + bytes memory params = + abi.encodePacked(GHO_ADDR, GHO_USDC_POOL, address(2), true); - ( - IERC20 tokenIn, - address target, - address receiver, - TokenTransfer.TransferType transferType - ) = maverickV2Exposed.decodeParams(params); + (IERC20 tokenIn, address target, address receiver, bool transferNeeded) + = maverickV2Exposed.decodeParams(params); assertEq(address(tokenIn), GHO_ADDR); assertEq(target, GHO_USDC_POOL); assertEq(receiver, address(2)); - assertEq( - uint8(transferType), - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL) - ); + assertEq(transferNeeded, true); } function testDecodeParamsInvalidDataLength() public { @@ -72,12 +61,8 @@ contract MaverickV2ExecutorTest is TestUtils, Constants { function testSwap() public { uint256 amountIn = 10e18; - bytes memory protocolData = abi.encodePacked( - GHO_ADDR, - GHO_USDC_POOL, - BOB, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL - ); + bytes memory protocolData = + abi.encodePacked(GHO_ADDR, GHO_USDC_POOL, BOB, true); deal(GHO_ADDR, address(maverickV2Exposed), amountIn); uint256 balanceBefore = USDC.balanceOf(BOB); @@ -94,20 +79,13 @@ contract MaverickV2ExecutorTest is TestUtils, Constants { bytes memory protocolData = loadCallDataFromFile("test_encode_maverick_v2"); - ( - IERC20 tokenIn, - address pool, - address receiver, - TokenTransfer.TransferType transferType - ) = maverickV2Exposed.decodeParams(protocolData); + (IERC20 tokenIn, address pool, address receiver, bool transferNeeded) = + maverickV2Exposed.decodeParams(protocolData); assertEq(address(tokenIn), GHO_ADDR); assertEq(pool, GHO_USDC_POOL); assertEq(receiver, BOB); - assertEq( - uint8(transferType), - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL) - ); + assertEq(transferNeeded, true); } function testSwapIntegration() public { diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 5f81427..0f1b2a5 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -2,7 +2,6 @@ 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"; @@ -23,7 +22,7 @@ contract UniswapV2ExecutorExposed is UniswapV2Executor { address target, address receiver, bool zeroForOne, - TransferType transferType + bool transferNeeded ) { return _decodeData(data); @@ -60,7 +59,6 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { UniswapV2ExecutorExposed pancakeswapV2Exposed; IERC20 WETH = IERC20(WETH_ADDR); IERC20 DAI = IERC20(DAI_ADDR); - IAllowanceTransfer permit2; function setUp() public { uint256 forkBlock = 17323404; @@ -80,34 +78,25 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { PERMIT2_ADDRESS, 25 ); - permit2 = IAllowanceTransfer(PERMIT2_ADDRESS); } function testDecodeParams() public view { - bytes memory params = abi.encodePacked( - WETH_ADDR, - address(2), - address(3), - false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL - ); + bytes memory params = + abi.encodePacked(WETH_ADDR, address(2), address(3), false, true); ( IERC20 tokenIn, address target, address receiver, bool zeroForOne, - TokenTransfer.TransferType transferType + bool transferNeeded ) = uniswapV2Exposed.decodeParams(params); assertEq(address(tokenIn), WETH_ADDR); assertEq(target, address(2)); assertEq(receiver, address(3)); assertEq(zeroForOne, false); - assertEq( - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), - uint8(transferType) - ); + assertEq(transferNeeded, true); } function testDecodeParamsInvalidDataLength() public { @@ -158,13 +147,8 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { 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.TRANSFER_TO_PROTOCOL) - ); + bytes memory protocolData = + abi.encodePacked(WETH_ADDR, WETH_DAI_POOL, BOB, zeroForOne, true); deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); uniswapV2Exposed.swap(amountIn, protocolData); @@ -173,70 +157,12 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { 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.TRANSFER_FROM_TO_PROTOCOL) - ); - - 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.TRANSFER_PERMIT2_TO_PROTOCOL) - ); - - 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) - ); + bytes memory protocolData = + abi.encodePacked(WETH_ADDR, WETH_DAI_POOL, BOB, zeroForOne, false); deal(WETH_ADDR, address(this), amountIn); IERC20(WETH_ADDR).transfer(address(WETH_DAI_POOL), amountIn); @@ -255,20 +181,19 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { address target, address receiver, bool zeroForOne, - TokenTransfer.TransferType transferType + bool transferNeeded ) = uniswapV2Exposed.decodeParams(protocolData); assertEq(address(tokenIn), WETH_ADDR); assertEq(target, 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640); assertEq(receiver, 0x0000000000000000000000000000000000000001); assertEq(zeroForOne, false); - // TRANSFER = 0 - assertEq(0, uint8(transferType)); + assertEq(transferNeeded, false); } function testSwapIntegration() public { bytes memory protocolData = - hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0000"; + hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0001"; uint256 amountIn = 10 ** 18; uint256 amountOut = 1847751195973566072891; deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); @@ -282,13 +207,8 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { 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, - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL) - ); + bytes memory protocolData = + abi.encodePacked(WETH_ADDR, fakePool, BOB, zeroForOne, true); deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); vm.expectRevert(UniswapV2Executor__InvalidTarget.selector); @@ -302,13 +222,8 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { vm.rollFork(26857267); uint256 amountIn = 10 * 10 ** 6; bool zeroForOne = true; - bytes memory protocolData = abi.encodePacked( - BASE_USDC, - USDC_MAG7_POOL, - BOB, - zeroForOne, - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL) - ); + bytes memory protocolData = + abi.encodePacked(BASE_USDC, USDC_MAG7_POOL, BOB, zeroForOne, true); deal(BASE_USDC, address(uniswapV2Exposed), amountIn); diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index e9d95e6..9f3e1dd 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -22,7 +22,8 @@ contract UniswapV3ExecutorExposed is UniswapV3Executor { address receiver, address target, bool zeroForOne, - TransferType transferType + bool transferFromNeeded, + bool transferNeeded ) { return _decodeData(data); @@ -71,7 +72,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address(2), address(3), false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + false, + true ); ( @@ -81,7 +83,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address receiver, address target, bool zeroForOne, - TokenTransfer.TransferType transferType + bool transferFromNeeded, + bool transferNeeded ) = uniswapV3Exposed.decodeData(data); assertEq(tokenIn, WETH_ADDR); @@ -90,10 +93,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { assertEq(receiver, address(2)); assertEq(target, address(3)); assertEq(zeroForOne, false); - assertEq( - uint8(transferType), - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL) - ); + assertEq(transferFromNeeded, false); + assertEq(transferNeeded, true); } function testSwapIntegration() public { @@ -109,7 +110,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address(this), DAI_WETH_USV3, zeroForOne, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + false, + true ); uint256 amountOut = uniswapV3Exposed.swap(amountIn, data); @@ -184,7 +186,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address(this), fakePool, zeroForOne, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + false, + true ); vm.expectRevert(UniswapV3Executor__InvalidTarget.selector); @@ -197,7 +200,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address receiver, address target, bool zero2one, - TokenTransfer.TransferType transferType + bool transferFromNeeded, + bool transferNeeded ) internal view returns (bytes memory) { IUniswapV3Pool pool = IUniswapV3Pool(target); return abi.encodePacked( @@ -207,7 +211,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { receiver, target, zero2one, - transferType + transferFromNeeded, + transferNeeded ); } } diff --git a/foundry/test/executors/UniswapV4Executor.t.sol b/foundry/test/executors/UniswapV4Executor.t.sol index 8f2c292..d2918a0 100644 --- a/foundry/test/executors/UniswapV4Executor.t.sol +++ b/foundry/test/executors/UniswapV4Executor.t.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.26; import "../../src/executors/UniswapV4Executor.sol"; import "../TestUtils.sol"; import "./UniswapV4Utils.sol"; -import "@src/executors/TokenTransfer.sol"; import "@src/executors/UniswapV4Executor.sol"; import {Constants} from "../Constants.sol"; import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol"; @@ -22,7 +21,8 @@ contract UniswapV4ExecutorExposed is UniswapV4Executor { address tokenIn, address tokenOut, bool zeroForOne, - TokenTransfer.TransferType transferType, + bool transferFromNeeded, + bool transferNeeded, address receiver, UniswapV4Pool[] memory pools ) @@ -53,8 +53,8 @@ contract UniswapV4ExecutorTest is Constants, TestUtils { int24 tickSpacing1 = 60; uint24 pool2Fee = 1000; int24 tickSpacing2 = -10; - TokenTransfer.TransferType transferType = - TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL; + bool transferFromNeeded = false; + bool transferNeeded = true; UniswapV4Executor.UniswapV4Pool[] memory pools = new UniswapV4Executor.UniswapV4Pool[](2); @@ -70,14 +70,21 @@ contract UniswapV4ExecutorTest is Constants, TestUtils { }); bytes memory data = UniswapV4Utils.encodeExactInput( - USDE_ADDR, USDT_ADDR, zeroForOne, transferType, ALICE, pools + USDE_ADDR, + USDT_ADDR, + zeroForOne, + transferFromNeeded, + transferNeeded, + ALICE, + pools ); ( address tokenIn, address tokenOut, bool zeroForOneDecoded, - TokenTransfer.TransferType transferTypeDecoded, + bool transferFromNeededDecoded, + bool transferNeededDecoded, address receiver, UniswapV4Executor.UniswapV4Pool[] memory decodedPools ) = uniswapV4Exposed.decodeData(data); @@ -85,7 +92,8 @@ contract UniswapV4ExecutorTest is Constants, TestUtils { assertEq(tokenIn, USDE_ADDR); assertEq(tokenOut, USDT_ADDR); assertEq(zeroForOneDecoded, zeroForOne); - assertEq(uint8(transferTypeDecoded), uint8(transferType)); + assertEq(transferFromNeededDecoded, transferFromNeeded); + assertEq(transferNeededDecoded, transferNeeded); assertEq(receiver, ALICE); assertEq(decodedPools.length, 2); assertEq(decodedPools[0].intermediaryToken, USDT_ADDR); @@ -112,12 +120,7 @@ contract UniswapV4ExecutorTest is Constants, TestUtils { }); bytes memory data = UniswapV4Utils.encodeExactInput( - USDE_ADDR, - USDT_ADDR, - true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL, - ALICE, - pools + USDE_ADDR, USDT_ADDR, true, false, true, ALICE, pools ); uint256 amountOut = uniswapV4Exposed.swap(amountIn, data); @@ -169,12 +172,7 @@ contract UniswapV4ExecutorTest is Constants, TestUtils { }); bytes memory data = UniswapV4Utils.encodeExactInput( - USDE_ADDR, - WBTC_ADDR, - true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL, - ALICE, - pools + USDE_ADDR, WBTC_ADDR, true, false, true, ALICE, pools ); uint256 amountOut = uniswapV4Exposed.swap(amountIn, data); diff --git a/foundry/test/executors/UniswapV4Utils.sol b/foundry/test/executors/UniswapV4Utils.sol index c96465c..aa1e5eb 100644 --- a/foundry/test/executors/UniswapV4Utils.sol +++ b/foundry/test/executors/UniswapV4Utils.sol @@ -8,7 +8,8 @@ library UniswapV4Utils { address tokenIn, address tokenOut, bool zeroForOne, - UniswapV4Executor.TransferType transferType, + bool transferFromNeeded, + bool transferNeeded, address receiver, UniswapV4Executor.UniswapV4Pool[] memory pools ) public pure returns (bytes memory) { @@ -24,7 +25,12 @@ library UniswapV4Utils { } return abi.encodePacked( - tokenIn, tokenOut, zeroForOne, transferType, receiver, encodedPools + tokenIn, + tokenOut, + zeroForOne, + transferNeeded, + receiver, + encodedPools ); } }