From 4d0f5cec64af9c65f5a03685d4c89bb0dd0a897c Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Fri, 14 Feb 2025 11:43:59 +0000 Subject: [PATCH] fix(univ4): Append callback data instead of prepending Bring back tests on the executor level. This way the executor can actually be used alone --- don't change below this line --- ENG-4222 Took 12 minutes --- foundry/src/TychoRouter.sol | 6 +- foundry/src/executors/UniswapV4Executor.sol | 15 ++-- .../test/executors/UniswapV4Executor.t.sol | 76 +++++++++++++++++++ 3 files changed, 85 insertions(+), 12 deletions(-) diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 205a6a4..c26e8cb 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -449,9 +449,9 @@ contract TychoRouter is returns (bytes memory) { require(data.length >= 20, "Invalid data length"); - address executor = address(uint160(bytes20(data[0:20]))); - bytes4 selector = bytes4(data[20:24]); - bytes memory protocolData = data[24:]; + bytes4 selector = bytes4(data[data.length - 4:]); + address executor = address(uint160(bytes20(data[data.length - 24:data.length - 4]))); + bytes memory protocolData = data[:data.length - 24]; if (!executors[executor]) { revert ExecutionDispatcher__UnapprovedExecutor(); diff --git a/foundry/src/executors/UniswapV4Executor.sol b/foundry/src/executors/UniswapV4Executor.sol index 57ba690..545729a 100644 --- a/foundry/src/executors/UniswapV4Executor.sol +++ b/foundry/src/executors/UniswapV4Executor.sol @@ -16,7 +16,6 @@ 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 "lib/forge-std/src/console.sol"; error UniswapV4Executor__InvalidDataLength(); @@ -47,7 +46,7 @@ contract UniswapV4Executor is IExecutor, V4Router { UniswapV4Executor.UniswapV4Pool[] memory pools ) = _decodeData(data); - bytes memory fullData; + bytes memory swapData; if (pools.length == 1) { PoolKey memory key = PoolKey({ currency0: Currency.wrap(zeroForOne ? tokenIn : tokenOut), @@ -75,9 +74,8 @@ contract UniswapV4Executor is IExecutor, V4Router { ); params[1] = abi.encode(key.currency0, amountIn); params[2] = abi.encode(key.currency1, amountOutMin); - bytes memory swapData = abi.encode(actions, params); - fullData = - abi.encodePacked(callbackExecutor, callbackSelector, swapData); + swapData = abi.encode(actions, params); + } else { PathKey[] memory path = new PathKey[](pools.length); for (uint256 i = 0; i < pools.length; i++) { @@ -109,11 +107,10 @@ contract UniswapV4Executor is IExecutor, V4Router { ); params[1] = abi.encode(currencyIn, amountIn); params[2] = abi.encode(Currency.wrap(tokenOut), amountOutMin); - bytes memory swapData = abi.encode(actions, params); - fullData = - abi.encodePacked(callbackExecutor, callbackSelector, swapData); + swapData = abi.encode(actions, params); } - + bytes memory fullData = + abi.encodePacked( swapData, callbackExecutor, callbackSelector); uint256 tokenOutBalanceBefore; tokenOutBalanceBefore = tokenOut == address(0) diff --git a/foundry/test/executors/UniswapV4Executor.t.sol b/foundry/test/executors/UniswapV4Executor.t.sol index 7aa9db9..c9afd56 100644 --- a/foundry/test/executors/UniswapV4Executor.t.sol +++ b/foundry/test/executors/UniswapV4Executor.t.sol @@ -98,4 +98,80 @@ contract UniswapV4ExecutorTest is Test, Constants { assertEq(decodedPools[1].fee, pool2Fee); assertEq(decodedPools[1].tickSpacing, tickSpacing2); } + + function testSingleSwap() public { + uint256 amountIn = 100 ether; + deal(USDE_ADDR, address(uniswapV4Exposed), amountIn); + uint256 usdeBalanceBeforePool = USDE.balanceOf(poolManager); + uint256 usdeBalanceBeforeSwapExecutor = + USDE.balanceOf(address(uniswapV4Exposed)); + + UniswapV4Executor.UniswapV4Pool[] memory pools = + new UniswapV4Executor.UniswapV4Pool[](1); + pools[0] = UniswapV4Executor.UniswapV4Pool({ + intermediaryToken: USDT_ADDR, + fee: uint24(100), + tickSpacing: int24(1) + }); + + bytes memory data = UniswapV4Utils.encodeExactInput( + USDE_ADDR, + USDT_ADDR, + uint256(1), + true, + address(uniswapV4Exposed), + SafeCallback.unlockCallback.selector, + pools + ); + + uint256 amountOut = uniswapV4Exposed.swap(amountIn, data); + assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn); + assertEq( + USDE.balanceOf(address(uniswapV4Exposed)), + usdeBalanceBeforeSwapExecutor - amountIn + ); + assertTrue(USDT.balanceOf(address(uniswapV4Exposed)) == amountOut); + } + + function testMultipleSwap() public { + // USDE -> USDT -> WBTC + uint256 amountIn = 100 ether; + deal(USDE_ADDR, address(uniswapV4Exposed), amountIn); + uint256 usdeBalanceBeforePool = USDE.balanceOf(poolManager); + uint256 usdeBalanceBeforeSwapExecutor = + USDE.balanceOf(address(uniswapV4Exposed)); + + + UniswapV4Executor.UniswapV4Pool[] memory pools = + new UniswapV4Executor.UniswapV4Pool[](2); + pools[0] = UniswapV4Executor.UniswapV4Pool({ + intermediaryToken: USDT_ADDR, + fee: uint24(100), + tickSpacing: int24(1) + }); + pools[1] = UniswapV4Executor.UniswapV4Pool({ + intermediaryToken: WBTC_ADDR, + fee: uint24(3000), + tickSpacing: int24(60) + }); + + bytes memory data = UniswapV4Utils.encodeExactInput( + USDE_ADDR, + WBTC_ADDR, + uint256(1), + true, + address(uniswapV4Exposed), + SafeCallback.unlockCallback.selector, + pools + ); + + uint256 amountOut = uniswapV4Exposed.swap(amountIn, data); + assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn); + assertEq( + USDE.balanceOf(address(uniswapV4Exposed)), + usdeBalanceBeforeSwapExecutor - amountIn + ); + assertTrue(IERC20(WBTC_ADDR).balanceOf(address(uniswapV4Exposed)) == amountOut); + } + }