feat(univ4): Implement swapping with multiple hops

--- don't change below this line ---
ENG-4222 Took 47 minutes
This commit is contained in:
Diana Carvalho
2025-02-14 11:32:38 +00:00
parent be7883affc
commit 21a8c1a27a
4 changed files with 88 additions and 11 deletions

View File

@@ -19,7 +19,6 @@ import {PathKey} from "@uniswap/v4-periphery/src/libraries/PathKey.sol";
import "lib/forge-std/src/console.sol";
error UniswapV4Executor__InvalidDataLength();
error UniswapV4Executor__SwapFailed();
contract UniswapV4Executor is IExecutor, V4Router {
using SafeERC20 for IERC20;
@@ -49,9 +48,7 @@ contract UniswapV4Executor is IExecutor, V4Router {
) = _decodeData(data);
bytes memory fullData;
if (pools.length == 0) {
console.log("problem"); // raise error
} else if (pools.length == 1) {
if (pools.length == 1) {
PoolKey memory key = PoolKey({
currency0: Currency.wrap(zeroForOne ? tokenIn : tokenOut),
currency1: Currency.wrap(zeroForOne ? tokenOut : tokenIn),
@@ -77,12 +74,44 @@ contract UniswapV4Executor is IExecutor, V4Router {
})
);
params[1] = abi.encode(key.currency0, amountIn);
params[2] = abi.encode(key.currency1, 0);
params[2] = abi.encode(key.currency1, amountOutMin);
bytes memory swapData = abi.encode(actions, params);
fullData =
abi.encodePacked(callbackExecutor, callbackSelector, swapData);
} else {
console.log("do later");
PathKey[] memory path = new PathKey[](pools.length);
for (uint256 i = 0; i < pools.length; i++) {
path[i] = PathKey({
intermediateCurrency: Currency.wrap(pools[i].intermediaryToken),
fee: pools[i].fee,
tickSpacing: pools[i].tickSpacing,
hooks: IHooks(address(0)),
hookData: bytes("")
});
}
bytes memory actions = abi.encodePacked(
uint8(Actions.SWAP_EXACT_IN),
uint8(Actions.SETTLE_ALL),
uint8(Actions.TAKE_ALL)
);
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(amountOutMin)
})
);
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);
}
uint256 tokenOutBalanceBefore;
@@ -122,7 +151,9 @@ contract UniswapV4Executor is IExecutor, V4Router {
UniswapV4Pool[] memory pools
)
{
require(data.length >= 97, "Invalid data length");
if(data.length < 123) {
revert UniswapV4Executor__InvalidDataLength();
}
tokenIn = address(bytes20(data[0:20]));
tokenOut = address(bytes20(data[20:40]));

View File

@@ -865,7 +865,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
tickSpacing: int24(1)
});
bytes memory protocolData = UniswapV4Utils.encodeExactInputSingle(
bytes memory protocolData = UniswapV4Utils.encodeExactInput(
USDE_ADDR,
USDT_ADDR,
uint256(1),
@@ -889,6 +889,52 @@ contract TychoRouterTest is TychoRouterTestSetup {
tychoRouter.exposedSwap(amountIn, 2, pleEncode(swaps));
assertTrue(IERC20(USDT_ADDR).balanceOf(tychoRouterAddr) == 99943852);
assertEq(IERC20(USDT_ADDR).balanceOf(tychoRouterAddr), 99943852);
}
function testSwapMultipleUSV4Callback() public {
// This test has two uniswap v4 hops that will be executed inside of the V4 pool manager
// USDE -> USDT -> WBTC
uint256 amountIn = 100 ether;
deal(USDE_ADDR, tychoRouterAddr, amountIn);
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 protocolData = UniswapV4Utils.encodeExactInput(
USDE_ADDR,
WBTC_ADDR,
uint256(1),
true,
address(usv4Executor),
SafeCallback.unlockCallback.selector,
pools
);
bytes memory swap = encodeSwap(
uint8(0),
uint8(1),
uint24(0),
address(usv4Executor),
bytes4(0),
protocolData
);
bytes[] memory swaps = new bytes[](1);
swaps[0] = swap;
tychoRouter.exposedSwap(amountIn, 2, pleEncode(swaps));
assertEq(IERC20(WBTC_ADDR).balanceOf(tychoRouterAddr), 102718);
}
}

View File

@@ -64,7 +64,7 @@ contract UniswapV4ExecutorTest is Test, Constants {
tickSpacing: tickSpacing2
});
bytes memory data = UniswapV4Utils.encodeExactInputSingle(
bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR,
USDT_ADDR,
minAmountOut,

View File

@@ -4,7 +4,7 @@ pragma solidity ^0.8.26;
import "@src/executors/UniswapV4Executor.sol";
library UniswapV4Utils {
function encodeExactInputSingle(
function encodeExactInput(
address tokenIn,
address tokenOut,
uint256 amountOutMin,