diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 9e244f3..2f9683f 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -786,8 +786,8 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { returns (bytes memory) { if (data.length < 24) revert TychoRouter__InvalidDataLength(); - _handleCallback(data); - return ""; + bytes memory result = _handleCallback(data); + return result; } function _balanceOf(address token, address owner) diff --git a/foundry/src/executors/UniswapV4Executor.sol b/foundry/src/executors/UniswapV4Executor.sol index 9191f98..aa9cfe7 100644 --- a/foundry/src/executors/UniswapV4Executor.sol +++ b/foundry/src/executors/UniswapV4Executor.sol @@ -2,28 +2,48 @@ pragma solidity ^0.8.26; import "@interfaces/IExecutor.sol"; +import {ICallback} from "@interfaces/ICallback.sol"; +import {TokenTransfer} from "./TokenTransfer.sol"; import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import { Currency, CurrencyLibrary } from "@uniswap/v4-core/src/types/Currency.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; -import {V4Router} from "@uniswap/v4-periphery/src/V4Router.sol"; -import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol"; -import {IV4Router} from "@uniswap/v4-periphery/src/interfaces/IV4Router.sol"; import {PathKey} from "@uniswap/v4-periphery/src/libraries/PathKey.sol"; -import {ICallback} from "@interfaces/ICallback.sol"; -import {TokenTransfer} from "./TokenTransfer.sol"; +import {IUnlockCallback} from + "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {TransientStateLibrary} from + "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; error UniswapV4Executor__InvalidDataLength(); +error UniswapV4Executor__NotPoolManager(); +error UniswapV4Executor__DeltaNotPositive(Currency currency); +error UniswapV4Executor__DeltaNotNegative(Currency currency); +error UniswapV4Executor__V4TooMuchRequested( + uint256 maxAmountInRequested, uint256 amountRequested +); -contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer { +contract UniswapV4Executor is + IExecutor, + IUnlockCallback, + ICallback, + TokenTransfer +{ using SafeERC20 for IERC20; using CurrencyLibrary for Currency; + using SafeCast for *; + using TransientStateLibrary for IPoolManager; + + IPoolManager public immutable poolManager; struct UniswapV4Pool { address intermediaryToken; @@ -32,9 +52,20 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer { } constructor(IPoolManager _poolManager, address _permit2) - V4Router(_poolManager) TokenTransfer(_permit2) - {} + { + poolManager = _poolManager; + } + + /** + * @dev Modifier to restrict access to only the pool manager. + */ + modifier poolManagerOnly() virtual { + if (msg.sender != address(poolManager)) { + revert UniswapV4Executor__NotPoolManager(); + } + _; + } function swap(uint256 amountIn, bytes calldata data) external @@ -49,18 +80,6 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer { address receiver, UniswapV4Executor.UniswapV4Pool[] memory pools ) = _decodeData(data); - - // TODO move this into callback when we implement callback transfer type support - _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 - ); - bytes memory swapData; if (pools.length == 1) { PoolKey memory key = PoolKey({ @@ -70,26 +89,16 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer { tickSpacing: pools[0].tickSpacing, hooks: IHooks(address(0)) }); - bytes memory actions = abi.encodePacked( - uint8(Actions.SWAP_EXACT_IN_SINGLE), - uint8(Actions.SETTLE_ALL), - uint8(Actions.TAKE) + swapData = abi.encodeWithSelector( + this.swapExactInputSingle.selector, + key, + zeroForOne, + amountIn, + msg.sender, + transferType, + receiver, + bytes("") ); - - bytes[] memory params = new bytes[](3); - - params[0] = abi.encode( - IV4Router.ExactInputSingleParams({ - poolKey: key, - zeroForOne: zeroForOne, - amountIn: uint128(amountIn), - amountOutMinimum: uint128(0), - hookData: bytes("") - }) - ); - params[1] = abi.encode(tokenIn, amountIn); // currency to settle - params[2] = abi.encode(tokenOut, receiver, uint256(0)); // currency to take. 0 means to take the full amount - swapData = abi.encode(actions, params); } else { PathKey[] memory path = new PathKey[](pools.length); for (uint256 i = 0; i < pools.length; i++) { @@ -102,51 +111,22 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer { }); } - bytes memory actions = abi.encodePacked( - uint8(Actions.SWAP_EXACT_IN), - uint8(Actions.SETTLE_ALL), - uint8(Actions.TAKE) - ); - - bytes[] memory params = new bytes[](3); - Currency currencyIn = Currency.wrap(tokenIn); - params[0] = abi.encode( - IV4Router.ExactInputParams({ - currencyIn: currencyIn, - path: path, - amountIn: uint128(amountIn), - amountOutMinimum: uint128(0) - }) + swapData = abi.encodeWithSelector( + this.swapExactInput.selector, + currencyIn, + path, + amountIn, + msg.sender, + transferType, + receiver ); - params[1] = abi.encode(currencyIn, amountIn); - params[2] = - abi.encode(Currency.wrap(tokenOut), receiver, uint256(0)); - swapData = abi.encode(actions, params); } - uint256 tokenOutBalanceBefore; - tokenOutBalanceBefore = tokenOut == address(0) - ? receiver.balance - : IERC20(tokenOut).balanceOf(receiver); + bytes memory result = poolManager.unlock(swapData); + uint128 amountOut = abi.decode(result, (uint128)); - executeActions(swapData); - - uint256 tokenOutBalanceAfter; - - tokenOutBalanceAfter = tokenOut == address(0) - ? receiver.balance - : IERC20(tokenOut).balanceOf(receiver); - - calculatedAmount = tokenOutBalanceAfter - tokenOutBalanceBefore; - - return calculatedAmount; - } - - // necessary to convert bytes memory to bytes calldata - function executeActions(bytes memory unlockData) public { - // slither-disable-next-line unused-return - poolManager.unlock(unlockData); + return amountOut; } function _decodeData(bytes calldata data) @@ -191,6 +171,9 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer { } } + /** + * @notice Handles the callback from the pool manager. This is used for callbacks from the router. + */ function handleCallback(bytes calldata data) external returns (bytes memory) @@ -199,15 +182,256 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer { return _unlockCallback(data); } - function verifyCallback(bytes calldata) public view onlyPoolManager {} + function verifyCallback(bytes calldata) public view poolManagerOnly {} - function _pay(Currency token, address, uint256 amount) internal override { - IERC20(Currency.unwrap(token)).safeTransfer( - address(poolManager), amount - ); + /** + * @notice Handles the unlock callback from the pool manager. This is used for swaps against the executor directly (bypassing the router). + */ + function unlockCallback(bytes calldata data) + external + poolManagerOnly + returns (bytes memory) + { + return _unlockCallback(data); } - function msgSender() public view override returns (address) { - return address(this); + /** + * @dev Internal function to handle the unlock callback. + * The executor address is needed to perform the call. If the router is being used, the executor address is in + * transient storage. If it is not, then address(this) should be used. + */ + function _unlockCallback(bytes calldata data) + internal + returns (bytes memory) + { + address executor; + // slither-disable-next-line assembly + assembly { + executor := tload(0) + } + + if (executor == address(0)) { + executor = address(this); + } + // here we expect to call either `swapExactInputSingle` or `swapExactInput`. See `swap` to see how we encode the selector and the calldata + // slither-disable-next-line low-level-calls + (bool success, bytes memory returnData) = executor.delegatecall(data); + if (!success) { + revert( + string( + returnData.length > 0 + ? returnData + : abi.encodePacked("Uniswap v4 Callback failed") + ) + ); + } + return returnData; + } + + /** + * @notice Performs an exact input single swap. It settles and takes the tokens after the swap. + * @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 receiver The address of the receiver. + * @param hookData Additional data for hook contracts. + */ + function swapExactInputSingle( + PoolKey memory poolKey, + bool zeroForOne, + uint128 amountIn, + address sender, + TransferType transferType, + address receiver, + bytes calldata hookData + ) external returns (uint128) { + uint128 amountOut = _swap( + poolKey, zeroForOne, -int256(uint256(amountIn)), hookData + ).toUint128(); + + Currency currencyIn = zeroForOne ? poolKey.currency0 : poolKey.currency1; + uint256 amount = _getFullDebt(currencyIn); + if (amount > amountIn) { + revert UniswapV4Executor__V4TooMuchRequested(amountIn, amount); + } + _settle(currencyIn, amount, sender, transferType); + + Currency currencyOut = + zeroForOne ? poolKey.currency1 : poolKey.currency0; + _take(currencyOut, receiver, _mapTakeAmount(amountOut, currencyOut)); + return amountOut; + } + + /** + * @notice Performs an exact input swap along a path. It settles and takes the tokens after the swap. + * @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 receiver The address of the receiver. + */ + function swapExactInput( + Currency currencyIn, + PathKey[] calldata path, + uint128 amountIn, + address sender, + TransferType transferType, + address receiver + ) external returns (uint128) { + uint128 amountOut = 0; + Currency swapCurrencyIn = currencyIn; + uint256 swapAmountIn = amountIn; + unchecked { + uint256 pathLength = path.length; + PathKey calldata pathKey; + + for (uint256 i = 0; i < pathLength; i++) { + pathKey = path[i]; + (PoolKey memory poolKey, bool zeroForOne) = + pathKey.getPoolAndSwapDirection(swapCurrencyIn); + // The output delta will always be positive, except for when interacting with certain hook pools + amountOut = _swap( + poolKey, + zeroForOne, + -int256(uint256(swapAmountIn)), + pathKey.hookData + ).toUint128(); + + swapAmountIn = amountOut; + swapCurrencyIn = pathKey.intermediateCurrency; + } + } + + uint256 amount = _getFullDebt(currencyIn); + if (amount > amountIn) { + revert UniswapV4Executor__V4TooMuchRequested(amountIn, amount); + } + _settle(currencyIn, amount, sender, transferType); + + _take( + swapCurrencyIn, // at the end of the loop this is actually currency out + receiver, + _mapTakeAmount(amountOut, swapCurrencyIn) + ); + return amountOut; + } + + function _swap( + PoolKey memory poolKey, + bool zeroForOne, + int256 amountSpecified, + bytes calldata hookData + ) private returns (int128 reciprocalAmount) { + unchecked { + // slither-disable-next-line calls-loop + BalanceDelta delta = poolManager.swap( + poolKey, + IPoolManager.SwapParams( + zeroForOne, + amountSpecified, + zeroForOne + ? TickMath.MIN_SQRT_PRICE + 1 + : TickMath.MAX_SQRT_PRICE - 1 + ), + hookData + ); + + reciprocalAmount = (zeroForOne == amountSpecified < 0) + ? delta.amount1() + : delta.amount0(); + } + } + + /** + * @notice Obtains the full amount owed by this contract (negative delta). + * @param currency The currency to get the delta for. + * @return amount The amount owed by this contract. + */ + function _getFullCredit(Currency currency) + internal + view + returns (uint256 amount) + { + int256 _amount = poolManager.currencyDelta(address(this), currency); + // If the amount is negative, it should be settled not taken. + if (_amount < 0) revert UniswapV4Executor__DeltaNotPositive(currency); + amount = uint256(_amount); + } + + /// @notice Obtain the full amount owed by this contract (negative delta) + /// @param currency Currency to get the delta for + /// @return amount The amount owed by this contract as a uint256 + function _getFullDebt(Currency currency) + internal + view + returns (uint256 amount) + { + int256 _amount = poolManager.currencyDelta(address(this), currency); + // If the amount is positive, it should be taken not settled. + if (_amount > 0) revert UniswapV4Executor__DeltaNotNegative(currency); + // Casting is safe due to limits on the total supply of a pool + amount = uint256(-_amount); + } + + /** + * @notice Pays and settles a currency to the pool manager. + * @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. + * @dev Returns early if the amount is 0. + */ + function _settle( + Currency currency, + uint256 amount, + address sender, + TransferType transferType + ) internal { + if (amount == 0) return; + poolManager.sync(currency); + if (currency.isAddressZero()) { + // slither-disable-next-line unused-return + poolManager.settle{value: amount}(); + } else { + _transfer( + Currency.unwrap(currency), + sender, + address(poolManager), + amount, + transferType + ); + // slither-disable-next-line unused-return + poolManager.settle(); + } + } + + /** + * @notice Takes an amount of currency out of the pool manager. + * @param currency The currency to take. + * @param recipient The address to receive the currency. + * @param amount The amount to take. + * @dev Returns early if the amount is 0. + */ + function _take(Currency currency, address recipient, uint256 amount) + internal + { + if (amount == 0) return; + poolManager.take(currency, recipient, amount); + } + + function _mapTakeAmount(uint256 amount, Currency currency) + internal + view + returns (uint256) + { + if (amount == 0) { + return _getFullCredit(currency); + } else { + return amount; + } } } diff --git a/foundry/test/TychoRouterProtocolIntegration.t.sol b/foundry/test/TychoRouterProtocolIntegration.t.sol index b2710c1..238b4f2 100644 --- a/foundry/test/TychoRouterProtocolIntegration.t.sol +++ b/foundry/test/TychoRouterProtocolIntegration.t.sol @@ -20,7 +20,7 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_sequential_encoding_strategy_usv4` (bool success,) = tychoRouterAddr.call( - hex"51bcc7b6000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000000000000000682db60700000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000006806300f00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041c5715acd97f16c669ba5b6a15d911e61f9b5a056c1bb4f0576dbf7c1251bddd70ac5e929270186517e593e1c8d1d1ecf5c742576affcd5d64cac409600ad054e1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000880086f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d23119330004cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f4000000000000000000000000000000000000000000000000" + hex"7c553846000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000006828a8d900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000680122e10000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000412fb2c4e85c1b2236aef343641c10f81e4abfd675f520d86778cb9db16c9f500d11fe28b99285dd1bef082b9ccde3360a8077c57ece0775677fddfd5ff11b6e081c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d008b0001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d23119330002cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f400000000000000000000000000000000000000" ); vm.stopPrank(); @@ -43,7 +43,7 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { // Encoded solution generated using `test_single_encoding_strategy_usv4_eth_in` (bool success,) = tychoRouterAddr.call{value: 1 ether}( - hex"30ace1b10000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933000000000000000000000000000000000000000000c87c939ae635f92dc2379c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000682db9c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000680633c800000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000417d375e095d10a0d69c183082f533f2393e7ec356e4d222d32943ecab59683b013047017436b824fb8d00c2cdda2ab4136da5bc32ea79c6305b237633f6d0978c1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006cf62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330105cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc26982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000000000000000000000000000" + hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933000000000000000000000000000000000000000000c87c939ae635f92dc2379c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006828a8d900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000680122e1000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041b7928a6257d4f01539c357c322036b5df1799313f83a119c843a239ca474955820f791f028fa10a9fe3ec0d6be7d782e5824ac1942e27ebd2a0a3e1687bec4451c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007300710001000000f62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330105cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc26982508145454ce325ddbe47a25d4ec3d23119330061a80001f400000000000000000000000000" ); vm.stopPrank(); @@ -70,7 +70,7 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { // Encoded solution generated using `test_single_encoding_strategy_usv4_eth_out` (bool success,) = tychoRouterAddr.call( - hex"7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000000000000000000000000000000000006826193a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067fe9342000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041dbb84c68a4293bcf6303ca45327614667c54226086ddcfa2daa9289c1657da9a57268f4d8ceea3c831d43e5a96b1dc54766bc3fda8845d5c7e266981b9d84c651b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007300710001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000004cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c00000000000000000000000000" + hex"7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000000000000000000000000000000000006828a8d900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000680122e1000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041bf1373f3d3943e0865f8081b1569b4deb66b56b8690500c4c9f1c1f7e1299510720e3d4c92abf6ec75f0b14a87b92957fd43408562f26b8616857469f94012e21b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007300710001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000002cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c00000000000000000000000000" ); vm.stopPrank(); diff --git a/foundry/test/TychoRouterSplitSwap.t.sol b/foundry/test/TychoRouterSplitSwap.t.sol index b086c6e..b26e16e 100644 --- a/foundry/test/TychoRouterSplitSwap.t.sol +++ b/foundry/test/TychoRouterSplitSwap.t.sol @@ -427,7 +427,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { USDE_ADDR, USDT_ADDR, true, - TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_ROUTER, + TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL, ALICE, pools ); @@ -480,7 +480,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { USDE_ADDR, WBTC_ADDR, true, - TokenTransfer.TransferType.NONE, + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL, ALICE, pools ); diff --git a/foundry/test/executors/UniswapV4Executor.t.sol b/foundry/test/executors/UniswapV4Executor.t.sol index 9ff7f72..17acd0a 100644 --- a/foundry/test/executors/UniswapV4Executor.t.sol +++ b/foundry/test/executors/UniswapV4Executor.t.sol @@ -114,7 +114,7 @@ contract UniswapV4ExecutorTest is Test, Constants { USDE_ADDR, USDT_ADDR, true, - TokenTransfer.TransferType.NONE, + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL, ALICE, pools ); @@ -172,7 +172,7 @@ contract UniswapV4ExecutorTest is Test, Constants { USDE_ADDR, WBTC_ADDR, true, - TokenTransfer.TransferType.NONE, + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL, ALICE, pools ); diff --git a/src/encoding/evm/constants.rs b/src/encoding/evm/constants.rs index 1ac6bb3..d4a4863 100644 --- a/src/encoding/evm/constants.rs +++ b/src/encoding/evm/constants.rs @@ -28,6 +28,7 @@ pub static IN_TRANSFER_REQUIRED_PROTOCOLS: LazyLock> = Laz set.insert("pancakeswap_v2"); set.insert("uniswap_v3"); set.insert("pancakeswap_v3"); + set.insert("uniswap_v4"); set.insert("ekubo_v2"); set }); diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 950c721..7dd7afd 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -2314,7 +2314,7 @@ mod tests { "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // group token in "6982508145454ce325ddbe47a25d4ec3d2311933", // group token in "00", // zero2one - "04", // transfer type (transfer to router) + "02", // transfer type (transfer to router) "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver // First pool params "0000000000000000000000000000000000000000", // intermediary token (ETH)