feat: (WIP) Support selection of transfer into router

- For protocols like Balancer and Curve, which expect funds to be in the router at the time of swap, we must support also transferring funds from the user into the router. Doing this in the router would mean we are dealing with transfers in two different places: in the router main methods and in the executors. To avoid this, we are now performing transfers just in the executors, and two transfer types have been added to support transfers into the router.

TODO:
- Add this for Balancer and Curve (only added for USV4 atm).
- TODO consider renaming TRANSFER_FROM and TRANSFER_PERMIT2 to include "pool" in the name
This commit is contained in:
TAMARA LIPOWSKI
2025-04-11 23:25:45 -04:00
committed by Diana Carvalho
parent 59a80dc392
commit a301a1cef3
19 changed files with 426 additions and 240 deletions

View File

@@ -8,13 +8,22 @@ import "./executors/UniswapV4Utils.sol";
import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol";
contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
function _getSplitSwaps() private view returns (bytes[] memory) {
function _getSplitSwaps(bool permit2)
private
view
returns (bytes[] memory)
{
// Trade 1 WETH for USDC through DAI and WBTC with 4 swaps on Uniswap V2
// -> DAI ->
// 1 WETH USDC
// -> WBTC ->
// (univ2) (univ2)
bytes[] memory swaps = new bytes[](4);
TokenTransfer.TransferType inTransferType = permit2
? TokenTransfer.TransferType.TRANSFER_PERMIT2
: TokenTransfer.TransferType.TRANSFER_FROM;
// WETH -> WBTC (60%)
swaps[0] = encodeSplitSwap(
uint8(0),
@@ -22,7 +31,11 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
(0xffffff * 60) / 100, // 60%
address(usv2Executor),
encodeUniswapV2Swap(
WETH_ADDR, WETH_WBTC_POOL, tychoRouterAddr, false
WETH_ADDR,
WETH_WBTC_POOL,
tychoRouterAddr,
false,
inTransferType
)
);
// WBTC -> USDC
@@ -32,7 +45,11 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
uint24(0),
address(usv2Executor),
encodeUniswapV2Swap(
WBTC_ADDR, USDC_WBTC_POOL, tychoRouterAddr, true
WBTC_ADDR,
USDC_WBTC_POOL,
tychoRouterAddr,
true,
TokenTransfer.TransferType.TRANSFER
)
);
// WETH -> DAI
@@ -42,7 +59,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
uint24(0),
address(usv2Executor),
encodeUniswapV2Swap(
WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false
WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false, inTransferType
)
);
@@ -52,7 +69,13 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
uint8(2),
uint24(0),
address(usv2Executor),
encodeUniswapV2Swap(DAI_ADDR, DAI_USDC_POOL, tychoRouterAddr, true)
encodeUniswapV2Swap(
DAI_ADDR,
DAI_USDC_POOL,
tychoRouterAddr,
true,
TokenTransfer.TransferType.TRANSFER
)
);
return swaps;
@@ -62,9 +85,12 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
// Trade 1 WETH for USDC through DAI and WBTC - see _getSplitSwaps for more info
uint256 amountIn = 1 ether;
deal(WETH_ADDR, tychoRouterAddr, amountIn);
bytes[] memory swaps = _getSplitSwaps();
deal(WETH_ADDR, ALICE, amountIn);
vm.startPrank(ALICE);
IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn);
bytes[] memory swaps = _getSplitSwaps(false);
tychoRouter.exposedSplitSwap(amountIn, 4, pleEncode(swaps));
vm.stopPrank();
uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(tychoRouterAddr);
assertEq(usdcBalance, 2615491639);
@@ -83,7 +109,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
bytes memory signature
) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
bytes[] memory swaps = _getSplitSwaps();
bytes[] memory swaps = _getSplitSwaps(true);
tychoRouter.splitSwapPermit2(
amountIn,
@@ -112,7 +138,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
vm.startPrank(ALICE);
IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn);
bytes[] memory swaps = _getSplitSwaps();
bytes[] memory swaps = _getSplitSwaps(false);
tychoRouter.splitSwap(
amountIn,
@@ -139,7 +165,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
vm.startPrank(ALICE);
IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn);
bytes[] memory swaps = _getSplitSwaps();
bytes[] memory swaps = _getSplitSwaps(false);
vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector);
tychoRouter.splitSwap(
@@ -164,7 +190,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
vm.startPrank(ALICE);
// Approve less than the amountIn
IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn - 1);
bytes[] memory swaps = _getSplitSwaps();
bytes[] memory swaps = _getSplitSwaps(false);
vm.expectRevert();
tychoRouter.splitSwap(
@@ -193,7 +219,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
bytes memory signature
) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
bytes[] memory swaps = _getSplitSwaps();
bytes[] memory swaps = _getSplitSwaps(true);
uint256 minAmountOut = 3000 * 1e18;
@@ -240,7 +266,11 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
sigDeadline: 0
});
bytes memory protocolData = encodeUniswapV2Swap(
WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false
WETH_ADDR,
WETH_DAI_POOL,
tychoRouterAddr,
false,
TokenTransfer.TransferType.TRANSFER
);
bytes memory swap = encodeSplitSwap(
@@ -284,8 +314,13 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
bytes memory signature
) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn);
bytes memory protocolData =
encodeUniswapV2Swap(DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true);
bytes memory protocolData = encodeUniswapV2Swap(
DAI_ADDR,
WETH_DAI_POOL,
tychoRouterAddr,
true,
TokenTransfer.TransferType.TRANSFER_PERMIT2
);
bytes memory swap = encodeSplitSwap(
uint8(0), uint8(1), uint24(0), address(usv2Executor), protocolData
@@ -330,7 +365,12 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI
bool zeroForOne = false;
bytes memory protocolData = encodeUniswapV3Swap(
WETH_ADDR, DAI_ADDR, tychoRouterAddr, DAI_WETH_USV3, zeroForOne
WETH_ADDR,
DAI_ADDR,
tychoRouterAddr,
DAI_WETH_USV3,
zeroForOne,
TokenTransfer.TransferType.TRANSFER_PERMIT2
);
bytes memory swap = encodeSplitSwap(
uint8(0), uint8(1), uint24(0), address(usv3Executor), protocolData
@@ -366,59 +406,6 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
tychoRouter.exposedSplitSwap(amountIn, 2, swaps);
}
function testSplitSwapAmountInNotFullySpent() public {
// Trade 1 WETH for DAI with 1 swap on Uniswap V2
// Has invalid data as input! There is only one swap with 60% of the input amount
uint256 amountIn = 1 ether;
deal(WETH_ADDR, ALICE, amountIn);
vm.startPrank(ALICE);
(
IAllowanceTransfer.PermitSingle memory permitSingle,
bytes memory signature
) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
bytes memory protocolData = encodeUniswapV2Swap(
WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false
);
bytes memory swap = encodeSplitSwap(
uint8(0),
uint8(1),
(0xffffff * 60) / 100, // 60%
address(usv2Executor),
protocolData
);
bytes[] memory swaps = new bytes[](1);
swaps[0] = swap;
vm.expectRevert(
abi.encodeWithSelector(
TychoRouter__AmountInDiffersFromConsumed.selector,
1000000000000000000,
600000000000000000
)
);
tychoRouter.splitSwapPermit2(
amountIn,
WETH_ADDR,
DAI_ADDR,
1,
false,
false,
2,
ALICE,
permitSingle,
signature,
pleEncode(swaps)
);
vm.stopPrank();
}
function testSplitSwapSingleUSV4CallbackPermit2() public {
vm.startPrank(ALICE);
uint256 amountIn = 100 ether;
@@ -436,8 +423,13 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
tickSpacing: int24(1)
});
bytes memory protocolData =
UniswapV4Utils.encodeExactInput(USDE_ADDR, USDT_ADDR, true, pools);
bytes memory protocolData = UniswapV4Utils.encodeExactInput(
USDE_ADDR,
USDT_ADDR,
true,
TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_ROUTER,
pools
);
bytes memory swap = encodeSplitSwap(
uint8(0), uint8(1), uint24(0), address(usv4Executor), protocolData
@@ -483,8 +475,13 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
tickSpacing: int24(60)
});
bytes memory protocolData =
UniswapV4Utils.encodeExactInput(USDE_ADDR, WBTC_ADDR, true, pools);
bytes memory protocolData = UniswapV4Utils.encodeExactInput(
USDE_ADDR,
WBTC_ADDR,
true,
TokenTransfer.TransferType.NONE,
pools
);
bytes memory swap = encodeSplitSwap(
uint8(0), uint8(1), uint24(0), address(usv4Executor), protocolData
@@ -507,18 +504,35 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
// │ │
// └─ (USV3, 40% split) ──> WETH ─┘
uint256 amountIn = 100 * 10 ** 6;
deal(USDC_ADDR, tychoRouterAddr, amountIn);
deal(USDC_ADDR, ALICE, amountIn);
vm.startPrank(ALICE);
// Approve the TychoRouter to spend USDC
IERC20(USDC_ADDR).approve(tychoRouterAddr, amountIn);
bytes memory usdcWethV3Pool1ZeroOneData = encodeUniswapV3Swap(
USDC_ADDR, WETH_ADDR, tychoRouterAddr, USDC_WETH_USV3, true
USDC_ADDR,
WETH_ADDR,
tychoRouterAddr,
USDC_WETH_USV3,
true,
TokenTransfer.TransferType.TRANSFER_FROM
);
bytes memory usdcWethV3Pool2ZeroOneData = encodeUniswapV3Swap(
USDC_ADDR, WETH_ADDR, tychoRouterAddr, USDC_WETH_USV3_2, true
USDC_ADDR,
WETH_ADDR,
tychoRouterAddr,
USDC_WETH_USV3_2,
true,
TokenTransfer.TransferType.TRANSFER_FROM
);
bytes memory wethUsdcV2OneZeroData = encodeUniswapV2Swap(
WETH_ADDR, USDC_WETH_USV2, tychoRouterAddr, false
WETH_ADDR,
USDC_WETH_USV2,
tychoRouterAddr,
false,
TokenTransfer.TransferType.TRANSFER
);
bytes[] memory swaps = new bytes[](3);
@@ -547,6 +561,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
wethUsdcV2OneZeroData
);
tychoRouter.exposedSplitSwap(amountIn, 2, pleEncode(swaps));
vm.stopPrank();
assertEq(IERC20(USDC_ADDR).balanceOf(tychoRouterAddr), 99574171);
}
@@ -563,15 +578,29 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
deal(USDC_ADDR, tychoRouterAddr, amountIn);
bytes memory usdcWethV2Data = encodeUniswapV2Swap(
USDC_ADDR, USDC_WETH_USV2, tychoRouterAddr, true
USDC_ADDR,
USDC_WETH_USV2,
tychoRouterAddr,
true,
TokenTransfer.TransferType.TRANSFER
);
bytes memory usdcWethV3Pool1OneZeroData = encodeUniswapV3Swap(
WETH_ADDR, USDC_ADDR, tychoRouterAddr, USDC_WETH_USV3, false
WETH_ADDR,
USDC_ADDR,
tychoRouterAddr,
USDC_WETH_USV3,
false,
TokenTransfer.TransferType.TRANSFER
);
bytes memory usdcWethV3Pool2OneZeroData = encodeUniswapV3Swap(
WETH_ADDR, USDC_ADDR, tychoRouterAddr, USDC_WETH_USV3_2, false
WETH_ADDR,
USDC_ADDR,
tychoRouterAddr,
USDC_WETH_USV3_2,
false,
TokenTransfer.TransferType.TRANSFER
);
bytes[] memory swaps = new bytes[](3);
@@ -610,7 +639,11 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
deal(BASE_USDC, tychoRouterAddr, amountIn);
bytes memory protocolData = encodeUniswapV2Swap(
BASE_USDC, USDC_MAG7_POOL, tychoRouterAddr, true
BASE_USDC,
USDC_MAG7_POOL,
tychoRouterAddr,
true,
TokenTransfer.TransferType.TRANSFER_FROM
);
bytes memory swap = encodeSplitSwap(