feat: Support new transfer logic in all executors

TODO:
- Fix failing tests
- Remove permit2 from initialization of contracts
This commit is contained in:
TAMARA LIPOWSKI
2025-05-14 20:42:19 -04:00
parent 0f9af65846
commit 27dfde3118
22 changed files with 378 additions and 462 deletions

View File

@@ -17,7 +17,7 @@ contract BalancerV2ExecutorExposed is BalancerV2Executor {
bytes32 poolId,
address receiver,
bool needsApproval,
TransferType transferType
bool transferNeeded
)
{
return _decodeData(data);
@@ -41,12 +41,7 @@ contract BalancerV2ExecutorTest is Constants, TestUtils {
function testDecodeParams() public view {
bytes memory params = abi.encodePacked(
WETH_ADDR,
BAL_ADDR,
WETH_BAL_POOL_ID,
address(2),
true,
TokenTransfer.TransferType.NONE
WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, address(2), true, false
);
(
@@ -55,7 +50,7 @@ contract BalancerV2ExecutorTest is Constants, TestUtils {
bytes32 poolId,
address receiver,
bool needsApproval,
TokenTransfer.TransferType transferType
bool transferNeeded
) = balancerV2Exposed.decodeParams(params);
assertEq(address(tokenIn), WETH_ADDR);
@@ -63,7 +58,6 @@ contract BalancerV2ExecutorTest is Constants, TestUtils {
assertEq(poolId, WETH_BAL_POOL_ID);
assertEq(receiver, address(2));
assertEq(needsApproval, true);
assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE));
}
function testDecodeParamsInvalidDataLength() public {
@@ -77,12 +71,7 @@ contract BalancerV2ExecutorTest is Constants, TestUtils {
function testSwap() public {
uint256 amountIn = 10 ** 18;
bytes memory protocolData = abi.encodePacked(
WETH_ADDR,
BAL_ADDR,
WETH_BAL_POOL_ID,
BOB,
true,
TokenTransfer.TransferType.NONE
WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, BOB, true, false
);
deal(WETH_ADDR, address(balancerV2Exposed), amountIn);
@@ -104,7 +93,7 @@ contract BalancerV2ExecutorTest is Constants, TestUtils {
bytes32 poolId,
address receiver,
bool needsApproval,
TokenTransfer.TransferType transferType
bool transferNeeded
) = balancerV2Exposed.decodeParams(protocolData);
assertEq(address(tokenIn), WETH_ADDR);
@@ -112,7 +101,6 @@ contract BalancerV2ExecutorTest is Constants, TestUtils {
assertEq(poolId, WETH_BAL_POOL_ID);
assertEq(receiver, BOB);
assertEq(needsApproval, true);
assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE));
}
function testSwapIntegration() public {

View File

@@ -37,7 +37,7 @@ contract CurveExecutorExposed is CurveExecutor {
int128 i,
int128 j,
bool tokenApprovalNeeded,
TokenTransfer.TransferType transferType,
bool transferNeeded,
address receiver
)
{
@@ -68,7 +68,7 @@ contract CurveExecutorTest is Test, Constants {
uint8(2),
uint8(0),
true,
TokenTransfer.TransferType.NONE,
false,
ALICE
);
@@ -80,7 +80,7 @@ contract CurveExecutorTest is Test, Constants {
int128 i,
int128 j,
bool tokenApprovalNeeded,
TokenTransfer.TransferType transferType,
bool transferNeeded,
address receiver
) = curveExecutorExposed.decodeData(data);
@@ -91,7 +91,6 @@ contract CurveExecutorTest is Test, Constants {
assertEq(i, 2);
assertEq(j, 0);
assertEq(tokenApprovalNeeded, true);
assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE));
assertEq(receiver, ALICE);
}
@@ -295,7 +294,7 @@ contract CurveExecutorTest is Test, Constants {
uint8(uint256(uint128(i))),
uint8(uint256(uint128(j))),
true,
TokenTransfer.TransferType.NONE,
false,
receiver
);
}

View File

@@ -3,7 +3,7 @@ pragma solidity ^0.8.26;
import "../TestUtils.sol";
import {Constants} from "../Constants.sol";
import {EkuboExecutor, TokenTransfer} from "@src/executors/EkuboExecutor.sol";
import {EkuboExecutor} from "@src/executors/EkuboExecutor.sol";
import {ICore} from "@ekubo/interfaces/ICore.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {NATIVE_TOKEN_ADDRESS} from "@ekubo/math/constants.sol";
@@ -45,7 +45,7 @@ contract EkuboExecutorTest is Constants, TestUtils {
uint256 usdcBalanceBeforeExecutor = USDC.balanceOf(address(executor));
bytes memory data = abi.encodePacked(
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType (transfer from executor to core)
true, // transferNeeded (transfer from executor to core)
address(executor), // receiver
NATIVE_TOKEN_ADDRESS, // tokenIn
USDC_ADDR, // tokenOut
@@ -82,7 +82,7 @@ contract EkuboExecutorTest is Constants, TestUtils {
uint256 ethBalanceBeforeExecutor = address(executor).balance;
bytes memory data = abi.encodePacked(
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType (transfer from executor to core)
true, // transferNeeded (transfer from executor to core)
address(executor), // receiver
USDC_ADDR, // tokenIn
NATIVE_TOKEN_ADDRESS, // tokenOut
@@ -140,7 +140,7 @@ contract EkuboExecutorTest is Constants, TestUtils {
// Same test case as in swap_encoder::tests::ekubo::test_encode_swap_multi
function testMultiHopSwap() public {
bytes memory data = abi.encodePacked(
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType
true, // transferNeeded (transfer from executor to core)
address(executor), // receiver
NATIVE_TOKEN_ADDRESS, // tokenIn
USDC_ADDR, // tokenOut of 1st swap

View File

@@ -17,7 +17,7 @@ contract MaverickV2ExecutorExposed is MaverickV2Executor {
IERC20 tokenIn,
address target,
address receiver,
TransferType transferType
bool transferNeeded
)
{
return _decodeData(data);
@@ -39,27 +39,16 @@ contract MaverickV2ExecutorTest is TestUtils, Constants {
}
function testDecodeParams() public view {
bytes memory params = abi.encodePacked(
GHO_ADDR,
GHO_USDC_POOL,
address(2),
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
);
bytes memory params =
abi.encodePacked(GHO_ADDR, GHO_USDC_POOL, address(2), true);
(
IERC20 tokenIn,
address target,
address receiver,
TokenTransfer.TransferType transferType
) = maverickV2Exposed.decodeParams(params);
(IERC20 tokenIn, address target, address receiver, bool transferNeeded)
= maverickV2Exposed.decodeParams(params);
assertEq(address(tokenIn), GHO_ADDR);
assertEq(target, GHO_USDC_POOL);
assertEq(receiver, address(2));
assertEq(
uint8(transferType),
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
);
assertEq(transferNeeded, true);
}
function testDecodeParamsInvalidDataLength() public {
@@ -72,12 +61,8 @@ contract MaverickV2ExecutorTest is TestUtils, Constants {
function testSwap() public {
uint256 amountIn = 10e18;
bytes memory protocolData = abi.encodePacked(
GHO_ADDR,
GHO_USDC_POOL,
BOB,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
);
bytes memory protocolData =
abi.encodePacked(GHO_ADDR, GHO_USDC_POOL, BOB, true);
deal(GHO_ADDR, address(maverickV2Exposed), amountIn);
uint256 balanceBefore = USDC.balanceOf(BOB);
@@ -94,20 +79,13 @@ contract MaverickV2ExecutorTest is TestUtils, Constants {
bytes memory protocolData =
loadCallDataFromFile("test_encode_maverick_v2");
(
IERC20 tokenIn,
address pool,
address receiver,
TokenTransfer.TransferType transferType
) = maverickV2Exposed.decodeParams(protocolData);
(IERC20 tokenIn, address pool, address receiver, bool transferNeeded) =
maverickV2Exposed.decodeParams(protocolData);
assertEq(address(tokenIn), GHO_ADDR);
assertEq(pool, GHO_USDC_POOL);
assertEq(receiver, BOB);
assertEq(
uint8(transferType),
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
);
assertEq(transferNeeded, true);
}
function testSwapIntegration() public {

View File

@@ -2,7 +2,6 @@
pragma solidity ^0.8.26;
import "@src/executors/UniswapV2Executor.sol";
import "@src/executors/TokenTransfer.sol";
import {Test} from "../../lib/forge-std/src/Test.sol";
import {Constants} from "../Constants.sol";
import {Permit2TestHelper} from "../Permit2TestHelper.sol";
@@ -23,7 +22,7 @@ contract UniswapV2ExecutorExposed is UniswapV2Executor {
address target,
address receiver,
bool zeroForOne,
TransferType transferType
bool transferNeeded
)
{
return _decodeData(data);
@@ -60,7 +59,6 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
UniswapV2ExecutorExposed pancakeswapV2Exposed;
IERC20 WETH = IERC20(WETH_ADDR);
IERC20 DAI = IERC20(DAI_ADDR);
IAllowanceTransfer permit2;
function setUp() public {
uint256 forkBlock = 17323404;
@@ -80,34 +78,25 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
PERMIT2_ADDRESS,
25
);
permit2 = IAllowanceTransfer(PERMIT2_ADDRESS);
}
function testDecodeParams() public view {
bytes memory params = abi.encodePacked(
WETH_ADDR,
address(2),
address(3),
false,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
);
bytes memory params =
abi.encodePacked(WETH_ADDR, address(2), address(3), false, true);
(
IERC20 tokenIn,
address target,
address receiver,
bool zeroForOne,
TokenTransfer.TransferType transferType
bool transferNeeded
) = uniswapV2Exposed.decodeParams(params);
assertEq(address(tokenIn), WETH_ADDR);
assertEq(target, address(2));
assertEq(receiver, address(3));
assertEq(zeroForOne, false);
assertEq(
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL),
uint8(transferType)
);
assertEq(transferNeeded, true);
}
function testDecodeParamsInvalidDataLength() public {
@@ -158,13 +147,8 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
uint256 amountIn = 10 ** 18;
uint256 amountOut = 1847751195973566072891;
bool zeroForOne = false;
bytes memory protocolData = abi.encodePacked(
WETH_ADDR,
WETH_DAI_POOL,
BOB,
zeroForOne,
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
);
bytes memory protocolData =
abi.encodePacked(WETH_ADDR, WETH_DAI_POOL, BOB, zeroForOne, true);
deal(WETH_ADDR, address(uniswapV2Exposed), amountIn);
uniswapV2Exposed.swap(amountIn, protocolData);
@@ -173,70 +157,12 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
assertGe(finalBalance, amountOut);
}
function testSwapWithTransferFrom() public {
uint256 amountIn = 10 ** 18;
uint256 amountOut = 1847751195973566072891;
bool zeroForOne = false;
bytes memory protocolData = abi.encodePacked(
WETH_ADDR,
WETH_DAI_POOL,
BOB,
zeroForOne,
uint8(TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL)
);
deal(WETH_ADDR, address(this), amountIn);
IERC20(WETH_ADDR).approve(address(uniswapV2Exposed), amountIn);
uniswapV2Exposed.swap(amountIn, protocolData);
uint256 finalBalance = DAI.balanceOf(BOB);
assertGe(finalBalance, amountOut);
}
function testSwapWithPermit2TransferFrom() public {
uint256 amountIn = 10 ** 18;
uint256 amountOut = 1847751195973566072891;
bool zeroForOne = false;
bytes memory protocolData = abi.encodePacked(
WETH_ADDR,
WETH_DAI_POOL,
ALICE,
zeroForOne,
uint8(TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL)
);
deal(WETH_ADDR, ALICE, amountIn);
vm.startPrank(ALICE);
(
IAllowanceTransfer.PermitSingle memory permitSingle,
bytes memory signature
) = handlePermit2Approval(
WETH_ADDR, address(uniswapV2Exposed), amountIn
);
// Assume the permit2.approve method will be called from the TychoRouter
// Replicate this scenario in this test.
permit2.permit(ALICE, permitSingle, signature);
uniswapV2Exposed.swap(amountIn, protocolData);
vm.stopPrank();
uint256 finalBalance = DAI.balanceOf(ALICE);
assertGe(finalBalance, amountOut);
}
function testSwapNoTransfer() public {
uint256 amountIn = 10 ** 18;
uint256 amountOut = 1847751195973566072891;
bool zeroForOne = false;
bytes memory protocolData = abi.encodePacked(
WETH_ADDR,
WETH_DAI_POOL,
BOB,
zeroForOne,
uint8(TokenTransfer.TransferType.NONE)
);
bytes memory protocolData =
abi.encodePacked(WETH_ADDR, WETH_DAI_POOL, BOB, zeroForOne, false);
deal(WETH_ADDR, address(this), amountIn);
IERC20(WETH_ADDR).transfer(address(WETH_DAI_POOL), amountIn);
@@ -255,20 +181,19 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
address target,
address receiver,
bool zeroForOne,
TokenTransfer.TransferType transferType
bool transferNeeded
) = uniswapV2Exposed.decodeParams(protocolData);
assertEq(address(tokenIn), WETH_ADDR);
assertEq(target, 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640);
assertEq(receiver, 0x0000000000000000000000000000000000000001);
assertEq(zeroForOne, false);
// TRANSFER = 0
assertEq(0, uint8(transferType));
assertEq(transferNeeded, false);
}
function testSwapIntegration() public {
bytes memory protocolData =
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0000";
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0001";
uint256 amountIn = 10 ** 18;
uint256 amountOut = 1847751195973566072891;
deal(WETH_ADDR, address(uniswapV2Exposed), amountIn);
@@ -282,13 +207,8 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
uint256 amountIn = 10 ** 18;
bool zeroForOne = false;
address fakePool = address(new FakeUniswapV2Pool(WETH_ADDR, DAI_ADDR));
bytes memory protocolData = abi.encodePacked(
WETH_ADDR,
fakePool,
BOB,
zeroForOne,
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
);
bytes memory protocolData =
abi.encodePacked(WETH_ADDR, fakePool, BOB, zeroForOne, true);
deal(WETH_ADDR, address(uniswapV2Exposed), amountIn);
vm.expectRevert(UniswapV2Executor__InvalidTarget.selector);
@@ -302,13 +222,8 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
vm.rollFork(26857267);
uint256 amountIn = 10 * 10 ** 6;
bool zeroForOne = true;
bytes memory protocolData = abi.encodePacked(
BASE_USDC,
USDC_MAG7_POOL,
BOB,
zeroForOne,
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
);
bytes memory protocolData =
abi.encodePacked(BASE_USDC, USDC_MAG7_POOL, BOB, zeroForOne, true);
deal(BASE_USDC, address(uniswapV2Exposed), amountIn);

View File

@@ -22,7 +22,8 @@ contract UniswapV3ExecutorExposed is UniswapV3Executor {
address receiver,
address target,
bool zeroForOne,
TransferType transferType
bool transferFromNeeded,
bool transferNeeded
)
{
return _decodeData(data);
@@ -71,7 +72,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
address(2),
address(3),
false,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
false,
true
);
(
@@ -81,7 +83,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
address receiver,
address target,
bool zeroForOne,
TokenTransfer.TransferType transferType
bool transferFromNeeded,
bool transferNeeded
) = uniswapV3Exposed.decodeData(data);
assertEq(tokenIn, WETH_ADDR);
@@ -90,10 +93,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
assertEq(receiver, address(2));
assertEq(target, address(3));
assertEq(zeroForOne, false);
assertEq(
uint8(transferType),
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
);
assertEq(transferFromNeeded, false);
assertEq(transferNeeded, true);
}
function testSwapIntegration() public {
@@ -109,7 +110,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
address(this),
DAI_WETH_USV3,
zeroForOne,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
false,
true
);
uint256 amountOut = uniswapV3Exposed.swap(amountIn, data);
@@ -184,7 +186,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
address(this),
fakePool,
zeroForOne,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
false,
true
);
vm.expectRevert(UniswapV3Executor__InvalidTarget.selector);
@@ -197,7 +200,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
address receiver,
address target,
bool zero2one,
TokenTransfer.TransferType transferType
bool transferFromNeeded,
bool transferNeeded
) internal view returns (bytes memory) {
IUniswapV3Pool pool = IUniswapV3Pool(target);
return abi.encodePacked(
@@ -207,7 +211,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
receiver,
target,
zero2one,
transferType
transferFromNeeded,
transferNeeded
);
}
}

View File

@@ -4,7 +4,6 @@ pragma solidity ^0.8.26;
import "../../src/executors/UniswapV4Executor.sol";
import "../TestUtils.sol";
import "./UniswapV4Utils.sol";
import "@src/executors/TokenTransfer.sol";
import "@src/executors/UniswapV4Executor.sol";
import {Constants} from "../Constants.sol";
import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol";
@@ -22,7 +21,8 @@ contract UniswapV4ExecutorExposed is UniswapV4Executor {
address tokenIn,
address tokenOut,
bool zeroForOne,
TokenTransfer.TransferType transferType,
bool transferFromNeeded,
bool transferNeeded,
address receiver,
UniswapV4Pool[] memory pools
)
@@ -53,8 +53,8 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
int24 tickSpacing1 = 60;
uint24 pool2Fee = 1000;
int24 tickSpacing2 = -10;
TokenTransfer.TransferType transferType =
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL;
bool transferFromNeeded = false;
bool transferNeeded = true;
UniswapV4Executor.UniswapV4Pool[] memory pools =
new UniswapV4Executor.UniswapV4Pool[](2);
@@ -70,14 +70,21 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
});
bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR, USDT_ADDR, zeroForOne, transferType, ALICE, pools
USDE_ADDR,
USDT_ADDR,
zeroForOne,
transferFromNeeded,
transferNeeded,
ALICE,
pools
);
(
address tokenIn,
address tokenOut,
bool zeroForOneDecoded,
TokenTransfer.TransferType transferTypeDecoded,
bool transferFromNeededDecoded,
bool transferNeededDecoded,
address receiver,
UniswapV4Executor.UniswapV4Pool[] memory decodedPools
) = uniswapV4Exposed.decodeData(data);
@@ -85,7 +92,8 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
assertEq(tokenIn, USDE_ADDR);
assertEq(tokenOut, USDT_ADDR);
assertEq(zeroForOneDecoded, zeroForOne);
assertEq(uint8(transferTypeDecoded), uint8(transferType));
assertEq(transferFromNeededDecoded, transferFromNeeded);
assertEq(transferNeededDecoded, transferNeeded);
assertEq(receiver, ALICE);
assertEq(decodedPools.length, 2);
assertEq(decodedPools[0].intermediaryToken, USDT_ADDR);
@@ -112,12 +120,7 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
});
bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR,
USDT_ADDR,
true,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL,
ALICE,
pools
USDE_ADDR, USDT_ADDR, true, false, true, ALICE, pools
);
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
@@ -169,12 +172,7 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
});
bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR,
WBTC_ADDR,
true,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL,
ALICE,
pools
USDE_ADDR, WBTC_ADDR, true, false, true, ALICE, pools
);
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);

View File

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