feat(univ4): Implement swapping with multiple hops
--- don't change below this line --- ENG-4222 Took 47 minutes
This commit is contained in:
@@ -19,7 +19,6 @@ import {PathKey} from "@uniswap/v4-periphery/src/libraries/PathKey.sol";
|
|||||||
import "lib/forge-std/src/console.sol";
|
import "lib/forge-std/src/console.sol";
|
||||||
|
|
||||||
error UniswapV4Executor__InvalidDataLength();
|
error UniswapV4Executor__InvalidDataLength();
|
||||||
error UniswapV4Executor__SwapFailed();
|
|
||||||
|
|
||||||
contract UniswapV4Executor is IExecutor, V4Router {
|
contract UniswapV4Executor is IExecutor, V4Router {
|
||||||
using SafeERC20 for IERC20;
|
using SafeERC20 for IERC20;
|
||||||
@@ -49,9 +48,7 @@ contract UniswapV4Executor is IExecutor, V4Router {
|
|||||||
) = _decodeData(data);
|
) = _decodeData(data);
|
||||||
|
|
||||||
bytes memory fullData;
|
bytes memory fullData;
|
||||||
if (pools.length == 0) {
|
if (pools.length == 1) {
|
||||||
console.log("problem"); // raise error
|
|
||||||
} else if (pools.length == 1) {
|
|
||||||
PoolKey memory key = PoolKey({
|
PoolKey memory key = PoolKey({
|
||||||
currency0: Currency.wrap(zeroForOne ? tokenIn : tokenOut),
|
currency0: Currency.wrap(zeroForOne ? tokenIn : tokenOut),
|
||||||
currency1: Currency.wrap(zeroForOne ? tokenOut : tokenIn),
|
currency1: Currency.wrap(zeroForOne ? tokenOut : tokenIn),
|
||||||
@@ -77,12 +74,44 @@ contract UniswapV4Executor is IExecutor, V4Router {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
params[1] = abi.encode(key.currency0, amountIn);
|
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);
|
bytes memory swapData = abi.encode(actions, params);
|
||||||
fullData =
|
fullData =
|
||||||
abi.encodePacked(callbackExecutor, callbackSelector, swapData);
|
abi.encodePacked(callbackExecutor, callbackSelector, swapData);
|
||||||
} else {
|
} 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;
|
uint256 tokenOutBalanceBefore;
|
||||||
@@ -122,7 +151,9 @@ contract UniswapV4Executor is IExecutor, V4Router {
|
|||||||
UniswapV4Pool[] memory pools
|
UniswapV4Pool[] memory pools
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
require(data.length >= 97, "Invalid data length");
|
if(data.length < 123) {
|
||||||
|
revert UniswapV4Executor__InvalidDataLength();
|
||||||
|
}
|
||||||
|
|
||||||
tokenIn = address(bytes20(data[0:20]));
|
tokenIn = address(bytes20(data[0:20]));
|
||||||
tokenOut = address(bytes20(data[20:40]));
|
tokenOut = address(bytes20(data[20:40]));
|
||||||
|
|||||||
@@ -865,7 +865,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
|
|||||||
tickSpacing: int24(1)
|
tickSpacing: int24(1)
|
||||||
});
|
});
|
||||||
|
|
||||||
bytes memory protocolData = UniswapV4Utils.encodeExactInputSingle(
|
bytes memory protocolData = UniswapV4Utils.encodeExactInput(
|
||||||
USDE_ADDR,
|
USDE_ADDR,
|
||||||
USDT_ADDR,
|
USDT_ADDR,
|
||||||
uint256(1),
|
uint256(1),
|
||||||
@@ -889,6 +889,52 @@ contract TychoRouterTest is TychoRouterTestSetup {
|
|||||||
|
|
||||||
tychoRouter.exposedSwap(amountIn, 2, pleEncode(swaps));
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
|||||||
tickSpacing: tickSpacing2
|
tickSpacing: tickSpacing2
|
||||||
});
|
});
|
||||||
|
|
||||||
bytes memory data = UniswapV4Utils.encodeExactInputSingle(
|
bytes memory data = UniswapV4Utils.encodeExactInput(
|
||||||
USDE_ADDR,
|
USDE_ADDR,
|
||||||
USDT_ADDR,
|
USDT_ADDR,
|
||||||
minAmountOut,
|
minAmountOut,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ pragma solidity ^0.8.26;
|
|||||||
import "@src/executors/UniswapV4Executor.sol";
|
import "@src/executors/UniswapV4Executor.sol";
|
||||||
|
|
||||||
library UniswapV4Utils {
|
library UniswapV4Utils {
|
||||||
function encodeExactInputSingle(
|
function encodeExactInput(
|
||||||
address tokenIn,
|
address tokenIn,
|
||||||
address tokenOut,
|
address tokenOut,
|
||||||
uint256 amountOutMin,
|
uint256 amountOutMin,
|
||||||
|
|||||||
Reference in New Issue
Block a user