Merge pull request #162 from propeller-heads/router/tnl/ENG-4419-ekubo-token-transfer
feat: Use TokenTransfer optimization helper in Ekubo
This commit is contained in:
@@ -66,7 +66,7 @@ contract Dispatcher {
|
|||||||
tstore(0, executor)
|
tstore(0, executor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// slither-disable-next-line controlled-delegatecall,low-level-calls
|
// slither-disable-next-line controlled-delegatecall,low-level-calls,calls-loop
|
||||||
(bool success, bytes memory result) = executor.delegatecall(
|
(bool success, bytes memory result) = executor.delegatecall(
|
||||||
abi.encodeWithSelector(IExecutor.swap.selector, amount, data)
|
abi.encodeWithSelector(IExecutor.swap.selector, amount, data)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,21 +11,28 @@ import {SafeTransferLib} from "@solady/utils/SafeTransferLib.sol";
|
|||||||
import {LibBytes} from "@solady/utils/LibBytes.sol";
|
import {LibBytes} from "@solady/utils/LibBytes.sol";
|
||||||
import {Config, EkuboPoolKey} from "@ekubo/types/poolKey.sol";
|
import {Config, EkuboPoolKey} from "@ekubo/types/poolKey.sol";
|
||||||
import {MAX_SQRT_RATIO, MIN_SQRT_RATIO} from "@ekubo/types/sqrtRatio.sol";
|
import {MAX_SQRT_RATIO, MIN_SQRT_RATIO} from "@ekubo/types/sqrtRatio.sol";
|
||||||
|
import {TokenTransfer} from "./TokenTransfer.sol";
|
||||||
|
|
||||||
contract EkuboExecutor is IExecutor, ILocker, IPayer, ICallback {
|
contract EkuboExecutor is
|
||||||
|
IExecutor,
|
||||||
|
ILocker,
|
||||||
|
IPayer,
|
||||||
|
ICallback,
|
||||||
|
TokenTransfer
|
||||||
|
{
|
||||||
error EkuboExecutor__InvalidDataLength();
|
error EkuboExecutor__InvalidDataLength();
|
||||||
error EkuboExecutor__CoreOnly();
|
error EkuboExecutor__CoreOnly();
|
||||||
error EkuboExecutor__UnknownCallback();
|
error EkuboExecutor__UnknownCallback();
|
||||||
|
|
||||||
ICore immutable core;
|
ICore immutable core;
|
||||||
|
|
||||||
uint256 constant POOL_DATA_OFFSET = 56;
|
uint256 constant POOL_DATA_OFFSET = 77;
|
||||||
uint256 constant HOP_BYTE_LEN = 52;
|
uint256 constant HOP_BYTE_LEN = 52;
|
||||||
|
|
||||||
bytes4 constant LOCKED_SELECTOR = 0xb45a3c0e; // locked(uint256)
|
bytes4 constant LOCKED_SELECTOR = 0xb45a3c0e; // locked(uint256)
|
||||||
bytes4 constant PAY_CALLBACK_SELECTOR = 0x599d0714; // payCallback(uint256,address)
|
bytes4 constant PAY_CALLBACK_SELECTOR = 0x599d0714; // payCallback(uint256,address)
|
||||||
|
|
||||||
constructor(address _core) {
|
constructor(address _core, address _permit2) TokenTransfer(_permit2) {
|
||||||
core = ICore(_core);
|
core = ICore(_core);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,11 +41,16 @@ contract EkuboExecutor is IExecutor, ILocker, IPayer, ICallback {
|
|||||||
payable
|
payable
|
||||||
returns (uint256 calculatedAmount)
|
returns (uint256 calculatedAmount)
|
||||||
{
|
{
|
||||||
if (data.length < 92) revert EkuboExecutor__InvalidDataLength();
|
if (data.length < 93) revert EkuboExecutor__InvalidDataLength();
|
||||||
|
|
||||||
// amountIn must be at most type(int128).MAX
|
// amountIn must be at most type(int128).MAX
|
||||||
calculatedAmount =
|
calculatedAmount = uint256(
|
||||||
uint256(_lock(bytes.concat(bytes16(uint128(amountIn)), data)));
|
_lock(
|
||||||
|
bytes.concat(
|
||||||
|
bytes16(uint128(amountIn)), bytes20(msg.sender), data
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCallback(bytes calldata raw)
|
function handleCallback(bytes calldata raw)
|
||||||
@@ -113,9 +125,11 @@ contract EkuboExecutor is IExecutor, ILocker, IPayer, ICallback {
|
|||||||
function _locked(bytes calldata swapData) internal returns (int128) {
|
function _locked(bytes calldata swapData) internal returns (int128) {
|
||||||
int128 nextAmountIn = int128(uint128(bytes16(swapData[0:16])));
|
int128 nextAmountIn = int128(uint128(bytes16(swapData[0:16])));
|
||||||
uint128 tokenInDebtAmount = uint128(nextAmountIn);
|
uint128 tokenInDebtAmount = uint128(nextAmountIn);
|
||||||
|
address sender = address(bytes20(swapData[16:36]));
|
||||||
|
uint8 transferType = uint8(swapData[36]);
|
||||||
|
|
||||||
address receiver = address(bytes20(swapData[16:36]));
|
address receiver = address(bytes20(swapData[37:57]));
|
||||||
address tokenIn = address(bytes20(swapData[36:POOL_DATA_OFFSET]));
|
address tokenIn = address(bytes20(swapData[57:77]));
|
||||||
|
|
||||||
address nextTokenIn = tokenIn;
|
address nextTokenIn = tokenIn;
|
||||||
|
|
||||||
@@ -149,14 +163,17 @@ contract EkuboExecutor is IExecutor, ILocker, IPayer, ICallback {
|
|||||||
offset += HOP_BYTE_LEN;
|
offset += HOP_BYTE_LEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
_pay(tokenIn, tokenInDebtAmount);
|
_pay(tokenIn, tokenInDebtAmount, sender, transferType);
|
||||||
|
|
||||||
core.withdraw(nextTokenIn, receiver, uint128(nextAmountIn));
|
core.withdraw(nextTokenIn, receiver, uint128(nextAmountIn));
|
||||||
|
|
||||||
return nextAmountIn;
|
return nextAmountIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _pay(address token, uint128 amount) internal {
|
function _pay(
|
||||||
|
address token,
|
||||||
|
uint128 amount,
|
||||||
|
address sender,
|
||||||
|
uint8 transferType
|
||||||
|
) internal {
|
||||||
address target = address(core);
|
address target = address(core);
|
||||||
|
|
||||||
if (token == NATIVE_TOKEN_ADDRESS) {
|
if (token == NATIVE_TOKEN_ADDRESS) {
|
||||||
@@ -169,9 +186,11 @@ contract EkuboExecutor is IExecutor, ILocker, IPayer, ICallback {
|
|||||||
mstore(free, shl(224, 0x0c11dedd))
|
mstore(free, shl(224, 0x0c11dedd))
|
||||||
mstore(add(free, 4), token)
|
mstore(add(free, 4), token)
|
||||||
mstore(add(free, 36), shl(128, amount))
|
mstore(add(free, 36), shl(128, amount))
|
||||||
|
mstore(add(free, 52), shl(96, sender))
|
||||||
|
mstore(add(free, 72), shl(248, transferType))
|
||||||
|
|
||||||
// if it failed, pass through revert
|
// 4 (selector) + 32 (token) + 16 (amount) + 20 (sender) + 1 (transferType) = 73
|
||||||
if iszero(call(gas(), target, 0, free, 52, 0, 0)) {
|
if iszero(call(gas(), target, 0, free, 73, 0, 0)) {
|
||||||
returndatacopy(0, 0, returndatasize())
|
returndatacopy(0, 0, returndatasize())
|
||||||
revert(0, returndatasize())
|
revert(0, returndatasize())
|
||||||
}
|
}
|
||||||
@@ -182,8 +201,9 @@ contract EkuboExecutor is IExecutor, ILocker, IPayer, ICallback {
|
|||||||
function _payCallback(bytes calldata payData) internal {
|
function _payCallback(bytes calldata payData) internal {
|
||||||
address token = address(bytes20(payData[12:32])); // This arg is abi-encoded
|
address token = address(bytes20(payData[12:32])); // This arg is abi-encoded
|
||||||
uint128 amount = uint128(bytes16(payData[32:48]));
|
uint128 amount = uint128(bytes16(payData[32:48]));
|
||||||
|
address sender = address(bytes20(payData[48:68]));
|
||||||
SafeTransferLib.safeTransfer(token, address(core), amount);
|
TransferType transferType = TransferType(uint8(payData[68]));
|
||||||
|
_transfer(token, sender, address(core), amount, transferType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// To receive withdrawals from Core
|
// To receive withdrawals from Core
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup {
|
|||||||
vm.startPrank(ALICE);
|
vm.startPrank(ALICE);
|
||||||
// Encoded solution generated using `test_split_encoding_strategy_ekubo`
|
// Encoded solution generated using `test_split_encoding_strategy_ekubo`
|
||||||
(bool success,) = address(tychoRouter).call{value: 1 ether}(
|
(bool success,) = address(tychoRouter).call{value: 1 ether}(
|
||||||
hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000077007500010000003d7ebc40af7092e3f1c81f2e996cba5cae2090d7a4ad4f68d0b91cfd19687c881e50f3a00242828c0000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c000000000000000000000000000000000000000000"
|
hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000078007600010000003d7ebc40af7092e3f1c81f2e996cba5cae2090d705a4ad4f68d0b91cfd19687c881e50f3a00242828c0000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c0000000000000000000000000000000000000000"
|
||||||
);
|
);
|
||||||
|
|
||||||
uint256 balancerAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
|
uint256 balancerAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper {
|
|||||||
factoryPancakeV3, initCodePancakeV3, PERMIT2_ADDRESS
|
factoryPancakeV3, initCodePancakeV3, PERMIT2_ADDRESS
|
||||||
);
|
);
|
||||||
balancerv2Executor = new BalancerV2Executor(PERMIT2_ADDRESS);
|
balancerv2Executor = new BalancerV2Executor(PERMIT2_ADDRESS);
|
||||||
ekuboExecutor = new EkuboExecutor(ekuboCore);
|
ekuboExecutor = new EkuboExecutor(ekuboCore, PERMIT2_ADDRESS);
|
||||||
curveExecutor = new CurveExecutor(ETH_ADDR_FOR_CURVE, PERMIT2_ADDRESS);
|
curveExecutor = new CurveExecutor(ETH_ADDR_FOR_CURVE, PERMIT2_ADDRESS);
|
||||||
|
|
||||||
address[] memory executors = new address[](7);
|
address[] memory executors = new address[](7);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// SPDX-License-Identifier: BUSL-1.1
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
pragma solidity ^0.8.26;
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
import {EkuboExecutor} from "@src/executors/EkuboExecutor.sol";
|
import {EkuboExecutor, TokenTransfer} from "@src/executors/EkuboExecutor.sol";
|
||||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
import {Constants} from "../Constants.sol";
|
import {Constants} from "../Constants.sol";
|
||||||
import {Test, console} from "forge-std/Test.sol";
|
import {Test, console} from "forge-std/Test.sol";
|
||||||
@@ -26,7 +26,7 @@ contract EkuboExecutorTest is Test, Constants {
|
|||||||
|
|
||||||
deployCodeTo(
|
deployCodeTo(
|
||||||
"executors/EkuboExecutor.sol",
|
"executors/EkuboExecutor.sol",
|
||||||
abi.encode(CORE_ADDRESS),
|
abi.encode(CORE_ADDRESS, PERMIT2_ADDRESS),
|
||||||
EXECUTOR_ADDRESS
|
EXECUTOR_ADDRESS
|
||||||
);
|
);
|
||||||
executor = EkuboExecutor(payable(EXECUTOR_ADDRESS));
|
executor = EkuboExecutor(payable(EXECUTOR_ADDRESS));
|
||||||
@@ -44,6 +44,7 @@ contract EkuboExecutorTest is Test, Constants {
|
|||||||
uint256 usdcBalanceBeforeExecutor = USDC.balanceOf(address(executor));
|
uint256 usdcBalanceBeforeExecutor = USDC.balanceOf(address(executor));
|
||||||
|
|
||||||
bytes memory data = abi.encodePacked(
|
bytes memory data = abi.encodePacked(
|
||||||
|
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType (transfer from executor to core)
|
||||||
address(executor), // receiver
|
address(executor), // receiver
|
||||||
NATIVE_TOKEN_ADDRESS, // tokenIn
|
NATIVE_TOKEN_ADDRESS, // tokenIn
|
||||||
USDC_ADDR, // tokenOut
|
USDC_ADDR, // tokenOut
|
||||||
@@ -80,6 +81,7 @@ contract EkuboExecutorTest is Test, Constants {
|
|||||||
uint256 ethBalanceBeforeExecutor = address(executor).balance;
|
uint256 ethBalanceBeforeExecutor = address(executor).balance;
|
||||||
|
|
||||||
bytes memory data = abi.encodePacked(
|
bytes memory data = abi.encodePacked(
|
||||||
|
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType (transfer from executor to core)
|
||||||
address(executor), // receiver
|
address(executor), // receiver
|
||||||
USDC_ADDR, // tokenIn
|
USDC_ADDR, // tokenIn
|
||||||
NATIVE_TOKEN_ADDRESS, // tokenOut
|
NATIVE_TOKEN_ADDRESS, // tokenOut
|
||||||
@@ -137,6 +139,7 @@ contract EkuboExecutorTest is Test, Constants {
|
|||||||
// Same test case as in swap_encoder::tests::ekubo::test_encode_swap_multi
|
// Same test case as in swap_encoder::tests::ekubo::test_encode_swap_multi
|
||||||
function testMultiHopSwap() public {
|
function testMultiHopSwap() public {
|
||||||
bytes memory data = abi.encodePacked(
|
bytes memory data = abi.encodePacked(
|
||||||
|
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType
|
||||||
address(executor), // receiver
|
address(executor), // receiver
|
||||||
NATIVE_TOKEN_ADDRESS, // tokenIn
|
NATIVE_TOKEN_ADDRESS, // tokenIn
|
||||||
USDC_ADDR, // tokenOut of 1st swap
|
USDC_ADDR, // tokenOut of 1st swap
|
||||||
@@ -152,7 +155,7 @@ contract EkuboExecutorTest is Test, Constants {
|
|||||||
// Data is generated by test case in swap_encoder::tests::ekubo::test_encode_swap_multi
|
// Data is generated by test case in swap_encoder::tests::ekubo::test_encode_swap_multi
|
||||||
function testMultiHopSwapIntegration() public {
|
function testMultiHopSwapIntegration() public {
|
||||||
multiHopSwap(
|
multiHopSwap(
|
||||||
hex"ca4f73fe97d0b987a0d12b39bbd562c779bab6f60000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000001a36e2eb1c43200000032"
|
hex"00ca4f73fe97d0b987a0d12b39bbd562c779bab6f60000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000001a36e2eb1c43200000032"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ pub static IN_TRANSFER_OPTIMIZABLE_PROTOCOLS: LazyLock<HashSet<&'static str>> =
|
|||||||
let mut set = HashSet::new();
|
let mut set = HashSet::new();
|
||||||
set.insert("uniswap_v2");
|
set.insert("uniswap_v2");
|
||||||
set.insert("uniswap_v3");
|
set.insert("uniswap_v3");
|
||||||
|
set.insert("ekubo_v2");
|
||||||
set
|
set
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -343,6 +343,7 @@ impl SwapEncoder for EkuboSwapEncoder {
|
|||||||
let mut encoded = vec![];
|
let mut encoded = vec![];
|
||||||
|
|
||||||
if encoding_context.group_token_in == swap.token_in {
|
if encoding_context.group_token_in == swap.token_in {
|
||||||
|
encoded.extend((encoding_context.transfer_type as u8).to_be_bytes());
|
||||||
encoded.extend(bytes_to_address(&encoding_context.receiver)?);
|
encoded.extend(bytes_to_address(&encoding_context.receiver)?);
|
||||||
encoded.extend(bytes_to_address(&swap.token_in)?);
|
encoded.extend(bytes_to_address(&swap.token_in)?);
|
||||||
}
|
}
|
||||||
@@ -1032,15 +1033,18 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
hex_swap,
|
hex_swap,
|
||||||
RECEIVER.to_string() +
|
concat!(
|
||||||
concat!(
|
// transfer type
|
||||||
// group token in
|
"00",
|
||||||
"0000000000000000000000000000000000000000",
|
// receiver
|
||||||
// token out 1st swap
|
"ca4f73fe97d0b987a0d12b39bbd562c779bab6f6",
|
||||||
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
// group token in
|
||||||
// pool config 1st swap
|
"0000000000000000000000000000000000000000",
|
||||||
"51d02a5948496a67827242eabc5725531342527c000000000000000000000000",
|
// token out 1st swap
|
||||||
),
|
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
||||||
|
// pool config 1st swap
|
||||||
|
"51d02a5948496a67827242eabc5725531342527c000000000000000000000000",
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1107,22 +1111,25 @@ mod tests {
|
|||||||
format!("{}{}", encode(first_encoded_swap), encode(second_encoded_swap));
|
format!("{}{}", encode(first_encoded_swap), encode(second_encoded_swap));
|
||||||
|
|
||||||
println!("{}", combined_hex);
|
println!("{}", combined_hex);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
combined_hex,
|
combined_hex,
|
||||||
RECEIVER.to_string() +
|
// transfer type
|
||||||
concat!(
|
concat!(
|
||||||
// group token in
|
// transfer type
|
||||||
"0000000000000000000000000000000000000000",
|
"00",
|
||||||
// token out 1st swap
|
// receiver
|
||||||
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
"ca4f73fe97d0b987a0d12b39bbd562c779bab6f6",
|
||||||
// pool config 1st swap
|
// group token in
|
||||||
"51d02a5948496a67827242eabc5725531342527c000000000000000000000000",
|
"0000000000000000000000000000000000000000",
|
||||||
// token out 2nd swap
|
// token out 1st swap
|
||||||
"dac17f958d2ee523a2206206994597c13d831ec7",
|
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
||||||
// pool config 2nd swap
|
// pool config 1st swap
|
||||||
"00000000000000000000000000000000000000000001a36e2eb1c43200000032",
|
"51d02a5948496a67827242eabc5725531342527c000000000000000000000000",
|
||||||
),
|
// token out 2nd swap
|
||||||
|
"dac17f958d2ee523a2206206994597c13d831ec7",
|
||||||
|
// pool config 2nd swap
|
||||||
|
"00000000000000000000000000000000000000000001a36e2eb1c43200000032",
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user