diff --git a/foundry/src/Dispatcher.sol b/foundry/src/Dispatcher.sol index a5354c7..6daaaa8 100644 --- a/foundry/src/Dispatcher.sol +++ b/foundry/src/Dispatcher.sol @@ -52,7 +52,7 @@ contract Dispatcher { * @dev Calls an executor, assumes swap.protocolData contains * protocol-specific data required by the executor. */ - // slither-disable-next-line delegatecall-loop + // slither-disable-next-line delegatecall-loop,assembly function _callExecutor( address executor, uint256 amount, @@ -62,6 +62,10 @@ contract Dispatcher { revert Dispatcher__UnapprovedExecutor(executor); } + assembly { + tstore(0, executor) + } + // slither-disable-next-line controlled-delegatecall,low-level-calls (bool success, bytes memory result) = executor.delegatecall( abi.encodeWithSelector(IExecutor.swap.selector, amount, data) @@ -80,8 +84,15 @@ contract Dispatcher { calculatedAmount = abi.decode(result, (uint256)); } - function _handleCallback(bytes calldata data) internal { - address executor = address(uint160(bytes20(data[data.length - 20:]))); + // slither-disable-next-line assembly + function _handleCallback(bytes calldata data) + internal + returns (bytes memory) + { + address executor; + assembly { + executor := tload(0) + } if (!executors[executor]) { revert Dispatcher__UnapprovedExecutor(executor); @@ -101,5 +112,14 @@ contract Dispatcher { ) ); } + + // to prevent multiple callbacks + assembly { + tstore(0, 0) + } + + // this is necessary because the delegatecall will prepend extra bytes we don't want like the length and prefix + bytes memory decodedResult = abi.decode(result, (bytes)); + return decodedResult; } } diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index dcfe35d..955cd7d 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -745,7 +745,12 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { * @dev We use the fallback function to allow flexibility on callback. */ fallback() external { - _handleCallback(msg.data); + bytes memory result = _handleCallback(msg.data); + // slither-disable-next-line assembly + assembly ("memory-safe") { + // Propagate the calculatedAmount + return(add(result, 32), 16) + } } /** @@ -880,45 +885,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { require(msg.sender.code.length != 0); } - /** - * @dev Called by UniswapV3 pool when swapping on it. - * See in IUniswapV3SwapCallback for documentation. - */ - function uniswapV3SwapCallback( - int256, /* amount0Delta */ - int256, /* amount1Delta */ - bytes calldata data - ) external { - if (data.length < 24) revert TychoRouter__InvalidDataLength(); - // We are taking advantage of the fact that the data we need is already encoded in the correct format inside msg.data - // This way we preserve the bytes calldata (and don't need to convert it to bytes memory) - uint256 dataOffset = 4 + 32 + 32 + 32; // Skip selector + 2 ints + data_offset - uint256 dataLength = - uint256(bytes32(msg.data[dataOffset:dataOffset + 32])); - - bytes calldata fullData = msg.data[4:dataOffset + 32 + dataLength]; - _handleCallback(fullData); - } - - /** - * @dev Called by PancakeV3 pool when swapping on it. - */ - function pancakeV3SwapCallback( - int256, /* amount0Delta */ - int256, /* amount1Delta */ - bytes calldata data - ) external { - if (data.length < 24) revert TychoRouter__InvalidDataLength(); - // We are taking advantage of the fact that the data we need is already encoded in the correct format inside msg.data - // This way we preserve the bytes calldata (and don't need to convert it to bytes memory) - uint256 dataOffset = 4 + 32 + 32 + 32; // Skip selector + 2 ints + data_offset - uint256 dataLength = - uint256(bytes32(msg.data[dataOffset:dataOffset + 32])); - - bytes calldata fullData = msg.data[4:dataOffset + 32 + dataLength]; - _handleCallback(fullData); - } - /** * @dev Called by UniswapV4 pool manager after achieving unlock state. */ @@ -930,44 +896,4 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { _handleCallback(data); return ""; } - - function locked(uint256) external { - address executor = address(0x4f88f6630a33dB05BEa1FeF7Dc7ff7508D1c531D); - - // slither-disable-next-line controlled-delegatecall,low-level-calls - (bool success, bytes memory result) = executor.delegatecall(msg.data); - - if (!success) { - revert( - string( - result.length > 0 - ? result - : abi.encodePacked("Callback failed") - ) - ); - } - - // slither-disable-next-line assembly - assembly ("memory-safe") { - // Propagate the swappedAmount - return(add(result, 32), 16) - } - } - - function payCallback(uint256, address /*token*/ ) external { - address executor = address(0x4f88f6630a33dB05BEa1FeF7Dc7ff7508D1c531D); - - // slither-disable-next-line controlled-delegatecall,low-level-calls - (bool success, bytes memory result) = executor.delegatecall(msg.data); - - if (!success) { - revert( - string( - result.length > 0 - ? result - : abi.encodePacked("Callback failed") - ) - ); - } - } } diff --git a/foundry/src/executors/EkuboExecutor.sol b/foundry/src/executors/EkuboExecutor.sol index 564b9d2..ad67205 100644 --- a/foundry/src/executors/EkuboExecutor.sol +++ b/foundry/src/executors/EkuboExecutor.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.26; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IExecutor} from "@interfaces/IExecutor.sol"; +import {ICallback} from "@interfaces/ICallback.sol"; import {ICore} from "@ekubo/interfaces/ICore.sol"; import {ILocker, IPayer} from "@ekubo/interfaces/IFlashAccountant.sol"; import {NATIVE_TOKEN_ADDRESS} from "@ekubo/math/constants.sol"; @@ -11,16 +12,19 @@ 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"; -contract EkuboExecutor is IExecutor, ILocker, IPayer { +contract EkuboExecutor is IExecutor, ILocker, IPayer, ICallback { error EkuboExecutor__InvalidDataLength(); error EkuboExecutor__CoreOnly(); error EkuboExecutor__UnknownCallback(); ICore immutable core; - uint256 constant POOL_DATA_OFFSET = 92; + uint256 constant POOL_DATA_OFFSET = 56; uint256 constant HOP_BYTE_LEN = 52; + bytes4 constant LOCKED_SELECTOR = 0xb45a3c0e; // locked(uint256) + bytes4 constant PAY_CALLBACK_SELECTOR = 0x599d0714; // payCallback(uint256,address) + constructor(address _core) { core = ICore(_core); } @@ -37,60 +41,45 @@ contract EkuboExecutor is IExecutor, ILocker, IPayer { uint256(_lock(bytes.concat(bytes16(uint128(amountIn)), data))); } - function locked(uint256) external coreOnly { - int128 nextAmountIn = int128(uint128(bytes16(msg.data[36:52]))); - uint128 tokenInDebtAmount = uint128(nextAmountIn); + function handleCallback(bytes calldata raw) + external + returns (bytes memory) + { + verifyCallback(raw); - address receiver = address(bytes20(msg.data[52:72])); - address tokenIn = address(bytes20(msg.data[72:POOL_DATA_OFFSET])); + // Without selector and locker id + bytes calldata stripped = raw[36:]; - address nextTokenIn = tokenIn; + bytes4 selector = bytes4(raw[:4]); - uint256 hopsLength = (msg.data.length - POOL_DATA_OFFSET) / HOP_BYTE_LEN; - - uint256 offset = POOL_DATA_OFFSET; - - for (uint256 i = 0; i < hopsLength; i++) { - address nextTokenOut = - address(bytes20(LibBytes.loadCalldata(msg.data, offset))); - Config poolConfig = - Config.wrap(LibBytes.loadCalldata(msg.data, offset + 20)); - - (address token0, address token1, bool isToken1) = nextTokenIn - > nextTokenOut - ? (nextTokenOut, nextTokenIn, true) - : (nextTokenIn, nextTokenOut, false); - - // slither-disable-next-line calls-loop - (int128 delta0, int128 delta1) = core.swap_611415377( - EkuboPoolKey(token0, token1, poolConfig), - nextAmountIn, - isToken1, - isToken1 ? MAX_SQRT_RATIO : MIN_SQRT_RATIO, - 0 - ); - - nextTokenIn = nextTokenOut; - nextAmountIn = -(isToken1 ? delta0 : delta1); - - offset += HOP_BYTE_LEN; + bytes memory result = ""; + if (selector == LOCKED_SELECTOR) { + int128 calculatedAmount = _locked(stripped); + result = abi.encodePacked(calculatedAmount); + } else if (selector == PAY_CALLBACK_SELECTOR) { + _payCallback(stripped); + } else { + revert EkuboExecutor__UnknownCallback(); } - _pay(tokenIn, tokenInDebtAmount); + return result; + } - core.withdraw(nextTokenIn, receiver, uint128(nextAmountIn)); + function verifyCallback(bytes calldata) public view coreOnly {} + function locked(uint256) external coreOnly { + // Without selector and locker id + int128 calculatedAmount = _locked(msg.data[36:]); // slither-disable-next-line assembly assembly ("memory-safe") { - mstore(0, nextAmountIn) + mstore(0, calculatedAmount) return(0x10, 16) } } - function payCallback(uint256, address token) external coreOnly { - uint128 amount = uint128(bytes16(msg.data[68:84])); - - SafeTransferLib.safeTransfer(token, address(core), amount); + function payCallback(uint256, address /*token*/ ) external coreOnly { + // Without selector and locker id + _payCallback(msg.data[36:]); } function _lock(bytes memory data) @@ -121,6 +110,52 @@ contract EkuboExecutor is IExecutor, ILocker, IPayer { } } + function _locked(bytes calldata swapData) internal returns (int128) { + int128 nextAmountIn = int128(uint128(bytes16(swapData[0:16]))); + uint128 tokenInDebtAmount = uint128(nextAmountIn); + + address receiver = address(bytes20(swapData[16:36])); + address tokenIn = address(bytes20(swapData[36:POOL_DATA_OFFSET])); + + address nextTokenIn = tokenIn; + + uint256 hopsLength = (swapData.length - POOL_DATA_OFFSET) / HOP_BYTE_LEN; + + uint256 offset = POOL_DATA_OFFSET; + + for (uint256 i = 0; i < hopsLength; i++) { + address nextTokenOut = + address(bytes20(LibBytes.loadCalldata(swapData, offset))); + Config poolConfig = + Config.wrap(LibBytes.loadCalldata(swapData, offset + 20)); + + (address token0, address token1, bool isToken1) = nextTokenIn + > nextTokenOut + ? (nextTokenOut, nextTokenIn, true) + : (nextTokenIn, nextTokenOut, false); + + // slither-disable-next-line calls-loop + (int128 delta0, int128 delta1) = core.swap_611415377( + EkuboPoolKey(token0, token1, poolConfig), + nextAmountIn, + isToken1, + isToken1 ? MAX_SQRT_RATIO : MIN_SQRT_RATIO, + 0 + ); + + nextTokenIn = nextTokenOut; + nextAmountIn = -(isToken1 ? delta0 : delta1); + + offset += HOP_BYTE_LEN; + } + + _pay(tokenIn, tokenInDebtAmount); + + core.withdraw(nextTokenIn, receiver, uint128(nextAmountIn)); + + return nextAmountIn; + } + function _pay(address token, uint128 amount) internal { address target = address(core); @@ -144,6 +179,13 @@ contract EkuboExecutor is IExecutor, ILocker, IPayer { } } + 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])); + + SafeTransferLib.safeTransfer(token, address(core), amount); + } + // To receive withdrawals from Core receive() external payable {} diff --git a/foundry/src/executors/UniswapV3Executor.sol b/foundry/src/executors/UniswapV3Executor.sol index 1d46c08..7b6383f 100644 --- a/foundry/src/executors/UniswapV3Executor.sol +++ b/foundry/src/executors/UniswapV3Executor.sol @@ -80,6 +80,7 @@ contract UniswapV3Executor is IExecutor, ICallback { returns (bytes memory result) { // The data has the following layout: + // - selector (4 bytes) // - amount0Delta (32 bytes) // - amount1Delta (32 bytes) // - dataOffset (32 bytes) @@ -87,11 +88,11 @@ contract UniswapV3Executor is IExecutor, ICallback { // - protocolData (variable length) (int256 amount0Delta, int256 amount1Delta) = - abi.decode(msgData[:64], (int256, int256)); + abi.decode(msgData[4:68], (int256, int256)); - address tokenIn = address(bytes20(msgData[128:148])); + address tokenIn = address(bytes20(msgData[132:152])); - verifyCallback(msgData[128:]); + verifyCallback(msgData[132:]); uint256 amountOwed = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); @@ -147,10 +148,10 @@ contract UniswapV3Executor is IExecutor, ICallback { function _makeV3CallbackData(address tokenIn, address tokenOut, uint24 fee) internal - view + pure returns (bytes memory) { - return abi.encodePacked(tokenIn, tokenOut, fee, self); + return abi.encodePacked(tokenIn, tokenOut, fee); } function _verifyPairAddress( diff --git a/foundry/src/executors/UniswapV4Executor.sol b/foundry/src/executors/UniswapV4Executor.sol index a487ef1..694937f 100644 --- a/foundry/src/executors/UniswapV4Executor.sol +++ b/foundry/src/executors/UniswapV4Executor.sol @@ -41,7 +41,6 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback { address tokenIn, address tokenOut, bool zeroForOne, - address callbackExecutor, UniswapV4Executor.UniswapV4Pool[] memory pools ) = _decodeData(data); @@ -107,14 +106,13 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback { params[2] = abi.encode(Currency.wrap(tokenOut), uint256(0)); swapData = abi.encode(actions, params); } - bytes memory fullData = abi.encodePacked(swapData, callbackExecutor); uint256 tokenOutBalanceBefore; tokenOutBalanceBefore = tokenOut == address(0) ? address(this).balance : IERC20(tokenOut).balanceOf(address(this)); - executeActions(fullData); + executeActions(swapData); uint256 tokenOutBalanceAfter; @@ -140,22 +138,20 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback { address tokenIn, address tokenOut, bool zeroForOne, - address callbackExecutor, UniswapV4Pool[] memory pools ) { - if (data.length < 87) { + if (data.length < 67) { revert UniswapV4Executor__InvalidDataLength(); } tokenIn = address(bytes20(data[0:20])); tokenOut = address(bytes20(data[20:40])); zeroForOne = (data[40] != 0); - callbackExecutor = address(bytes20(data[41:61])); - uint256 poolsLength = (data.length - 61) / 26; // 26 bytes per pool object + uint256 poolsLength = (data.length - 41) / 26; // 26 bytes per pool object pools = new UniswapV4Pool[](poolsLength); - bytes memory poolsData = data[61:]; + bytes memory poolsData = data[41:]; uint256 offset = 0; for (uint256 i = 0; i < poolsLength; i++) { address intermediaryToken; diff --git a/foundry/test/TychoRouterIntegration.t.sol b/foundry/test/TychoRouterIntegration.t.sol index 27ac0e9..9042a89 100644 --- a/foundry/test/TychoRouterIntegration.t.sol +++ b/foundry/test/TychoRouterIntegration.t.sol @@ -58,7 +58,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_split_encoding_strategy_usv4` (bool success,) = tychoRouterAddr.call( - hex"7c553846000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000006814875700000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ed015f0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000413a7c6367c69ac46fc2b633fd53e583b74b20ec9b3ea83b069fe564765560a4cb335af200fd90ddb5f56d11e469c11a97420499f1b3ee0c1db13149a74daa90db1b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008c008a0001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d231193300f62849f9a0b5bf2913b396098f7c7019b51a820a0000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000000000000000000000000000" + hex"7c553846000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000000000000000681f1d6200000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f7976a0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000416af8ab519cbe8f466aed6188c8966ca4910d72d40d2f4360f62dbe75864b77ce5ec168870bb1540673b3f720c4f2bfaa5de2a79813c857ad5cc49b20217c65041c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007800760001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d2311933000000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000" ); vm.stopPrank(); @@ -81,7 +81,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { // Encoded solution generated using `test_split_encoding_strategy_usv4_eth_in` (bool success,) = tychoRouterAddr.call{value: 1 ether}( - hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933000000000000000000000000000000000000000000c87c939ae635f92dc2379c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006814877000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ed017800000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004193acc98d79044e8ec1bc3ced832dc679e38ac8c6fe9b5befd1e5e44cb44edb0e365f1c5d6e3ca6590ed1a053f1841aede29e5b573f046387aff794520a0f22581b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007200700001000000f62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d231193301f62849f9a0b5bf2913b396098f7c7019b51a820a6982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000000000000000" + hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933000000000000000000000000000000000000000000c87c939ae635f92dc2379c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000681f1d7100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f79779000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041c9ad125cc7c7188d1f0cd61dd8ee0d752d45c89ade1df275c5e0ce17525c6ff137bd976ebd0d17223c007e1f0a2806d086b4c7015460ebd21fef8a6caf8368d51c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e005c0001000000f62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933016982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000" ); vm.stopPrank(); @@ -108,7 +108,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { // Encoded solution generated using `test_split_encoding_strategy_usv4_eth_out` (bool success,) = tychoRouterAddr.call( - hex"7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000000000000000000000000000000000006814878000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ed018800000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004190134d2d142caff6dbea417292a15685119bd676b2b73bad35fe39f720f7c3163f16d057327499019506b6f690a3916fd3375c579c9cb814113b1516187380531b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007200700001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000f62849f9a0b5bf2913b396098f7c7019b51a820a0000000000000000000000000000000000000000000bb800003c0000000000000000000000000000" + hex"7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e0000000000000000000000000000000000000000000000000000000000681f1d7e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f797860000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000412beedebe0200a3d4ca03f34697af8ec027fe6c164e3d3b78e17d1c327dcfc8241ce8bda513f092e54b35856637e5e485a06cc8c1c6287f15f469d47e84fd91341b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e005c0001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000000000000000000000000000bb800003c0000" ); vm.stopPrank(); @@ -173,13 +173,6 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { tychoRouter.setExecutors(executors); vm.stopPrank(); - // TEMPORARY while the Ekubo executor address is hardcoded in TychoRouter - // This allows us to change the code at that address to be the testing executor code - vm.etch( - 0x4f88f6630a33dB05BEa1FeF7Dc7ff7508D1c531D, - 0x3D7Ebc40AF7092E3F1C81F2e996cbA5Cae2090d7.code - ); - deal(ALICE, 1 ether); uint256 balancerBefore = IERC20(USDC_ADDR).balanceOf(ALICE); diff --git a/foundry/test/TychoRouterSplitSwap.t.sol b/foundry/test/TychoRouterSplitSwap.t.sol index 8e8e20e..12ae2a4 100644 --- a/foundry/test/TychoRouterSplitSwap.t.sol +++ b/foundry/test/TychoRouterSplitSwap.t.sol @@ -492,9 +492,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tickSpacing: int24(1) }); - bytes memory protocolData = UniswapV4Utils.encodeExactInput( - USDE_ADDR, USDT_ADDR, true, address(usv4Executor), pools - ); + bytes memory protocolData = + UniswapV4Utils.encodeExactInput(USDE_ADDR, USDT_ADDR, true, pools); bytes memory swap = encodeSplitSwap( uint8(0), uint8(1), uint24(0), address(usv4Executor), protocolData @@ -540,9 +539,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tickSpacing: int24(60) }); - bytes memory protocolData = UniswapV4Utils.encodeExactInput( - USDE_ADDR, WBTC_ADDR, true, address(usv4Executor), pools - ); + bytes memory protocolData = + UniswapV4Utils.encodeExactInput(USDE_ADDR, WBTC_ADDR, true, pools); bytes memory swap = encodeSplitSwap( uint8(0), uint8(1), uint24(0), address(usv4Executor), protocolData diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index 01f5d1c..59b03ba 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -111,6 +111,7 @@ contract UniswapV3ExecutorTest is Test, Constants { uint256 dataLength = protocolData.length; bytes memory callbackData = abi.encodePacked( + bytes4(0xfa461e33), int256(amountOwed), // amount0Delta int256(0), // amount1Delta dataOffset, @@ -124,24 +125,6 @@ contract UniswapV3ExecutorTest is Test, Constants { assertEq(finalPoolReserve - initialPoolReserve, amountOwed); } - function testSwapIntegration() public { - uint256 amountIn = 10 ** 18; - deal(WETH_ADDR, address(uniswapV3Exposed), amountIn); - - uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI - bool zeroForOne = false; - - bytes memory data = encodeUniswapV3Swap( - WETH_ADDR, DAI_ADDR, address(this), DAI_WETH_USV3, zeroForOne - ); - - uint256 amountOut = uniswapV3Exposed.swap(amountIn, data); - - assertGe(amountOut, expAmountOut); - assertEq(IERC20(WETH_ADDR).balanceOf(address(uniswapV3Exposed)), 0); - assertGe(IERC20(DAI_ADDR).balanceOf(address(this)), expAmountOut); - } - function testSwapFailureInvalidTarget() public { uint256 amountIn = 10 ** 18; deal(WETH_ADDR, address(uniswapV3Exposed), amountIn); diff --git a/foundry/test/executors/UniswapV4Executor.t.sol b/foundry/test/executors/UniswapV4Executor.t.sol index e3b3cb6..487e251 100644 --- a/foundry/test/executors/UniswapV4Executor.t.sol +++ b/foundry/test/executors/UniswapV4Executor.t.sol @@ -18,7 +18,6 @@ contract UniswapV4ExecutorExposed is UniswapV4Executor { address tokenIn, address tokenOut, bool zeroForOne, - address callbackExecutor, UniswapV4Pool[] memory pools ) { @@ -62,21 +61,19 @@ contract UniswapV4ExecutorTest is Test, Constants { }); bytes memory data = UniswapV4Utils.encodeExactInput( - USDE_ADDR, USDT_ADDR, zeroForOne, address(uniswapV4Exposed), pools + USDE_ADDR, USDT_ADDR, zeroForOne, pools ); ( address tokenIn, address tokenOut, bool zeroForOneDecoded, - address callbackExecutor, UniswapV4Executor.UniswapV4Pool[] memory decodedPools ) = uniswapV4Exposed.decodeData(data); assertEq(tokenIn, USDE_ADDR); assertEq(tokenOut, USDT_ADDR); assertEq(zeroForOneDecoded, zeroForOne); - assertEq(callbackExecutor, address(uniswapV4Exposed)); assertEq(decodedPools.length, 2); assertEq(decodedPools[0].intermediaryToken, USDT_ADDR); assertEq(decodedPools[0].fee, pool1Fee); @@ -101,9 +98,8 @@ contract UniswapV4ExecutorTest is Test, Constants { tickSpacing: int24(1) }); - bytes memory data = UniswapV4Utils.encodeExactInput( - USDE_ADDR, USDT_ADDR, true, address(uniswapV4Exposed), pools - ); + bytes memory data = + UniswapV4Utils.encodeExactInput(USDE_ADDR, USDT_ADDR, true, pools); uint256 amountOut = uniswapV4Exposed.swap(amountIn, data); assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn); @@ -118,7 +114,7 @@ contract UniswapV4ExecutorTest is Test, Constants { // USDE -> USDT // Generated by the Tycho swap encoder - test_encode_uniswap_v4_simple_swap bytes memory protocolData = - hex"4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec701f62849f9a0b5bf2913b396098f7c7019b51a820adac17f958d2ee523a2206206994597c13d831ec7000064000001"; + hex"4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec701dac17f958d2ee523a2206206994597c13d831ec7000064000001"; uint256 amountIn = 100 ether; deal(USDE_ADDR, address(uniswapV4Exposed), amountIn); uint256 usdeBalanceBeforePool = USDE.balanceOf(poolManager); @@ -155,9 +151,8 @@ contract UniswapV4ExecutorTest is Test, Constants { tickSpacing: int24(60) }); - bytes memory data = UniswapV4Utils.encodeExactInput( - USDE_ADDR, WBTC_ADDR, true, address(uniswapV4Exposed), pools - ); + bytes memory data = + UniswapV4Utils.encodeExactInput(USDE_ADDR, WBTC_ADDR, true, pools); uint256 amountOut = uniswapV4Exposed.swap(amountIn, data); assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn); @@ -175,7 +170,7 @@ contract UniswapV4ExecutorTest is Test, Constants { // Generated by the Tycho swap encoder - test_encode_uniswap_v4_sequential_swap bytes memory protocolData = - hex"4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c59901f62849f9a0b5bf2913b396098f7c7019b51a820adac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c"; + hex"4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c59901dac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c"; uint256 amountIn = 100 ether; deal(USDE_ADDR, address(uniswapV4Exposed), amountIn); diff --git a/foundry/test/executors/UniswapV4Utils.sol b/foundry/test/executors/UniswapV4Utils.sol index b84bb69..67c7c7f 100644 --- a/foundry/test/executors/UniswapV4Utils.sol +++ b/foundry/test/executors/UniswapV4Utils.sol @@ -8,7 +8,6 @@ library UniswapV4Utils { address tokenIn, address tokenOut, bool zeroForOne, - address callbackExecutor, UniswapV4Executor.UniswapV4Pool[] memory pools ) public pure returns (bytes memory) { bytes memory encodedPools; @@ -22,8 +21,6 @@ library UniswapV4Utils { ); } - return abi.encodePacked( - tokenIn, tokenOut, zeroForOne, callbackExecutor, encodedPools - ); + return abi.encodePacked(tokenIn, tokenOut, zeroForOne, encodedPools); } } diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index a9ecf61..d1c683b 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -1326,9 +1326,9 @@ mod tests { let expected_swaps = String::from(concat!( // length of ple encoded swaps without padding - "000000000000000000000000000000000000000000000000000000000000008c", + "0000000000000000000000000000000000000000000000000000000000000078", // ple encoded swaps - "008a", // Swap length + "0076", // Swap length "00", // token in index "01", // token out index "000000", // split @@ -1338,7 +1338,6 @@ mod tests { "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // group token in "6982508145454ce325ddbe47a25d4ec3d2311933", // group token in "00", // zero2one - "f62849f9a0b5bf2913b396098f7c7019b51a820a", // executor address // First pool params "0000000000000000000000000000000000000000", // intermediary token (ETH) "000bb8", // fee @@ -1347,7 +1346,7 @@ mod tests { "6982508145454ce325ddbe47a25d4ec3d2311933", // intermediary token (PEPE) "0061a8", // fee "0001f4", // tick spacing - "0000000000000000000000000000000000000000" // padding + "0000000000000000" // padding )); let hex_calldata = encode(&calldata); diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index cf41e88..a4414b1 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -196,21 +196,11 @@ impl SwapEncoder for UniswapV4SwapEncoder { let group_token_out_address = bytes_to_address(&encoding_context.group_token_out)?; let zero_to_one = Self::get_zero_to_one(token_in_address, token_out_address); - let callback_executor = - bytes_to_address(&Bytes::from_str(&self.executor_address).map_err(|_| { - EncodingError::FatalError("Invalid UniswapV4 executor address".into()) - })?)?; let pool_params = (token_out_address, pool_fee_u24, pool_tick_spacing_u24).abi_encode_packed(); - let args = ( - group_token_in_address, - group_token_out_address, - zero_to_one, - callback_executor, - pool_params, - ); + let args = (group_token_in_address, group_token_out_address, zero_to_one, pool_params); Ok(args.abi_encode_packed()) } @@ -785,8 +775,6 @@ mod tests { "dac17f958d2ee523a2206206994597c13d831ec7", // zero for one "01", - // executor address - "f62849f9a0b5bf2913b396098f7c7019b51a820a", // pool params: // - intermediary token "dac17f958d2ee523a2206206994597c13d831ec7", @@ -950,8 +938,6 @@ mod tests { "2260fac5e5542a773aa44fbcfedf7c193bc2c599", // zero for one "01", - // executor address - "f62849f9a0b5bf2913b396098f7c7019b51a820a", // pool params: // - intermediary token USDT "dac17f958d2ee523a2206206994597c13d831ec7", diff --git a/src/encoding/evm/tycho_encoders.rs b/src/encoding/evm/tycho_encoders.rs index 53ba904..2c7f474 100644 --- a/src/encoding/evm/tycho_encoders.rs +++ b/src/encoding/evm/tycho_encoders.rs @@ -1044,8 +1044,6 @@ mod tests { "6982508145454ce325ddbe47a25d4ec3d2311933", // zero for one "00", - // executor address - "f62849f9a0b5bf2913b396098f7c7019b51a820a", // first pool intermediary token (ETH) "0000000000000000000000000000000000000000", // fee