From b3078f9c7b99b3c0c9f0008b97855b48483f06dc Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Thu, 27 Mar 2025 23:38:32 +0100 Subject: [PATCH 1/7] feat: Support Ekubo callback in TychoRouter - add integration test - cannot directly call _handleCallback from the locked method of the tycho router because of bytes memory to bytes callback conversion - Rename to EkuboPoolKey because of conflict with USV4 pool key - Bonus: fix bug where input token to swap method must be ERC20 (we should also support ETH) --- config/executor_addresses.json | 3 +- foundry/lib/ekubo/interfaces/ICore.sol | 4 +- foundry/lib/ekubo/types/poolKey.sol | 2 +- foundry/src/TychoRouter.sol | 26 ++++++++- foundry/src/executors/EkuboExecutor.sol | 4 +- foundry/test/TychoRouter.t.sol | 21 +++++++ foundry/test/TychoRouterTestSetup.sol | 8 ++- .../evm/strategy_encoder/strategy_encoders.rs | 58 ++++++++++++++++++- src/encoding/evm/swap_encoder/builder.rs | 4 +- 9 files changed, 120 insertions(+), 10 deletions(-) diff --git a/config/executor_addresses.json b/config/executor_addresses.json index 2079fcf..46db52c 100644 --- a/config/executor_addresses.json +++ b/config/executor_addresses.json @@ -6,7 +6,8 @@ "uniswap_v3": "0xdD8559c917393FC8DD2b4dD289c52Ff445fDE1B0", "pancakeswap_v3": "0x4929B619A8F0D9c06ed0FfD497636580D823F65d", "uniswap_v4": "0x042C0ebBEAb9d9987c2f64Ee05f2B3aeB86eAf70", - "vm:balancer_v2": "0x00BE8EfAE40219Ff76287b0F9b9e497942f5BC91" + "vm:balancer_v2": "0x00BE8EfAE40219Ff76287b0F9b9e497942f5BC91", + "ekubo": "0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9" }, "tenderly_ethereum": { "uniswap_v2": "0x00C1b81e3C8f6347E69e2DDb90454798A6Be975E", diff --git a/foundry/lib/ekubo/interfaces/ICore.sol b/foundry/lib/ekubo/interfaces/ICore.sol index 2bc6b3d..d2388e8 100644 --- a/foundry/lib/ekubo/interfaces/ICore.sol +++ b/foundry/lib/ekubo/interfaces/ICore.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.26; import {IFlashAccountant} from "./IFlashAccountant.sol"; -import {PoolKey} from "../types/poolKey.sol"; +import {EkuboPoolKey} from "../types/poolKey.sol"; import {SqrtRatio} from "../types/sqrtRatio.sol"; interface ICore is IFlashAccountant { function swap_611415377( - PoolKey memory poolKey, + EkuboPoolKey memory poolKey, int128 amount, bool isToken1, SqrtRatio sqrtRatioLimit, diff --git a/foundry/lib/ekubo/types/poolKey.sol b/foundry/lib/ekubo/types/poolKey.sol index b0301e2..ad20db8 100644 --- a/foundry/lib/ekubo/types/poolKey.sol +++ b/foundry/lib/ekubo/types/poolKey.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.26; type Config is bytes32; // Each pool has its own state associated with this key -struct PoolKey { +struct EkuboPoolKey { address token0; address token1; Config config; diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 2f29f6e..d997588 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -143,7 +143,11 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { address receiver, bytes calldata swaps ) public payable whenNotPaused nonReentrant returns (uint256 amountOut) { - IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn); + if (address(tokenIn) != address(0)) { + IERC20(tokenIn).safeTransferFrom( + msg.sender, address(this), amountIn + ); + } return _swapChecked( amountIn, tokenIn, @@ -548,4 +552,24 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { _handleCallback(data); return ""; } + + function locked(uint256) external { + // TODO replace with real executor address once deployed + address executor = address(0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9); + + // slither-disable-next-line controlled-delegatecall,low-level-calls + (bool success, bytes memory result) = executor.delegatecall( + abi.encodeWithSelector(ICallback.handleCallback.selector, msg.data) + ); + + if (!success) { + revert( + string( + result.length > 0 + ? result + : abi.encodePacked("Callback failed") + ) + ); + } + } } diff --git a/foundry/src/executors/EkuboExecutor.sol b/foundry/src/executors/EkuboExecutor.sol index 4f291fe..66e508f 100644 --- a/foundry/src/executors/EkuboExecutor.sol +++ b/foundry/src/executors/EkuboExecutor.sol @@ -9,7 +9,7 @@ import {ILocker, IPayer} from "@ekubo/interfaces/IFlashAccountant.sol"; import {NATIVE_TOKEN_ADDRESS} from "@ekubo/math/constants.sol"; import {SafeTransferLib} from "@solady/utils/SafeTransferLib.sol"; import {LibBytes} from "@solady/utils/LibBytes.sol"; -import {Config, PoolKey} 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"; contract EkuboExecutor is IExecutor, ICallback, ILocker, IPayer { @@ -146,7 +146,7 @@ contract EkuboExecutor is IExecutor, ICallback, ILocker, IPayer { : (nextTokenIn, nextTokenOut, false); (int128 delta0, int128 delta1) = core.swap_611415377( - PoolKey(token0, token1, poolConfig), + EkuboPoolKey(token0, token1, poolConfig), nextAmountIn, isToken1, isToken1 ? MAX_SQRT_RATIO : MIN_SQRT_RATIO, diff --git a/foundry/test/TychoRouter.t.sol b/foundry/test/TychoRouter.t.sol index befc8b5..128c97a 100644 --- a/foundry/test/TychoRouter.t.sol +++ b/foundry/test/TychoRouter.t.sol @@ -1028,6 +1028,27 @@ contract TychoRouterTest is TychoRouterTestSetup { assertEq(balancerAfter - balancerBefore, 1120007305574805922); } + function testEkuboIntegration() public { + deal(ALICE, 1 ether); + uint256 balancerBefore = IERC20(USDC_ADDR).balanceOf(ALICE); + + // Approve permit2 + vm.startPrank(ALICE); + // Encoded solution generated using `test_split_encoding_strategy_ekubo` + (bool success,) = tychoRouterAddr.call{value: 1 ether}( + hex"0a83cb080000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000077007500010000005991a2df15a8f6a256d3ec51e99254cd3fb576a93ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c000000000000000000000000000000000000000000" + ); + + uint256 balancerAfter = IERC20(USDC_ADDR).balanceOf(ALICE); + + assertTrue(success, "Call Failed"); + assertGe(balancerAfter - balancerBefore, 26173932); + + // All input tokens are transferred to the router at first. Make sure we used + // all of it (and thus our splits are correct). + assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0); + } + function testSplitSwapIntegration() public { // Test created with calldata from our router encoder, replacing the executor // address with the USV2 executor address. diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index 1e7b70e..e1e62e9 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -10,6 +10,7 @@ import "@src/TychoRouter.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {WETH} from "../lib/permit2/lib/solmate/src/tokens/WETH.sol"; import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import "../src/executors/EkuboExecutor.sol"; contract TychoRouterExposed is TychoRouter { constructor(address _permit2, address weth) TychoRouter(_permit2, weth) {} @@ -38,6 +39,7 @@ contract TychoRouterTestSetup is Test, Constants { UniswapV3Executor public usv3Executor; UniswapV3Executor public pancakev3Executor; UniswapV4Executor public usv4Executor; + EkuboExecutor public ekuboExecutor; MockERC20[] tokens; function setUp() public { @@ -52,6 +54,8 @@ contract TychoRouterTestSetup is Test, Constants { bytes32 initCodeV3 = USV3_POOL_CODE_INIT_HASH; bytes32 initCodePancakeV3 = PANCAKEV3_POOL_CODE_INIT_HASH; address poolManagerAddress = 0x000000000004444c5dc75cB358380D2e3dE08A90; + ICore ekuboCore = ICore(0xe0e0e08A6A4b9Dc7bD67BCB7aadE5cF48157d444); + IPoolManager poolManager = IPoolManager(poolManagerAddress); tychoRouter = new TychoRouterExposed(PERMIT2_ADDRESS, WETH_ADDR); tychoRouterAddr = address(tychoRouter); @@ -70,12 +74,14 @@ contract TychoRouterTestSetup is Test, Constants { usv4Executor = new UniswapV4Executor(poolManager); pancakev3Executor = new UniswapV3Executor(factoryPancakeV3, initCodePancakeV3); + ekuboExecutor = new EkuboExecutor(ekuboCore); vm.startPrank(EXECUTOR_SETTER); - address[] memory executors = new address[](4); + address[] memory executors = new address[](5); executors[0] = address(usv2Executor); executors[1] = address(usv3Executor); executors[2] = address(pancakev3Executor); executors[3] = address(usv4Executor); + executors[3] = address(ekuboExecutor); tychoRouter.setExecutors(executors); vm.stopPrank(); diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index a306b27..b6e4e7f 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -338,7 +338,7 @@ mod tests { use std::{collections::HashMap, str::FromStr}; use alloy::hex::encode; - use alloy_primitives::hex; + use alloy_primitives::{hex, Address}; use num_bigint::{BigInt, BigUint}; use rstest::rstest; use tycho_common::{ @@ -1014,6 +1014,62 @@ mod tests { assert_eq!(hex_calldata[1288..], expected_swaps); } + #[test] + fn test_split_encoding_strategy_ekubo() { + // ETH ──(EKUBO)──> USDC + + let token_in = Bytes::from(Address::ZERO.as_slice()); + let token_out = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC + + let static_attributes = HashMap::from([ + ("fee".to_string(), Bytes::from(0_u64)), + ("tick_spacing".to_string(), Bytes::from(0_u32)), + ("extension".to_string(), Bytes::from("0x51d02a5948496a67827242eabc5725531342527c")), /* Oracle */ + ]); + + let component = ProtocolComponent { + // All Ekubo swaps go through the core contract - not necessary to specify pool id + // for test + protocol_system: "ekubo".to_string(), + static_attributes, + ..Default::default() + }; + + let swap = Swap { + component, + token_in: token_in.clone(), + token_out: token_out.clone(), + split: 0f64, + }; + + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = + SplitSwapStrategyEncoder::new(eth_chain(), swap_encoder_registry, None).unwrap(); + + let solution = Solution { + exact_out: false, + given_token: token_in, + given_amount: BigUint::from_str("1_000000000000000000").unwrap(), + checked_token: token_out, + expected_amount: None, + checked_amount: Some(BigUint::from_str("1").unwrap()), + slippage: None, + // Alice + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + router_address: Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + swaps: vec![swap], + ..Default::default() + }; + + let (calldata, _) = encoder + .encode_strategy(solution) + .unwrap(); + + let hex_calldata = encode(&calldata); + println!("{}", hex_calldata); + } + #[test] fn test_split_swap_strategy_encoder_simple_route_no_permit2() { // Performs a single swap from WETH to DAI on a USV2 pool, without permit2 and no grouping diff --git a/src/encoding/evm/swap_encoder/builder.rs b/src/encoding/evm/swap_encoder/builder.rs index 6918b0a..7e12789 100644 --- a/src/encoding/evm/swap_encoder/builder.rs +++ b/src/encoding/evm/swap_encoder/builder.rs @@ -1,7 +1,8 @@ use crate::encoding::{ errors::EncodingError, evm::swap_encoder::swap_encoders::{ - BalancerV2SwapEncoder, UniswapV2SwapEncoder, UniswapV3SwapEncoder, UniswapV4SwapEncoder, + BalancerV2SwapEncoder, EkuboSwapEncoder, UniswapV2SwapEncoder, UniswapV3SwapEncoder, + UniswapV4SwapEncoder, }, swap_encoder::SwapEncoder, }; @@ -29,6 +30,7 @@ impl SwapEncoderBuilder { "uniswap_v3" => Ok(Box::new(UniswapV3SwapEncoder::new(self.executor_address))), "pancakeswap_v3" => Ok(Box::new(UniswapV3SwapEncoder::new(self.executor_address))), "uniswap_v4" => Ok(Box::new(UniswapV4SwapEncoder::new(self.executor_address))), + "ekubo" => Ok(Box::new(EkuboSwapEncoder::new(self.executor_address))), _ => Err(EncodingError::FatalError(format!( "Unknown protocol system: {}", self.protocol_system From c678f400571d1b001c98595f68d9a99e0cf4900d Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 31 Mar 2025 17:21:21 +0200 Subject: [PATCH 2/7] fix: Take address for EkuboExecutor init - To match the other executor inits and to not leak information about ICore into the deployment script. - Also fix fork block of tests. This had been temporarily changed to test the Ekubo executor. For some reason, vm.rollFork is not working in this case. Skip the Ekubo integration test for now (we know it runs with the correct block, where the pool we call is actually already deployed) --- foundry/src/executors/EkuboExecutor.sol | 4 ++-- foundry/test/TychoRouter.t.sol | 3 +++ foundry/test/TychoRouterTestSetup.sol | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/foundry/src/executors/EkuboExecutor.sol b/foundry/src/executors/EkuboExecutor.sol index 66e508f..d4175a3 100644 --- a/foundry/src/executors/EkuboExecutor.sol +++ b/foundry/src/executors/EkuboExecutor.sol @@ -25,8 +25,8 @@ contract EkuboExecutor is IExecutor, ICallback, ILocker, IPayer { uint256 constant POOL_DATA_OFFSET = 56; uint256 constant HOP_BYTE_LEN = 52; - constructor(ICore _core) { - core = _core; + constructor(address _core) { + core = ICore(_core); } function swap(uint256 amountIn, bytes calldata data) diff --git a/foundry/test/TychoRouter.t.sol b/foundry/test/TychoRouter.t.sol index 128c97a..38ae918 100644 --- a/foundry/test/TychoRouter.t.sol +++ b/foundry/test/TychoRouter.t.sol @@ -1029,6 +1029,9 @@ contract TychoRouterTest is TychoRouterTestSetup { } function testEkuboIntegration() public { + vm.skip(true); + // Test needs to be run on block 22082754 or later + deal(ALICE, 1 ether); uint256 balancerBefore = IERC20(USDC_ADDR).balanceOf(ALICE); diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index e1e62e9..f53b822 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -54,7 +54,7 @@ contract TychoRouterTestSetup is Test, Constants { bytes32 initCodeV3 = USV3_POOL_CODE_INIT_HASH; bytes32 initCodePancakeV3 = PANCAKEV3_POOL_CODE_INIT_HASH; address poolManagerAddress = 0x000000000004444c5dc75cB358380D2e3dE08A90; - ICore ekuboCore = ICore(0xe0e0e08A6A4b9Dc7bD67BCB7aadE5cF48157d444); + address ekuboCore = 0xe0e0e08A6A4b9Dc7bD67BCB7aadE5cF48157d444; IPoolManager poolManager = IPoolManager(poolManagerAddress); tychoRouter = new TychoRouterExposed(PERMIT2_ADDRESS, WETH_ADDR); From c0068d456bbd271d1c74797577e0fe514be0fcc7 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 31 Mar 2025 17:42:56 +0200 Subject: [PATCH 3/7] fix: Finalize ekubo executor address --- config/executor_addresses.json | 2 +- foundry/scripts/deploy-executors.js | 3 +++ foundry/src/TychoRouter.sol | 3 +-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/config/executor_addresses.json b/config/executor_addresses.json index 46db52c..92caa97 100644 --- a/config/executor_addresses.json +++ b/config/executor_addresses.json @@ -7,7 +7,7 @@ "pancakeswap_v3": "0x4929B619A8F0D9c06ed0FfD497636580D823F65d", "uniswap_v4": "0x042C0ebBEAb9d9987c2f64Ee05f2B3aeB86eAf70", "vm:balancer_v2": "0x00BE8EfAE40219Ff76287b0F9b9e497942f5BC91", - "ekubo": "0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9" + "ekubo": "0x5E40985A4d4E8DbAd1dc35fFCfacfCde3e3d1806" }, "tenderly_ethereum": { "uniswap_v2": "0x00C1b81e3C8f6347E69e2DDb90454798A6Be975E", diff --git a/foundry/scripts/deploy-executors.js b/foundry/scripts/deploy-executors.js index c8809b7..6c0f55b 100644 --- a/foundry/scripts/deploy-executors.js +++ b/foundry/scripts/deploy-executors.js @@ -33,6 +33,9 @@ const executors_to_deploy = { // Args: Pool manager {exchange: "UniswapV4Executor", args: ["0x000000000004444c5dc75cB358380D2e3dE08A90"]}, {exchange: "BalancerV2Executor", args: []}, + // Args: Ekubo core contract + {exchange: "EkuboExecutor", args: [ + "0xe0e0e08A6A4b9Dc7bD67BCB7aadE5cF48157d444" ], "base":[ // Args: Factory, Pool Init Code Hash diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index d997588..66c0be6 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -554,8 +554,7 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { } function locked(uint256) external { - // TODO replace with real executor address once deployed - address executor = address(0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9); + address executor = address(0x5E40985A4d4E8DbAd1dc35fFCfacfCde3e3d1806); // slither-disable-next-line controlled-delegatecall,low-level-calls (bool success, bytes memory result) = executor.delegatecall( From 28f9f244e6343393a96020a85f5e202012d7ca26 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 31 Mar 2025 17:54:35 +0200 Subject: [PATCH 4/7] fix: test setup fix after rebase --- foundry/test/TychoRouterTestSetup.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index f53b822..4f1343f 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -75,13 +75,14 @@ contract TychoRouterTestSetup is Test, Constants { pancakev3Executor = new UniswapV3Executor(factoryPancakeV3, initCodePancakeV3); ekuboExecutor = new EkuboExecutor(ekuboCore); + vm.startPrank(EXECUTOR_SETTER); address[] memory executors = new address[](5); executors[0] = address(usv2Executor); executors[1] = address(usv3Executor); executors[2] = address(pancakev3Executor); executors[3] = address(usv4Executor); - executors[3] = address(ekuboExecutor); + executors[4] = address(ekuboExecutor); tychoRouter.setExecutors(executors); vm.stopPrank(); From 6c35f114e383b7ba93cb44d314a05f69ab15fadd Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 31 Mar 2025 18:38:12 +0200 Subject: [PATCH 5/7] fix: ekubo -> ekubo_v2 This is how it's called in tycho indexer/simulation. --- config/executor_addresses.json | 2 +- src/encoding/evm/swap_encoder/builder.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/executor_addresses.json b/config/executor_addresses.json index 92caa97..cf8fc1a 100644 --- a/config/executor_addresses.json +++ b/config/executor_addresses.json @@ -7,7 +7,7 @@ "pancakeswap_v3": "0x4929B619A8F0D9c06ed0FfD497636580D823F65d", "uniswap_v4": "0x042C0ebBEAb9d9987c2f64Ee05f2B3aeB86eAf70", "vm:balancer_v2": "0x00BE8EfAE40219Ff76287b0F9b9e497942f5BC91", - "ekubo": "0x5E40985A4d4E8DbAd1dc35fFCfacfCde3e3d1806" + "ekubo_v2": "0x5E40985A4d4E8DbAd1dc35fFCfacfCde3e3d1806" }, "tenderly_ethereum": { "uniswap_v2": "0x00C1b81e3C8f6347E69e2DDb90454798A6Be975E", diff --git a/src/encoding/evm/swap_encoder/builder.rs b/src/encoding/evm/swap_encoder/builder.rs index 7e12789..65bc317 100644 --- a/src/encoding/evm/swap_encoder/builder.rs +++ b/src/encoding/evm/swap_encoder/builder.rs @@ -30,7 +30,7 @@ impl SwapEncoderBuilder { "uniswap_v3" => Ok(Box::new(UniswapV3SwapEncoder::new(self.executor_address))), "pancakeswap_v3" => Ok(Box::new(UniswapV3SwapEncoder::new(self.executor_address))), "uniswap_v4" => Ok(Box::new(UniswapV4SwapEncoder::new(self.executor_address))), - "ekubo" => Ok(Box::new(EkuboSwapEncoder::new(self.executor_address))), + "ekubo_v2" => Ok(Box::new(EkuboSwapEncoder::new(self.executor_address))), _ => Err(EncodingError::FatalError(format!( "Unknown protocol system: {}", self.protocol_system From 18fa0cc7adfaa46879aa3637c03563c770582b0d Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 31 Mar 2025 18:45:36 +0200 Subject: [PATCH 6/7] fix: ekubo -> ekubo_v2 This is how it's called in tycho indexer/simulation. --- src/encoding/evm/strategy_encoder/strategy_encoders.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index b6e4e7f..7ac8fd7 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -1030,7 +1030,7 @@ mod tests { let component = ProtocolComponent { // All Ekubo swaps go through the core contract - not necessary to specify pool id // for test - protocol_system: "ekubo".to_string(), + protocol_system: "ekubo_v2".to_string(), static_attributes, ..Default::default() }; From 75516122e1a084f86c34e6eaaf43fe5f53a30d96 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 31 Mar 2025 21:01:36 +0200 Subject: [PATCH 7/7] fix: support payCallback method for Ekubo - depending on the pool, either lock or payCallback may be called. --- foundry/src/TychoRouter.sol | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 66c0be6..21d51f9 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -571,4 +571,23 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { ); } } + + function payCallback(uint256, address /*token*/ ) external { + address executor = address(0x5E40985A4d4E8DbAd1dc35fFCfacfCde3e3d1806); + + // slither-disable-next-line controlled-delegatecall,low-level-calls + (bool success, bytes memory result) = executor.delegatecall( + abi.encodeWithSelector(ICallback.handleCallback.selector, msg.data) + ); + + if (!success) { + revert( + string( + result.length > 0 + ? result + : abi.encodePacked("Callback failed") + ) + ); + } + } }