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

@@ -176,7 +176,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
WETH_DAI_POOL,
BOB,
zeroForOne,
uint8(TokenTransfer.TransferType.TRANSFERFROM)
uint8(TokenTransfer.TransferType.TRANSFER_FROM)
);
deal(WETH_ADDR, address(this), amountIn);
@@ -197,7 +197,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
WETH_DAI_POOL,
ALICE,
zeroForOne,
uint8(TokenTransfer.TransferType.TRANSFERPERMIT2)
uint8(TokenTransfer.TransferType.TRANSFER_PERMIT2)
);
deal(WETH_ADDR, ALICE, amountIn);

View File

@@ -7,9 +7,12 @@ import "@src/executors/UniswapV4Executor.sol";
import {Constants} from "../Constants.sol";
import {Test} from "../../lib/forge-std/src/Test.sol";
import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol";
import "@src/executors/TokenTransfer.sol";
contract UniswapV4ExecutorExposed is UniswapV4Executor {
constructor(IPoolManager _poolManager) UniswapV4Executor(_poolManager) {}
constructor(IPoolManager _poolManager, address _permit2)
UniswapV4Executor(_poolManager, _permit2)
{}
function decodeData(bytes calldata data)
external
@@ -18,6 +21,7 @@ contract UniswapV4ExecutorExposed is UniswapV4Executor {
address tokenIn,
address tokenOut,
bool zeroForOne,
TokenTransfer.TransferType transferType,
UniswapV4Pool[] memory pools
)
{
@@ -36,8 +40,9 @@ contract UniswapV4ExecutorTest is Test, Constants {
function setUp() public {
uint256 forkBlock = 21817316;
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
uniswapV4Exposed =
new UniswapV4ExecutorExposed(IPoolManager(poolManager));
uniswapV4Exposed = new UniswapV4ExecutorExposed(
IPoolManager(poolManager), PERMIT2_ADDRESS
);
}
function testDecodeParams() public view {
@@ -46,6 +51,8 @@ contract UniswapV4ExecutorTest is Test, Constants {
int24 tickSpacing1 = 60;
uint24 pool2Fee = 1000;
int24 tickSpacing2 = -10;
TokenTransfer.TransferType transferType =
TokenTransfer.TransferType.TRANSFER_FROM;
UniswapV4Executor.UniswapV4Pool[] memory pools =
new UniswapV4Executor.UniswapV4Pool[](2);
@@ -61,19 +68,21 @@ contract UniswapV4ExecutorTest is Test, Constants {
});
bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR, USDT_ADDR, zeroForOne, pools
USDE_ADDR, USDT_ADDR, zeroForOne, transferType, pools
);
(
address tokenIn,
address tokenOut,
bool zeroForOneDecoded,
TokenTransfer.TransferType transferTypeDecoded,
UniswapV4Executor.UniswapV4Pool[] memory decodedPools
) = uniswapV4Exposed.decodeData(data);
assertEq(tokenIn, USDE_ADDR);
assertEq(tokenOut, USDT_ADDR);
assertEq(zeroForOneDecoded, zeroForOne);
assertEq(uint8(transferTypeDecoded), uint8(transferType));
assertEq(decodedPools.length, 2);
assertEq(decodedPools[0].intermediaryToken, USDT_ADDR);
assertEq(decodedPools[0].fee, pool1Fee);
@@ -98,8 +107,13 @@ contract UniswapV4ExecutorTest is Test, Constants {
tickSpacing: int24(1)
});
bytes memory data =
UniswapV4Utils.encodeExactInput(USDE_ADDR, USDT_ADDR, true, pools);
bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR,
USDT_ADDR,
true,
TokenTransfer.TransferType.NONE,
pools
);
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn);
@@ -114,7 +128,7 @@ contract UniswapV4ExecutorTest is Test, Constants {
// USDE -> USDT
// Generated by the Tycho swap encoder - test_encode_uniswap_v4_simple_swap
bytes memory protocolData =
hex"4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec701dac17f958d2ee523a2206206994597c13d831ec7000064000001";
hex"4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec701f62849f9a0b5bf2913b396098f7c7019b51a820a00dac17f958d2ee523a2206206994597c13d831ec7000064000001";
uint256 amountIn = 100 ether;
deal(USDE_ADDR, address(uniswapV4Exposed), amountIn);
uint256 usdeBalanceBeforePool = USDE.balanceOf(poolManager);
@@ -151,8 +165,13 @@ contract UniswapV4ExecutorTest is Test, Constants {
tickSpacing: int24(60)
});
bytes memory data =
UniswapV4Utils.encodeExactInput(USDE_ADDR, WBTC_ADDR, true, pools);
bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR,
WBTC_ADDR,
true,
TokenTransfer.TransferType.NONE,
pools
);
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn);
@@ -170,7 +189,7 @@ contract UniswapV4ExecutorTest is Test, Constants {
// Generated by the Tycho swap encoder - test_encode_uniswap_v4_sequential_swap
bytes memory protocolData =
hex"4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c59901dac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c";
hex"4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c59901f62849f9a0b5bf2913b396098f7c7019b51a820a00dac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c";
uint256 amountIn = 100 ether;
deal(USDE_ADDR, address(uniswapV4Exposed), amountIn);

View File

@@ -8,6 +8,7 @@ library UniswapV4Utils {
address tokenIn,
address tokenOut,
bool zeroForOne,
UniswapV4Executor.TransferType transferType,
UniswapV4Executor.UniswapV4Pool[] memory pools
) public pure returns (bytes memory) {
bytes memory encodedPools;
@@ -21,6 +22,12 @@ library UniswapV4Utils {
);
}
return abi.encodePacked(tokenIn, tokenOut, zeroForOne, encodedPools);
return abi.encodePacked(
tokenIn,
tokenOut,
zeroForOne,
transferType,
encodedPools
);
}
}