From 5d4d6d1ff891766c067e3ff6355ffbb5c50bbf16 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 10 Apr 2025 12:18:21 +0100 Subject: [PATCH 01/22] feat: Deploy Curve Executor --- don't change below this line --- ENG-4307 Took 27 minutes --- config/executor_addresses.json | 2 +- foundry/scripts/deploy-executors.js | 188 ++++++++++++++++------------ 2 files changed, 111 insertions(+), 79 deletions(-) diff --git a/config/executor_addresses.json b/config/executor_addresses.json index 89ebca7..f6b0579 100644 --- a/config/executor_addresses.json +++ b/config/executor_addresses.json @@ -8,7 +8,7 @@ "uniswap_v4": "0x042C0ebBEAb9d9987c2f64Ee05f2B3aeB86eAf70", "vm:balancer_v2": "0x00BE8EfAE40219Ff76287b0F9b9e497942f5BC91", "ekubo_v2": "0x4f88f6630a33dB05BEa1FeF7Dc7ff7508D1c531D", - "vm:curve": "0x1d1499e622D69689cdf9004d05Ec547d650Ff211" + "vm:curve": "0x8495985edA37081173A00F2F7d2cd55D42fecE35" }, "tenderly_ethereum": { "uniswap_v2": "0x00C1b81e3C8f6347E69e2DDb90454798A6Be975E", diff --git a/foundry/scripts/deploy-executors.js b/foundry/scripts/deploy-executors.js index fa32960..7149d63 100644 --- a/foundry/scripts/deploy-executors.js +++ b/foundry/scripts/deploy-executors.js @@ -4,84 +4,116 @@ const hre = require("hardhat"); // Comment out the executors you don't want to deploy const executors_to_deploy = { - "ethereum":[ - // USV2 - Args: Factory, Pool Init Code Hash - {exchange: "UniswapV2Executor", args: [ - "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", - "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" - ]}, - // SUSHISWAP - Args: Factory, Pool Init Code Hash - {exchange: "UniswapV2Executor", args: [ - "0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac", - "0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303" - ]}, - // PANCAKESWAP V2 - Args: Factory, Pool Init Code Hash - {exchange: "UniswapV2Executor", args: [ - "0x1097053Fd2ea711dad45caCcc45EfF7548fCB362", - "0x57224589c67f3f30a6b0d7a1b54cf3153ab84563bc609ef41dfb34f8b2974d2d" - ]}, - // USV3 -Args: Factory, Pool Init Code Hash - {exchange: "UniswapV3Executor", args: [ - "0x1F98431c8aD98523631AE4a59f267346ea31F984", - "0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54" - ]}, - // PANCAKESWAP V3 - Args: Deployer, Pool Init Code Hash - {exchange: "UniswapV3Executor", args: [ - "0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9", - "0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2" - ]}, - // 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 - {exchange: "UniswapV2Executor", args: [ - "0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6", - "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" - ]}, - // SUSHISWAP V2 - Args: Factory, Pool Init Code Hash - {exchange: "UniswapV2Executor", args: [ - "0x71524B4f93c58fcbF659783284E38825f0622859", - "0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303" - ]}, - // PANCAKESWAP V2 - Args: Factory, Pool Init Code Hash - {exchange: "UniswapV2Executor", args: [ - "0x02a84c1b3BBD7401a5f7fa98a384EBC70bB5749E", - "0x57224589c67f3f30a6b0d7a1b54cf3153ab84563bc609ef41dfb34f8b2974d2d" - ]}, - // USV3 - Args: Factory, Pool Init Code Hash - {exchange: "UniswapV3Executor", args: [ - "0x33128a8fC17869897dcE68Ed026d694621f6FDfD", - "0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54" - ]}, - // PANCAKESWAP V3 - Args: Deployer, Pool Init Code Hash - {exchange: "UniswapV3Executor", args: [ - "0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9", - "0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2" - ]}, - // Args: Pool manager - {exchange: "UniswapV4Executor", args: ["0x498581ff718922c3f8e6a244956af099b2652b2b"]}, - {exchange: "BalancerV2Executor", args: []}, - ], - "unichain":[ - // Args: Factory, Pool Init Code Hash - {exchange: "UniswapV2Executor", args: [ - "0x1f98400000000000000000000000000000000002", - "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" - ]}, - // USV3 - Args: Factory, Pool Init Code Hash - {exchange: "UniswapV3Executor", args: [ - "0x1f98400000000000000000000000000000000003", - "0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54" - ]}, - // Args: Pool manager - {exchange: "UniswapV4Executor", args: ["0x1f98400000000000000000000000000000000004"]}, - ], + "ethereum": [ + // USV2 - Args: Factory, Pool Init Code Hash + { + exchange: "UniswapV2Executor", args: [ + "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", + "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" + ] + }, + // SUSHISWAP - Args: Factory, Pool Init Code Hash + { + exchange: "UniswapV2Executor", args: [ + "0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac", + "0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303" + ] + }, + // PANCAKESWAP V2 - Args: Factory, Pool Init Code Hash + { + exchange: "UniswapV2Executor", args: [ + "0x1097053Fd2ea711dad45caCcc45EfF7548fCB362", + "0x57224589c67f3f30a6b0d7a1b54cf3153ab84563bc609ef41dfb34f8b2974d2d" + ] + }, + // USV3 -Args: Factory, Pool Init Code Hash + { + exchange: "UniswapV3Executor", args: [ + "0x1F98431c8aD98523631AE4a59f267346ea31F984", + "0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54" + ] + }, + // PANCAKESWAP V3 - Args: Deployer, Pool Init Code Hash + { + exchange: "UniswapV3Executor", args: [ + "0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9", + "0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2" + ] + }, + // Args: Pool manager + {exchange: "UniswapV4Executor", args: ["0x000000000004444c5dc75cB358380D2e3dE08A90"]}, + {exchange: "BalancerV2Executor", args: []}, + // Args: Ekubo core contract + { + exchange: "EkuboExecutor", args: [ + "0xe0e0e08A6A4b9Dc7bD67BCB7aadE5cF48157d444" + ] + }, + // Args: ETH address in curve pools + { + exchange: "CurveExecutor", args: [ + "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" + ] + } + ], + "base": [ + // Args: Factory, Pool Init Code Hash + { + exchange: "UniswapV2Executor", args: [ + "0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6", + "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" + ] + }, + // SUSHISWAP V2 - Args: Factory, Pool Init Code Hash + { + exchange: "UniswapV2Executor", args: [ + "0x71524B4f93c58fcbF659783284E38825f0622859", + "0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303" + ] + }, + // PANCAKESWAP V2 - Args: Factory, Pool Init Code Hash + { + exchange: "UniswapV2Executor", args: [ + "0x02a84c1b3BBD7401a5f7fa98a384EBC70bB5749E", + "0x57224589c67f3f30a6b0d7a1b54cf3153ab84563bc609ef41dfb34f8b2974d2d" + ] + }, + // USV3 - Args: Factory, Pool Init Code Hash + { + exchange: "UniswapV3Executor", args: [ + "0x33128a8fC17869897dcE68Ed026d694621f6FDfD", + "0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54" + ] + }, + // PANCAKESWAP V3 - Args: Deployer, Pool Init Code Hash + { + exchange: "UniswapV3Executor", args: [ + "0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9", + "0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2" + ] + }, + // Args: Pool manager + {exchange: "UniswapV4Executor", args: ["0x498581ff718922c3f8e6a244956af099b2652b2b"]}, + {exchange: "BalancerV2Executor", args: []}, + ], + "unichain": [ + // Args: Factory, Pool Init Code Hash + { + exchange: "UniswapV2Executor", args: [ + "0x1f98400000000000000000000000000000000002", + "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" + ] + }, + // USV3 - Args: Factory, Pool Init Code Hash + { + exchange: "UniswapV3Executor", args: [ + "0x1f98400000000000000000000000000000000003", + "0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54" + ] + }, + // Args: Pool manager + {exchange: "UniswapV4Executor", args: ["0x1f98400000000000000000000000000000000004"]}, + ], } async function main() { From 9e68ab8b0127831ee9dbc1f168e8aac9e28991c0 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 10 Apr 2025 12:38:39 +0100 Subject: [PATCH 02/22] fix: Checksum curve pool addresses --- don't change below this line --- ENG-4307 Took 5 minutes --- src/encoding/evm/swap_encoder/swap_encoders.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index cf41e88..c6a5d49 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -535,7 +535,10 @@ impl SwapEncoder for CurveSwapEncoder { })?) .map_err(|_| EncodingError::FatalError("Invalid curve factory address".to_string()))?; - let pool_type = self.get_pool_type(&swap.component.id, &factory_address.to_string())?; + let pool_address = Address::from_str(&swap.component.id) + .map_err(|_| EncodingError::FatalError("Invalid curve pool address".to_string()))?; + let pool_type = + self.get_pool_type(&pool_address.to_string(), &factory_address.to_string())?; let (i, j) = self.get_coin_indexes(component_address, token_in, token_out)?; From c963f3b2f61e9d1a6e333149b091c3df90fd857b Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 10 Apr 2025 15:44:02 +0100 Subject: [PATCH 03/22] fix: Use forceApprove instead of regular Approve This is necessary for USDT (it was failing) --- don't change below this line --- ENG-4307 Took 29 minutes --- foundry/src/executors/CurveExecutor.sol | 2 +- foundry/test/executors/CurveExecutor.t.sol | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index 569072f..5a72019 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -62,7 +62,7 @@ contract CurveExecutor is IExecutor { if (tokenApprovalNeeded && tokenIn != nativeToken) { // slither-disable-next-line unused-return - IERC20(tokenIn).approve(address(pool), type(uint256).max); + IERC20(tokenIn).forceApprove(address(pool), type(uint256).max); } /// Inspired by Curve's router contract: https://github.com/curvefi/curve-router-ng/blob/9ab006ca848fc7f1995b6fbbecfecc1e0eb29e2a/contracts/Router.vy#L44 diff --git a/foundry/test/executors/CurveExecutor.t.sol b/foundry/test/executors/CurveExecutor.t.sol index cd15079..601e54e 100644 --- a/foundry/test/executors/CurveExecutor.t.sol +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -266,16 +266,16 @@ contract CurveExecutorTest is Test, Constants { function testStableSwapPool() public { // Swapping CRVUSD -> USDT on a StableSwap pool, deployed by factory 0x4F8846Ae9380B90d2E71D5e3D042dff3E7ebb40d (plain pool) uint256 amountIn = 1 ether; - deal(CRVUSD_ADDR, address(curveExecutorExposed), amountIn); + deal(USDT_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = - _getData(CRVUSD_ADDR, USDT_ADDR, CRVUSD_USDT_POOL, 1); + _getData(USDT_ADDR, CRVUSD_ADDR, CRVUSD_USDT_POOL, 1); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - assertEq(amountOut, 999910); + assertEq(amountOut, 10436946786333182306400100); assertEq( - IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)), + IERC20(CRVUSD_ADDR).balanceOf(address(curveExecutorExposed)), amountOut ); } From 916c2b7dba2c1c424efcbf884932a05427816cf8 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 10 Apr 2025 15:52:21 +0100 Subject: [PATCH 04/22] feat: Add new CurveExecutor address --- don't change below this line --- ENG-4307 Took 5 minutes --- config/executor_addresses.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/executor_addresses.json b/config/executor_addresses.json index f6b0579..e7a8efc 100644 --- a/config/executor_addresses.json +++ b/config/executor_addresses.json @@ -8,7 +8,7 @@ "uniswap_v4": "0x042C0ebBEAb9d9987c2f64Ee05f2B3aeB86eAf70", "vm:balancer_v2": "0x00BE8EfAE40219Ff76287b0F9b9e497942f5BC91", "ekubo_v2": "0x4f88f6630a33dB05BEa1FeF7Dc7ff7508D1c531D", - "vm:curve": "0x8495985edA37081173A00F2F7d2cd55D42fecE35" + "vm:curve": "0x2751999a30A0026c909c4f1EB92d123254CABa7F" }, "tenderly_ethereum": { "uniswap_v2": "0x00C1b81e3C8f6347E69e2DDb90454798A6Be975E", From 2e8392ab40c6c0e99089fae71873755dedb6e925 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Fri, 11 Apr 2025 16:17:37 +0100 Subject: [PATCH 05/22] fix: Support pools that hold ETH but the coin is WETH --- don't change below this line --- ENG-4307 Took 1 hour 46 minutes --- .../evm/swap_encoder/swap_encoders.rs | 50 +++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index c6a5d49..8ce45da 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -377,6 +377,7 @@ pub struct CurveSwapEncoder { meta_registry_address: String, native_token_curve_address: String, native_token_address: Bytes, + wrapped_native_token_address: Bytes, } impl CurveSwapEncoder { @@ -449,10 +450,34 @@ impl CurveSwapEncoder { let j = U8::from(j_256); Ok((i, j)) } - Err(err) => Err(EncodingError::RecoverableError(format!( - "Curve meta registry call failed with error: {:?}", - err - ))), + Err(err) => { + // Temporary until we get the coin indexes from the indexer + // This is because some curve pools hold ETH but the coin is defined as WETH + // Our indexer reports this pool as holding ETH but then here we need to use WETH + // This is valid only for some pools, that's why we are doing the trial and error + // approach + let native_token_curve_address = + Address::from_str(&self.native_token_curve_address).map_err(|_| { + EncodingError::FatalError( + "Invalid Curve native token curve address".to_string(), + ) + })?; + if token_in != native_token_curve_address && token_out != native_token_curve_address + { + Err(EncodingError::RecoverableError(format!( + "Curve meta registry call failed with error: {:?}", + err + ))) + } else { + let wrapped_token = bytes_to_address(&self.wrapped_native_token_address)?; + let (i, j) = if token_in == native_token_curve_address { + self.get_coin_indexes(pool_id, wrapped_token, token_out)? + } else { + self.get_coin_indexes(pool_id, token_in, wrapped_token)? + }; + Ok((i, j)) + } + } } } } @@ -482,6 +507,7 @@ impl SwapEncoder for CurveSwapEncoder { executor_address, meta_registry_address, native_token_address: chain.native_token()?, + wrapped_native_token_address: chain.wrapped_token()?, native_token_curve_address, }) } @@ -1168,6 +1194,22 @@ mod tests { 2, 0 )] + // Pool that holds ETH but coin is WETH + #[case( + "0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B", + "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + 2, + 0 + )] + // Pool that holds ETH but coin is WETH + #[case( + "0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + 0, + 2 + )] fn test_curve_get_coin_indexes( #[case] pool: &str, #[case] token_in: &str, From 4f3fe270e9ca2e877382baae28fc34ea34ff50ea Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 11 Apr 2025 16:00:31 +0000 Subject: [PATCH 06/22] chore(release): 0.79.0 [skip ci] ## [0.79.0](https://github.com/propeller-heads/tycho-execution/compare/0.78.1...0.79.0) (2025-04-11) ### Features * Add new CurveExecutor address ([916c2b7](https://github.com/propeller-heads/tycho-execution/commit/916c2b7dba2c1c424efcbf884932a05427816cf8)) * Deploy Curve Executor ([5d4d6d1](https://github.com/propeller-heads/tycho-execution/commit/5d4d6d1ff891766c067e3ff6355ffbb5c50bbf16)) ### Bug Fixes * Checksum curve pool addresses ([9e68ab8](https://github.com/propeller-heads/tycho-execution/commit/9e68ab8b0127831ee9dbc1f168e8aac9e28991c0)) * Support pools that hold ETH but the coin is WETH ([2e8392a](https://github.com/propeller-heads/tycho-execution/commit/2e8392ab40c6c0e99089fae71873755dedb6e925)) * Use forceApprove instead of regular Approve ([c963f3b](https://github.com/propeller-heads/tycho-execution/commit/c963f3b2f61e9d1a6e333149b091c3df90fd857b)) --- CHANGELOG.md | 15 +++++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e854a..53abf22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## [0.79.0](https://github.com/propeller-heads/tycho-execution/compare/0.78.1...0.79.0) (2025-04-11) + + +### Features + +* Add new CurveExecutor address ([916c2b7](https://github.com/propeller-heads/tycho-execution/commit/916c2b7dba2c1c424efcbf884932a05427816cf8)) +* Deploy Curve Executor ([5d4d6d1](https://github.com/propeller-heads/tycho-execution/commit/5d4d6d1ff891766c067e3ff6355ffbb5c50bbf16)) + + +### Bug Fixes + +* Checksum curve pool addresses ([9e68ab8](https://github.com/propeller-heads/tycho-execution/commit/9e68ab8b0127831ee9dbc1f168e8aac9e28991c0)) +* Support pools that hold ETH but the coin is WETH ([2e8392a](https://github.com/propeller-heads/tycho-execution/commit/2e8392ab40c6c0e99089fae71873755dedb6e925)) +* Use forceApprove instead of regular Approve ([c963f3b](https://github.com/propeller-heads/tycho-execution/commit/c963f3b2f61e9d1a6e333149b091c3df90fd857b)) + ## [0.78.1](https://github.com/propeller-heads/tycho-execution/compare/0.78.0...0.78.1) (2025-04-09) diff --git a/Cargo.lock b/Cargo.lock index 09887cc..41cd13d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4341,7 +4341,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.78.1" +version = "0.79.0" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index 27c26a8..a482e14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.78.1" +version = "0.79.0" edition = "2021" description = "Provides tools for encoding and executing swaps against Tycho router and protocol executors." repository = "https://github.com/propeller-heads/tycho-execution" From a6b0f8d1f67a49848e90d2c4102195c4ac40c5a8 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Mon, 14 Apr 2025 09:49:39 +0100 Subject: [PATCH 07/22] feat: Redeploy balancer with forceApprove fix for USDT --- don't change below this line --- ENG-4307 Took 10 minutes --- config/executor_addresses.json | 2 +- foundry/src/executors/BalancerV2Executor.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/executor_addresses.json b/config/executor_addresses.json index e7a8efc..4faa439 100644 --- a/config/executor_addresses.json +++ b/config/executor_addresses.json @@ -6,7 +6,7 @@ "uniswap_v3": "0xdD8559c917393FC8DD2b4dD289c52Ff445fDE1B0", "pancakeswap_v3": "0x4929B619A8F0D9c06ed0FfD497636580D823F65d", "uniswap_v4": "0x042C0ebBEAb9d9987c2f64Ee05f2B3aeB86eAf70", - "vm:balancer_v2": "0x00BE8EfAE40219Ff76287b0F9b9e497942f5BC91", + "vm:balancer_v2": "0x2380a9ff20565191b67cd66914cf5151434d71f5", "ekubo_v2": "0x4f88f6630a33dB05BEa1FeF7Dc7ff7508D1c531D", "vm:curve": "0x2751999a30A0026c909c4f1EB92d123254CABa7F" }, diff --git a/foundry/src/executors/BalancerV2Executor.sol b/foundry/src/executors/BalancerV2Executor.sol index 0f69fcd..fdcbf3f 100644 --- a/foundry/src/executors/BalancerV2Executor.sol +++ b/foundry/src/executors/BalancerV2Executor.sol @@ -34,7 +34,7 @@ contract BalancerV2Executor is IExecutor { if (needsApproval) { // slither-disable-next-line unused-return - tokenIn.approve(VAULT, type(uint256).max); + tokenIn.forceApprove(VAULT, type(uint256).max); } IVault.SingleSwap memory singleSwap = IVault.SingleSwap({ From fa3ca344804261dc3e87f2ac21055c23c33aba18 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 14 Apr 2025 14:18:39 +0000 Subject: [PATCH 08/22] chore(release): 0.80.0 [skip ci] ## [0.80.0](https://github.com/propeller-heads/tycho-execution/compare/0.79.0...0.80.0) (2025-04-14) ### Features * Redeploy balancer with forceApprove fix for USDT ([a6b0f8d](https://github.com/propeller-heads/tycho-execution/commit/a6b0f8d1f67a49848e90d2c4102195c4ac40c5a8)) --- CHANGELOG.md | 7 +++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53abf22..cfd7d23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.80.0](https://github.com/propeller-heads/tycho-execution/compare/0.79.0...0.80.0) (2025-04-14) + + +### Features + +* Redeploy balancer with forceApprove fix for USDT ([a6b0f8d](https://github.com/propeller-heads/tycho-execution/commit/a6b0f8d1f67a49848e90d2c4102195c4ac40c5a8)) + ## [0.79.0](https://github.com/propeller-heads/tycho-execution/compare/0.78.1...0.79.0) (2025-04-11) diff --git a/Cargo.lock b/Cargo.lock index 41cd13d..5a81936 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4341,7 +4341,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.79.0" +version = "0.80.0" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index a482e14..455af9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.79.0" +version = "0.80.0" edition = "2021" description = "Provides tools for encoding and executing swaps against Tycho router and protocol executors." repository = "https://github.com/propeller-heads/tycho-execution" From 134c73e82be74fb5590e19c3d9b27304043bbbd8 Mon Sep 17 00:00:00 2001 From: Louise Poole Date: Fri, 18 Apr 2025 11:31:40 +0200 Subject: [PATCH 09/22] feat: update tycho-common version to 0.66.4 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a81936..a84ff6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4317,9 +4317,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tycho-common" -version = "0.64.1" +version = "0.66.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e318a43fab79199deaab2391c83c75724780151c0337b67914ed835ff04b52f" +checksum = "5131fdb21cbd754822b0947fc6c763494531837ba8bb34123f6c7f4f89cb69f7" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 455af9b..18cf38d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ clap = { version = "4.5.3", features = ["derive"] } alloy = { version = "0.9.2", features = ["providers", "rpc-types-eth", "eip712", "signer-local"], optional = true } alloy-sol-types = { version = "0.8.14", optional = true } alloy-primitives = { version = "0.8.9", optional = true } -tycho-common = "0.64.1" +tycho-common = "^0.66.4" once_cell = "1.20.2" [dev-dependencies] From 20573cbaf320ba99aa721e6e76a69447ab3f9694 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Fri, 18 Apr 2025 08:46:32 -0400 Subject: [PATCH 10/22] fix: add slither disable after slither actions update - We have always been ok with risk here and ensured this won't happen for our use case, but slither actions version was recently updated so this fails in CI. --- foundry/src/Dispatcher.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foundry/src/Dispatcher.sol b/foundry/src/Dispatcher.sol index 828d0b1..9af755d 100644 --- a/foundry/src/Dispatcher.sol +++ b/foundry/src/Dispatcher.sol @@ -62,7 +62,7 @@ contract Dispatcher { revert Dispatcher__UnapprovedExecutor(); } - // 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( abi.encodeWithSelector(IExecutor.swap.selector, amount, data) ); From 5e7c0721c51ef28d5435c06bbc45a85eae94043d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 18 Apr 2025 12:52:51 +0000 Subject: [PATCH 11/22] chore(release): 0.81.0 [skip ci] ## [0.81.0](https://github.com/propeller-heads/tycho-execution/compare/0.80.0...0.81.0) (2025-04-18) ### Features * update tycho-common version to 0.66.4 ([134c73e](https://github.com/propeller-heads/tycho-execution/commit/134c73e82be74fb5590e19c3d9b27304043bbbd8)) ### Bug Fixes * add slither disable after slither actions update ([20573cb](https://github.com/propeller-heads/tycho-execution/commit/20573cbaf320ba99aa721e6e76a69447ab3f9694)) --- CHANGELOG.md | 12 ++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfd7d23..d8cd8aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## [0.81.0](https://github.com/propeller-heads/tycho-execution/compare/0.80.0...0.81.0) (2025-04-18) + + +### Features + +* update tycho-common version to 0.66.4 ([134c73e](https://github.com/propeller-heads/tycho-execution/commit/134c73e82be74fb5590e19c3d9b27304043bbbd8)) + + +### Bug Fixes + +* add slither disable after slither actions update ([20573cb](https://github.com/propeller-heads/tycho-execution/commit/20573cbaf320ba99aa721e6e76a69447ab3f9694)) + ## [0.80.0](https://github.com/propeller-heads/tycho-execution/compare/0.79.0...0.80.0) (2025-04-14) diff --git a/Cargo.lock b/Cargo.lock index a84ff6c..ae054ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4341,7 +4341,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.80.0" +version = "0.81.0" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index 18cf38d..0421e50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.80.0" +version = "0.81.0" edition = "2021" description = "Provides tools for encoding and executing swaps against Tycho router and protocol executors." repository = "https://github.com/propeller-heads/tycho-execution" From 87de8bd6379f16cf8236a37b4ad31741b618c167 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Tue, 22 Apr 2025 12:14:21 +0100 Subject: [PATCH 12/22] chore: Refactor TransferOptimization to be a struct instead of a trait --- don't change below this line --- ENG-4446 Took 39 minutes --- .../evm/strategy_encoder/strategy_encoders.rs | 78 +++++++++---------- .../transfer_optimizations.rs | 66 ++++++++-------- 2 files changed, 69 insertions(+), 75 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 7dd7afd..bad91bf 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -32,17 +32,15 @@ use crate::encoding::{ /// * `permit2`: Permit2, responsible for managing permit2 operations and providing necessary /// signatures and permit2 objects for calling the router /// * `selector`: String, the selector for the swap function in the router contract -/// * `native_address`: Address of the chain's native token -/// * `wrapped_address`: Address of the chain's wrapped token /// * `router_address`: Address of the router to be used to execute swaps +/// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers #[derive(Clone)] pub struct SingleSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, permit2: Option, selector: String, - native_address: Bytes, - wrapped_address: Bytes, router_address: Bytes, + transfer_optimization: TransferOptimization, } impl SingleSwapStrategyEncoder { @@ -60,13 +58,17 @@ impl SingleSwapStrategyEncoder { "singleSwap(uint256,address,address,uint256,bool,bool,address,bytes)".to_string(), ) }; + let permit2_is_active = permit2.is_some(); Ok(Self { permit2, selector, swap_encoder_registry, - native_address: chain.native_token()?, - wrapped_address: chain.wrapped_token()?, router_address, + transfer_optimization: TransferOptimization::new( + chain.native_token()?, + chain.wrapped_token()?, + permit2_is_active, + ), }) } @@ -80,8 +82,6 @@ impl SingleSwapStrategyEncoder { } } -impl TransferOptimization for SingleSwapStrategyEncoder {} - impl StrategyEncoder for SingleSwapStrategyEncoder { fn encode_strategy(&self, solution: Solution) -> Result<(Vec, Bytes), EncodingError> { let grouped_swaps = group_swaps(solution.clone().swaps); @@ -127,15 +127,9 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { let mut grouped_protocol_data: Vec = vec![]; for swap in grouped_swap.swaps.iter() { - let transfer_type = self.get_transfer_type( - swap.clone(), - solution.given_token.clone(), - self.native_address.clone(), - self.wrapped_address.clone(), - self.permit2.clone().is_some(), - wrap, - false, - ); + let transfer_type = self + .transfer_optimization + .get_transfer_type(swap.clone(), solution.given_token.clone(), wrap, false); let encoding_context = EncodingContext { receiver: swap_receiver.clone(), @@ -215,6 +209,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { /// * `router_address`: Address of the router to be used to execute swaps /// * `sequential_swap_validator`: SequentialSwapValidator, responsible for checking validity of /// sequential swap solutions +/// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers #[derive(Clone)] pub struct SequentialSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, @@ -224,10 +219,9 @@ pub struct SequentialSwapStrategyEncoder { native_address: Bytes, wrapped_address: Bytes, sequential_swap_validator: SequentialSwapValidator, + transfer_optimization: TransferOptimization, } -impl TransferOptimization for SequentialSwapStrategyEncoder {} - impl SequentialSwapStrategyEncoder { pub fn new( chain: Chain, @@ -244,6 +238,7 @@ impl SequentialSwapStrategyEncoder { .to_string(), ) }; + let permit2_is_active = permit2.is_some(); Ok(Self { permit2, selector, @@ -252,6 +247,11 @@ impl SequentialSwapStrategyEncoder { native_address: chain.native_token()?, wrapped_address: chain.wrapped_token()?, sequential_swap_validator: SequentialSwapValidator, + transfer_optimization: TransferOptimization::new( + chain.native_token()?, + chain.wrapped_token()?, + permit2_is_active, + ), }) } @@ -329,15 +329,14 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { let mut grouped_protocol_data: Vec = vec![]; for swap in grouped_swap.swaps.iter() { - let transfer_type = self.get_transfer_type( - swap.clone(), - solution.given_token.clone(), - self.native_address.clone(), - self.wrapped_address.clone(), - self.permit2.clone().is_some(), - wrap, - in_between_swap_optimization, - ); + let transfer_type = self + .transfer_optimization + .get_transfer_type( + swap.clone(), + solution.given_token.clone(), + wrap, + in_between_swap_optimization, + ); let encoding_context = EncodingContext { receiver: swap_receiver.clone(), @@ -422,6 +421,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { /// * `split_swap_validator`: SplitSwapValidator, responsible for checking validity of split swap /// solutions /// * `router_address`: Address of the router to be used to execute swaps +/// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers #[derive(Clone)] pub struct SplitSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, @@ -431,6 +431,7 @@ pub struct SplitSwapStrategyEncoder { wrapped_address: Bytes, split_swap_validator: SplitSwapValidator, router_address: Bytes, + transfer_optimization: TransferOptimization, } impl SplitSwapStrategyEncoder { @@ -449,7 +450,7 @@ impl SplitSwapStrategyEncoder { .to_string(), ) }; - + let permit2_is_active = permit2.is_some(); Ok(Self { permit2, selector, @@ -458,6 +459,11 @@ impl SplitSwapStrategyEncoder { wrapped_address: chain.wrapped_token()?, split_swap_validator: SplitSwapValidator, router_address, + transfer_optimization: TransferOptimization::new( + chain.native_token()?, + chain.wrapped_token()?, + permit2_is_active, + ), }) } @@ -481,8 +487,6 @@ impl SplitSwapStrategyEncoder { } } -impl TransferOptimization for SplitSwapStrategyEncoder {} - impl StrategyEncoder for SplitSwapStrategyEncoder { fn encode_strategy(&self, solution: Solution) -> Result<(Vec, Bytes), EncodingError> { self.split_swap_validator @@ -566,15 +570,9 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { let mut grouped_protocol_data: Vec = vec![]; for swap in grouped_swap.swaps.iter() { - let transfer_type = self.get_transfer_type( - swap.clone(), - solution.given_token.clone(), - self.native_address.clone(), - self.wrapped_address.clone(), - self.permit2.clone().is_some(), - wrap, - false, - ); + let transfer_type = self + .transfer_optimization + .get_transfer_type(swap.clone(), solution.given_token.clone(), wrap, false); let encoding_context = EncodingContext { receiver: swap_receiver.clone(), diff --git a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs index 97e08fc..2c19fec 100644 --- a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs +++ b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs @@ -5,17 +5,23 @@ use crate::encoding::{ models::{Swap, TransferType}, }; -/// A trait that defines how the tokens will be transferred into the given pool given the solution. -pub trait TransferOptimization { +/// A struct that defines how the tokens will be transferred into the given pool given the solution. +#[derive(Clone)] +pub struct TransferOptimization { + native_token: Bytes, + wrapped_token: Bytes, + permit2: bool, +} + +impl TransferOptimization { + pub fn new(native_token: Bytes, wrapped_token: Bytes, permit2: bool) -> Self { + TransferOptimization { native_token, wrapped_token, permit2 } + } /// Returns the transfer method that should be used for the given swap and solution. - #[allow(clippy::too_many_arguments)] - fn get_transfer_type( + pub fn get_transfer_type( &self, swap: Swap, given_token: Bytes, - native_token: Bytes, - wrapped_token: Bytes, - permit2: bool, wrap: bool, in_between_swap_optimization: bool, ) -> TransferType { @@ -24,22 +30,22 @@ pub trait TransferOptimization { let is_first_swap = swap.token_in == given_token; - if swap.token_in == native_token { + if swap.token_in == self.native_token { // Funds are already in router. All protocols currently take care of native transfers. TransferType::None - } else if (swap.token_in == wrapped_token) && wrap { + } else if (swap.token_in == self.wrapped_token) && wrap { // Wrapping already happened in the router so we can just use a normal transfer. TransferType::TransferToProtocol } else if is_first_swap { if in_transfer_required { - if permit2 { + if self.permit2 { // Transfer from swapper to pool using permit2. TransferType::TransferPermit2ToProtocol } else { // Transfer from swapper to pool. TransferType::TransferFromToProtocol } - } else if permit2 { + } else if self.permit2 { // Transfer from swapper to router using permit2. TransferType::TransferPermit2ToRouter } else { @@ -63,9 +69,6 @@ mod tests { use super::*; - struct MockStrategy {} - impl TransferOptimization for MockStrategy {} - fn weth() -> Bytes { Bytes::from(hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").to_vec()) } @@ -94,9 +97,8 @@ mod tests { token_out: dai(), split: 0f64, }; - let strategy = MockStrategy {}; - let transfer_method = - strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), true, false, false); + let optimization = TransferOptimization::new(eth(), weth(), true); + let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); assert_eq!(transfer_method, TransferType::TransferPermit2ToProtocol); } @@ -112,9 +114,8 @@ mod tests { token_out: dai(), split: 0f64, }; - let strategy = MockStrategy {}; - let transfer_method = - strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false, false); + let optimization = TransferOptimization::new(eth(), weth(), false); + let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); assert_eq!(transfer_method, TransferType::TransferFromToProtocol); } @@ -131,9 +132,8 @@ mod tests { token_out: dai(), split: 0f64, }; - let strategy = MockStrategy {}; - let transfer_method = - strategy.get_transfer_type(swap.clone(), eth(), eth(), weth(), false, false, false); + let optimization = TransferOptimization::new(eth(), weth(), false); + let transfer_method = optimization.get_transfer_type(swap.clone(), eth(), false, false); assert_eq!(transfer_method, TransferType::None); } @@ -150,9 +150,8 @@ mod tests { token_out: dai(), split: 0f64, }; - let strategy = MockStrategy {}; - let transfer_method = - strategy.get_transfer_type(swap.clone(), eth(), eth(), weth(), false, true, false); + let optimization = TransferOptimization::new(eth(), weth(), false); + let transfer_method = optimization.get_transfer_type(swap.clone(), eth(), true, false); assert_eq!(transfer_method, TransferType::TransferToProtocol); } @@ -169,9 +168,8 @@ mod tests { token_out: dai(), split: 0f64, }; - let strategy = MockStrategy {}; - let transfer_method = - strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false, false); + let optimization = TransferOptimization::new(eth(), weth(), false); + let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); assert_eq!(transfer_method, TransferType::TransferToProtocol); } @@ -188,9 +186,8 @@ mod tests { token_out: dai(), split: 0f64, }; - let strategy = MockStrategy {}; - let transfer_method = - strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false, false); + let optimization = TransferOptimization::new(eth(), weth(), false); + let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); assert_eq!(transfer_method, TransferType::None); } @@ -207,9 +204,8 @@ mod tests { token_out: dai(), split: 0f64, }; - let strategy = MockStrategy {}; - let transfer_method = - strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false, true); + let optimization = TransferOptimization::new(eth(), weth(), false); + let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, true); assert_eq!(transfer_method, TransferType::None); } } From 304740574b31b9af6ce84971e2fe912ee1627cd8 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Tue, 22 Apr 2025 12:41:59 +0100 Subject: [PATCH 13/22] chore: get_transfer_type should receive a SwapGroup and not a Swap Only the first swap of a SwapGroup would actually get a transfer in, so we don't need to do this inside of the swaps loop Rename in_token and out_token to token_in and token_out in SwapGroup --- don't change below this line --- ENG-4446 Took 22 minutes --- src/encoding/evm/group_swaps.rs | 42 ++++----- .../evm/strategy_encoder/strategy_encoders.rs | 89 +++++++++---------- .../transfer_optimizations.rs | 66 ++++++-------- 3 files changed, 91 insertions(+), 106 deletions(-) diff --git a/src/encoding/evm/group_swaps.rs b/src/encoding/evm/group_swaps.rs index 1409b9f..5ec5fe6 100644 --- a/src/encoding/evm/group_swaps.rs +++ b/src/encoding/evm/group_swaps.rs @@ -6,15 +6,15 @@ use crate::encoding::{evm::constants::GROUPABLE_PROTOCOLS, models::Swap}; /// optimization. /// /// # Fields -/// * `input_token`: Bytes, the input token of the first swap -/// * `output_token`: Bytes, the output token of the final swap +/// * `token_in`: Bytes, the input token of the first swap +/// * `token_out`: Bytes, the output token of the final swap /// * `protocol_system`: String, the protocol system of the swaps /// * `swaps`: Vec, the sequence of swaps to be executed as a group /// * `split`: f64, the split percentage of the first swap in the group #[derive(Clone, PartialEq, Debug)] pub struct SwapGroup { - pub input_token: Bytes, - pub output_token: Bytes, + pub token_in: Bytes, + pub token_out: Bytes, pub protocol_system: String, pub swaps: Vec, pub split: f64, @@ -44,7 +44,7 @@ pub fn group_swaps(swaps: Vec) -> Vec { if let Some(group) = current_group.as_mut() { group.swaps.push(swap.clone()); // Update the output token of the current group. - group.output_token = swap.token_out.clone(); + group.token_out = swap.token_out.clone(); } } else { // Not second or later USV4 pool. Push the current group (if it exists) and then @@ -53,8 +53,8 @@ pub fn group_swaps(swaps: Vec) -> Vec { grouped_swaps.push(group.clone()); } current_group = Some(SwapGroup { - input_token: swap.token_in.clone(), - output_token: swap.token_out.clone(), + token_in: swap.token_in.clone(), + token_out: swap.token_out.clone(), protocol_system: current_swap_protocol.clone(), swaps: vec![swap.clone()], split: swap.split, @@ -135,15 +135,15 @@ mod tests { vec![ SwapGroup { swaps: vec![swap_weth_wbtc, swap_wbtc_usdc], - input_token: weth, - output_token: usdc.clone(), + token_in: weth, + token_out: usdc.clone(), protocol_system: "uniswap_v4".to_string(), split: 0f64, }, SwapGroup { swaps: vec![swap_usdc_dai], - input_token: usdc, - output_token: dai, + token_in: usdc, + token_out: dai, protocol_system: "uniswap_v2".to_string(), split: 0f64, } @@ -216,22 +216,22 @@ mod tests { vec![ SwapGroup { swaps: vec![swap_wbtc_weth], - input_token: wbtc.clone(), - output_token: weth.clone(), + token_in: wbtc.clone(), + token_out: weth.clone(), protocol_system: "uniswap_v4".to_string(), split: 0f64, }, SwapGroup { swaps: vec![swap_weth_usdc], - input_token: weth.clone(), - output_token: usdc.clone(), + token_in: weth.clone(), + token_out: usdc.clone(), protocol_system: "uniswap_v4".to_string(), split: 0.5f64, }, SwapGroup { swaps: vec![swap_weth_dai, swap_dai_usdc], - input_token: weth, - output_token: usdc, + token_in: weth, + token_out: usdc, protocol_system: "uniswap_v4".to_string(), split: 0f64, } @@ -304,15 +304,15 @@ mod tests { vec![ SwapGroup { swaps: vec![swap_weth_wbtc, swap_wbtc_usdc], - input_token: weth.clone(), - output_token: usdc.clone(), + token_in: weth.clone(), + token_out: usdc.clone(), protocol_system: "vm:balancer_v3".to_string(), split: 0.5f64, }, SwapGroup { swaps: vec![swap_weth_dai, swap_dai_usdc], - input_token: weth, - output_token: usdc, + token_in: weth, + token_out: usdc, protocol_system: "uniswap_v4".to_string(), split: 0f64, } diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index bad91bf..1d473c1 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -125,20 +125,20 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { let swap_receiver = if !unwrap { solution.receiver.clone() } else { self.router_address.clone() }; + let transfer_type = self + .transfer_optimization + .get_transfer_type(grouped_swap.clone(), solution.given_token.clone(), wrap, false); + let encoding_context = EncodingContext { + receiver: swap_receiver.clone(), + exact_out: solution.exact_out, + router_address: Some(self.router_address.clone()), + group_token_in: grouped_swap.token_in.clone(), + group_token_out: grouped_swap.token_out.clone(), + transfer_type: transfer_type.clone(), + }; + let mut grouped_protocol_data: Vec = vec![]; for swap in grouped_swap.swaps.iter() { - let transfer_type = self - .transfer_optimization - .get_transfer_type(swap.clone(), solution.given_token.clone(), wrap, false); - - let encoding_context = EncodingContext { - receiver: swap_receiver.clone(), - exact_out: solution.exact_out, - router_address: Some(self.router_address.clone()), - group_token_in: grouped_swap.input_token.clone(), - group_token_out: grouped_swap.output_token.clone(), - transfer_type: transfer_type.clone(), - }; let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?; grouped_protocol_data.extend(protocol_data); } @@ -327,25 +327,25 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { solution.receiver.clone() // last swap - there is not next swap }; + let transfer_type = self + .transfer_optimization + .get_transfer_type( + grouped_swap.clone(), + solution.given_token.clone(), + wrap, + in_between_swap_optimization, + ); + let encoding_context = EncodingContext { + receiver: swap_receiver.clone(), + exact_out: solution.exact_out, + router_address: Some(self.router_address.clone()), + group_token_in: grouped_swap.token_in.clone(), + group_token_out: grouped_swap.token_out.clone(), + transfer_type: transfer_type.clone(), + }; + let mut grouped_protocol_data: Vec = vec![]; for swap in grouped_swap.swaps.iter() { - let transfer_type = self - .transfer_optimization - .get_transfer_type( - swap.clone(), - solution.given_token.clone(), - wrap, - in_between_swap_optimization, - ); - - let encoding_context = EncodingContext { - receiver: swap_receiver.clone(), - exact_out: solution.exact_out, - router_address: Some(self.router_address.clone()), - group_token_in: grouped_swap.input_token.clone(), - group_token_out: grouped_swap.output_token.clone(), - transfer_type: transfer_type.clone(), - }; let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?; grouped_protocol_data.extend(protocol_data); @@ -517,7 +517,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { let intermediary_tokens: HashSet = grouped_swaps .iter() .flat_map(|grouped_swap| { - vec![grouped_swap.input_token.clone(), grouped_swap.output_token.clone()] + vec![grouped_swap.token_in.clone(), grouped_swap.token_out.clone()] }) .collect(); let mut intermediary_tokens: Vec = intermediary_tokens @@ -562,34 +562,33 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { )) })?; - let swap_receiver = if !unwrap && grouped_swap.output_token == solution.checked_token { + let swap_receiver = if !unwrap && grouped_swap.token_out == solution.checked_token { solution.receiver.clone() } else { self.router_address.clone() }; + let transfer_type = self + .transfer_optimization + .get_transfer_type(grouped_swap.clone(), solution.given_token.clone(), wrap, false); + let encoding_context = EncodingContext { + receiver: swap_receiver.clone(), + exact_out: solution.exact_out, + router_address: Some(self.router_address.clone()), + group_token_in: grouped_swap.token_in.clone(), + group_token_out: grouped_swap.token_out.clone(), + transfer_type: transfer_type.clone(), + }; let mut grouped_protocol_data: Vec = vec![]; for swap in grouped_swap.swaps.iter() { - let transfer_type = self - .transfer_optimization - .get_transfer_type(swap.clone(), solution.given_token.clone(), wrap, false); - - let encoding_context = EncodingContext { - receiver: swap_receiver.clone(), - exact_out: solution.exact_out, - router_address: Some(self.router_address.clone()), - group_token_in: grouped_swap.input_token.clone(), - group_token_out: grouped_swap.output_token.clone(), - transfer_type: transfer_type.clone(), - }; let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?; grouped_protocol_data.extend(protocol_data); } let swap_data = self.encode_swap_header( - get_token_position(tokens.clone(), grouped_swap.input_token.clone())?, - get_token_position(tokens.clone(), grouped_swap.output_token.clone())?, + get_token_position(tokens.clone(), grouped_swap.token_in.clone())?, + get_token_position(tokens.clone(), grouped_swap.token_out.clone())?, percentage_to_uint24(grouped_swap.split), Bytes::from_str(swap_encoder.executor_address()).map_err(|_| { EncodingError::FatalError("Invalid executor address".to_string()) diff --git a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs index 2c19fec..3d0b8a1 100644 --- a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs +++ b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs @@ -1,8 +1,8 @@ use tycho_common::Bytes; use crate::encoding::{ - evm::constants::IN_TRANSFER_REQUIRED_PROTOCOLS, - models::{Swap, TransferType}, + evm::{constants::IN_TRANSFER_REQUIRED_PROTOCOLS, group_swaps::SwapGroup}, + models::TransferType, }; /// A struct that defines how the tokens will be transferred into the given pool given the solution. @@ -20,13 +20,13 @@ impl TransferOptimization { /// Returns the transfer method that should be used for the given swap and solution. pub fn get_transfer_type( &self, - swap: Swap, + swap: SwapGroup, given_token: Bytes, wrap: bool, in_between_swap_optimization: bool, ) -> TransferType { let in_transfer_required: bool = - IN_TRANSFER_REQUIRED_PROTOCOLS.contains(&swap.component.protocol_system.as_str()); + IN_TRANSFER_REQUIRED_PROTOCOLS.contains(&swap.protocol_system.as_str()); let is_first_swap = swap.token_in == given_token; @@ -65,7 +65,7 @@ impl TransferOptimization { #[cfg(test)] mod tests { use alloy_primitives::hex; - use tycho_common::{models::protocol::ProtocolComponent, Bytes}; + use tycho_common::Bytes; use super::*; @@ -88,14 +88,12 @@ mod tests { #[test] fn test_first_swap_transfer_from_permit2() { // The swap token is the same as the given token, which is not the native token - let swap = Swap { - component: ProtocolComponent { - protocol_system: "uniswap_v2".to_string(), - ..Default::default() - }, + let swap = SwapGroup { + protocol_system: "uniswap_v2".to_string(), token_in: weth(), token_out: dai(), split: 0f64, + swaps: vec![], }; let optimization = TransferOptimization::new(eth(), weth(), true); let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); @@ -105,14 +103,12 @@ mod tests { #[test] fn test_first_swap_transfer_from() { // The swap token is the same as the given token, which is not the native token - let swap = Swap { - component: ProtocolComponent { - protocol_system: "uniswap_v2".to_string(), - ..Default::default() - }, + let swap = SwapGroup { + protocol_system: "uniswap_v2".to_string(), token_in: weth(), token_out: dai(), split: 0f64, + swaps: vec![], }; let optimization = TransferOptimization::new(eth(), weth(), false); let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); @@ -123,14 +119,12 @@ mod tests { fn test_first_swap_native() { // The swap token is the same as the given token, and it's the native token. // No transfer action is needed. - let swap = Swap { - component: ProtocolComponent { - protocol_system: "uniswap_v2".to_string(), - ..Default::default() - }, + let swap = SwapGroup { + protocol_system: "uniswap_v2".to_string(), token_in: eth(), token_out: dai(), split: 0f64, + swaps: vec![], }; let optimization = TransferOptimization::new(eth(), weth(), false); let transfer_method = optimization.get_transfer_type(swap.clone(), eth(), false, false); @@ -141,14 +135,12 @@ mod tests { fn test_first_swap_wrapped() { // The swap token is NOT the same as the given token, but we are wrapping. // Since the swap's token in is the wrapped token - this is the first swap. - let swap = Swap { - component: ProtocolComponent { - protocol_system: "uniswap_v2".to_string(), - ..Default::default() - }, + let swap = SwapGroup { + protocol_system: "uniswap_v2".to_string(), token_in: weth(), token_out: dai(), split: 0f64, + swaps: vec![], }; let optimization = TransferOptimization::new(eth(), weth(), false); let transfer_method = optimization.get_transfer_type(swap.clone(), eth(), true, false); @@ -159,14 +151,12 @@ mod tests { fn test_not_first_swap() { // The swap token is NOT the same as the given token, and we are NOT wrapping. // Thus, this is not the first swap. - let swap = Swap { - component: ProtocolComponent { - protocol_system: "uniswap_v2".to_string(), - ..Default::default() - }, + let swap = SwapGroup { + protocol_system: "uniswap_v2".to_string(), token_in: usdc(), token_out: dai(), split: 0f64, + swaps: vec![], }; let optimization = TransferOptimization::new(eth(), weth(), false); let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); @@ -177,14 +167,12 @@ mod tests { fn test_not_first_swap_funds_in_router() { // Not the first swap and the protocol requires the funds to be in the router (which they // already are, so the transfer type is None) - let swap = Swap { - component: ProtocolComponent { - protocol_system: "vm:curve".to_string(), - ..Default::default() - }, + let swap = SwapGroup { + protocol_system: "vm:curve".to_string(), token_in: usdc(), token_out: dai(), split: 0f64, + swaps: vec![], }; let optimization = TransferOptimization::new(eth(), weth(), false); let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); @@ -195,14 +183,12 @@ mod tests { fn test_not_first_swap_in_between_swap_optimization() { // Not the first swap and the in between swaps are optimized. The funds should already be in // the next pool or in the router - let swap = Swap { - component: ProtocolComponent { - protocol_system: "uniswap_v2".to_string(), - ..Default::default() - }, + let swap = SwapGroup { + protocol_system: "uniswap_v2".to_string(), token_in: usdc(), token_out: dai(), split: 0f64, + swaps: vec![], }; let optimization = TransferOptimization::new(eth(), weth(), false); let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, true); From df92be887573b297b22f2e01317305f2e5bb7e75 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Tue, 22 Apr 2025 14:01:03 +0100 Subject: [PATCH 14/22] feat: Allow for token_in_already_in_router --- don't change below this line --- ENG-4446 Took 31 minutes Took 26 seconds --- src/encoding/evm/encoder_builders.rs | 14 ++++ .../evm/strategy_encoder/strategy_encoders.rs | 26 +++++++ .../transfer_optimizations.rs | 78 +++++++++++++++---- src/encoding/evm/tycho_encoders.rs | 9 ++- 4 files changed, 109 insertions(+), 18 deletions(-) diff --git a/src/encoding/evm/encoder_builders.rs b/src/encoding/evm/encoder_builders.rs index 94929a7..ee412ac 100644 --- a/src/encoding/evm/encoder_builders.rs +++ b/src/encoding/evm/encoder_builders.rs @@ -21,6 +21,7 @@ pub struct TychoRouterEncoderBuilder { chain: Option, executors_file_path: Option, router_address: Option, + token_in_already_in_router: Option, } impl Default for TychoRouterEncoderBuilder { @@ -36,6 +37,7 @@ impl TychoRouterEncoderBuilder { chain: None, executors_file_path: None, router_address: None, + token_in_already_in_router: None, } } pub fn chain(mut self, chain: TychoCommonChain) -> Self { @@ -62,6 +64,16 @@ impl TychoRouterEncoderBuilder { self } + // Sets the `token_in_already_in_router` flag. + // If set to true, the encoder will assume that the token in is already in the router. + // WARNING: this is an advanced feature and should be used with caution. Make sure you have + // checks to make sure that your tokens won't be lost. The Router is not considered safe to hold + // tokens, so if this is not done within the same transaction you will lose your tokens. + pub fn token_in_already_in_router(mut self, token_in_already_in_router: bool) -> Self { + self.token_in_already_in_router = Some(token_in_already_in_router); + self + } + /// Builds the `TychoRouterEncoder` instance using the configured chain. /// Returns an error if either the chain has not been set. pub fn build(self) -> Result, EncodingError> { @@ -88,6 +100,8 @@ impl TychoRouterEncoderBuilder { swap_encoder_registry, self.swapper_pk, tycho_router_address, + self.token_in_already_in_router + .unwrap_or(false), )?)) } else { Err(EncodingError::FatalError( diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 1d473c1..81e9988 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -49,6 +49,7 @@ impl SingleSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, swapper_pk: Option, router_address: Bytes, + token_in_already_in_router: bool, ) -> Result { let (permit2, selector) = if let Some(swapper_pk) = swapper_pk { (Some(Permit2::new(swapper_pk, chain.clone())?), "singleSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string()) @@ -68,6 +69,7 @@ impl SingleSwapStrategyEncoder { chain.native_token()?, chain.wrapped_token()?, permit2_is_active, + token_in_already_in_router, ), }) } @@ -228,6 +230,7 @@ impl SequentialSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, swapper_pk: Option, router_address: Bytes, + token_in_already_in_router: bool, ) -> Result { let (permit2, selector) = if let Some(swapper_pk) = swapper_pk { (Some(Permit2::new(swapper_pk, chain.clone())?), "sequentialSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string()) @@ -251,6 +254,7 @@ impl SequentialSwapStrategyEncoder { chain.native_token()?, chain.wrapped_token()?, permit2_is_active, + token_in_already_in_router, ), }) } @@ -440,6 +444,7 @@ impl SplitSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, swapper_pk: Option, router_address: Bytes, + token_in_already_in_router: bool, ) -> Result { let (permit2, selector) = if let Some(swapper_pk) = swapper_pk { (Some(Permit2::new(swapper_pk, chain.clone())?), "splitSwapPermit2(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string()) @@ -463,6 +468,7 @@ impl SplitSwapStrategyEncoder { chain.native_token()?, chain.wrapped_token()?, permit2_is_active, + token_in_already_in_router, ), }) } @@ -741,6 +747,7 @@ mod tests { swap_encoder_registry, Some(private_key), Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, ) .unwrap(); let solution = Solution { @@ -824,6 +831,7 @@ mod tests { swap_encoder_registry, None, Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, ) .unwrap(); let solution = Solution { @@ -901,6 +909,7 @@ mod tests { swap_encoder_registry, Some(private_key), Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + false, ) .unwrap(); let solution = Solution { @@ -953,6 +962,7 @@ mod tests { swap_encoder_registry, Some(private_key), Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + false, ) .unwrap(); let solution = Solution { @@ -1024,6 +1034,7 @@ mod tests { swap_encoder_registry, Some(private_key), Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, ) .unwrap(); let solution = Solution { @@ -1083,6 +1094,7 @@ mod tests { swap_encoder_registry, None, Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, ) .unwrap(); let solution = Solution { @@ -1201,6 +1213,7 @@ mod tests { swap_encoder_registry, Some(private_key), Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + false, ) .unwrap(); @@ -1318,6 +1331,7 @@ mod tests { swap_encoder_registry, None, Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, ) .unwrap(); let solution = Solution { @@ -1399,6 +1413,7 @@ mod tests { swap_encoder_registry, None, Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, ) .unwrap(); let solution = Solution { @@ -1484,6 +1499,7 @@ mod tests { swap_encoder_registry, None, Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, ) .unwrap(); let solution = Solution { @@ -1550,6 +1566,7 @@ mod tests { swap_encoder_registry, None, Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, ) .unwrap(); let solution = Solution { @@ -1649,6 +1666,7 @@ mod tests { swap_encoder_registry, Some(private_key), Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + false, ) .unwrap(); let solution = Solution { @@ -1758,6 +1776,7 @@ mod tests { swap_encoder_registry, Some(private_key.clone()), Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + false, ) .unwrap(); @@ -1920,6 +1939,7 @@ mod tests { swap_encoder_registry, Some(private_key.clone()), Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + false, ) .unwrap(); @@ -2045,6 +2065,7 @@ mod tests { swap_encoder_registry, None, Bytes::from_str("0xA4AD4f68d0b91CFD19687c881e50f3A00242828c").unwrap(), + false, ) .unwrap(); @@ -2110,6 +2131,7 @@ mod tests { swap_encoder_registry, Some(private_key), Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + false, ) .unwrap(); @@ -2178,6 +2200,7 @@ mod tests { swap_encoder_registry, Some(private_key), Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + false, ) .unwrap(); @@ -2265,6 +2288,7 @@ mod tests { swap_encoder_registry, Some(private_key), Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + false, ) .unwrap(); let solution = Solution { @@ -2367,6 +2391,7 @@ mod tests { swap_encoder_registry, None, Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, ) .unwrap(); @@ -2429,6 +2454,7 @@ mod tests { swap_encoder_registry, None, Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, ) .unwrap(); diff --git a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs index 3d0b8a1..4b4586f 100644 --- a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs +++ b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs @@ -11,11 +11,17 @@ pub struct TransferOptimization { native_token: Bytes, wrapped_token: Bytes, permit2: bool, + token_in_already_in_router: bool, } impl TransferOptimization { - pub fn new(native_token: Bytes, wrapped_token: Bytes, permit2: bool) -> Self { - TransferOptimization { native_token, wrapped_token, permit2 } + pub fn new( + native_token: Bytes, + wrapped_token: Bytes, + permit2: bool, + token_in_already_in_router: bool, + ) -> Self { + TransferOptimization { native_token, wrapped_token, permit2, token_in_already_in_router } } /// Returns the transfer method that should be used for the given swap and solution. pub fn get_transfer_type( @@ -38,19 +44,28 @@ impl TransferOptimization { TransferType::TransferToProtocol } else if is_first_swap { if in_transfer_required { - if self.permit2 { + if self.token_in_already_in_router { + // Transfer from router to pool. + TransferType::TransferToProtocol + } else if self.permit2 { // Transfer from swapper to pool using permit2. TransferType::TransferPermit2ToProtocol } else { // Transfer from swapper to pool. TransferType::TransferFromToProtocol } - } else if self.permit2 { - // Transfer from swapper to router using permit2. - TransferType::TransferPermit2ToRouter + // in transfer is not necessary for these protocols. Only make a transfer if the + // tokens are not already in the router + } else if !self.token_in_already_in_router { + if self.permit2 { + // Transfer from swapper to router using permit2. + TransferType::TransferPermit2ToRouter + } else { + // Transfer from swapper to router. + TransferType::TransferFromToRouter + } } else { - // Transfer from swapper to router. - TransferType::TransferFromToRouter + TransferType::None } // all other swaps } else if !in_transfer_required || in_between_swap_optimization { @@ -65,7 +80,6 @@ impl TransferOptimization { #[cfg(test)] mod tests { use alloy_primitives::hex; - use tycho_common::Bytes; use super::*; @@ -95,7 +109,7 @@ mod tests { split: 0f64, swaps: vec![], }; - let optimization = TransferOptimization::new(eth(), weth(), true); + let optimization = TransferOptimization::new(eth(), weth(), true, false); let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); assert_eq!(transfer_method, TransferType::TransferPermit2ToProtocol); } @@ -110,7 +124,7 @@ mod tests { split: 0f64, swaps: vec![], }; - let optimization = TransferOptimization::new(eth(), weth(), false); + let optimization = TransferOptimization::new(eth(), weth(), false, false); let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); assert_eq!(transfer_method, TransferType::TransferFromToProtocol); } @@ -126,7 +140,7 @@ mod tests { split: 0f64, swaps: vec![], }; - let optimization = TransferOptimization::new(eth(), weth(), false); + let optimization = TransferOptimization::new(eth(), weth(), false, false); let transfer_method = optimization.get_transfer_type(swap.clone(), eth(), false, false); assert_eq!(transfer_method, TransferType::None); } @@ -142,7 +156,7 @@ mod tests { split: 0f64, swaps: vec![], }; - let optimization = TransferOptimization::new(eth(), weth(), false); + let optimization = TransferOptimization::new(eth(), weth(), false, false); let transfer_method = optimization.get_transfer_type(swap.clone(), eth(), true, false); assert_eq!(transfer_method, TransferType::TransferToProtocol); } @@ -158,7 +172,7 @@ mod tests { split: 0f64, swaps: vec![], }; - let optimization = TransferOptimization::new(eth(), weth(), false); + let optimization = TransferOptimization::new(eth(), weth(), false, false); let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); assert_eq!(transfer_method, TransferType::TransferToProtocol); } @@ -174,7 +188,7 @@ mod tests { split: 0f64, swaps: vec![], }; - let optimization = TransferOptimization::new(eth(), weth(), false); + let optimization = TransferOptimization::new(eth(), weth(), false, false); let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); assert_eq!(transfer_method, TransferType::None); } @@ -190,8 +204,40 @@ mod tests { split: 0f64, swaps: vec![], }; - let optimization = TransferOptimization::new(eth(), weth(), false); + let optimization = TransferOptimization::new(eth(), weth(), false, false); let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, true); assert_eq!(transfer_method, TransferType::None); } + + #[test] + fn test_first_swap_tokens_already_in_router_optimization() { + // It is the first swap, tokens are already in the router and the protocol requires the + // transfer in + let swap = SwapGroup { + protocol_system: "uniswap_v2".to_string(), + token_in: usdc(), + token_out: dai(), + split: 0f64, + swaps: vec![], + }; + let optimization = TransferOptimization::new(eth(), weth(), false, true); + let transfer_method = optimization.get_transfer_type(swap.clone(), usdc(), false, false); + assert_eq!(transfer_method, TransferType::TransferToProtocol); + } + + #[test] + fn test_first_swap_tokens_already_in_router_no_transfer_needed_optimization() { + // It is the first swap, tokens are already in the router and the protocol does not require + // the transfer in + let swap = SwapGroup { + protocol_system: "vm:curve".to_string(), + token_in: usdc(), + token_out: dai(), + split: 0f64, + swaps: vec![], + }; + let optimization = TransferOptimization::new(eth(), weth(), false, true); + let transfer_method = optimization.get_transfer_type(swap.clone(), usdc(), false, false); + assert_eq!(transfer_method, TransferType::None); + } } diff --git a/src/encoding/evm/tycho_encoders.rs b/src/encoding/evm/tycho_encoders.rs index 5d285e0..a4bbc7a 100644 --- a/src/encoding/evm/tycho_encoders.rs +++ b/src/encoding/evm/tycho_encoders.rs @@ -39,6 +39,7 @@ impl TychoRouterEncoder { swap_encoder_registry: SwapEncoderRegistry, swapper_pk: Option, router_address: Bytes, + token_in_already_in_router: bool, ) -> Result { let native_address = chain.native_token()?; let wrapped_address = chain.wrapped_token()?; @@ -48,18 +49,21 @@ impl TychoRouterEncoder { swap_encoder_registry.clone(), swapper_pk.clone(), router_address.clone(), + token_in_already_in_router, )?, sequential_swap_strategy: SequentialSwapStrategyEncoder::new( chain.clone(), swap_encoder_registry.clone(), swapper_pk.clone(), router_address.clone(), + token_in_already_in_router, )?, split_swap_strategy: SplitSwapStrategyEncoder::new( chain, swap_encoder_registry, None, router_address.clone(), + token_in_already_in_router, )?, native_address, wrapped_address, @@ -258,8 +262,8 @@ impl TychoExecutorEncoder { receiver: receiver.clone(), exact_out: solution.exact_out, router_address: None, - group_token_in: grouped_swap.input_token.clone(), - group_token_out: grouped_swap.output_token.clone(), + group_token_in: grouped_swap.token_in.clone(), + group_token_out: grouped_swap.token_out.clone(), transfer_type: TransferType::TransferToProtocol, }; let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?; @@ -354,6 +358,7 @@ mod tests { get_swap_encoder_registry(), None, Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, ) .unwrap() } From cebacc68fea9a8dbfcc9d42b81b813fb61c5acdc Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Tue, 22 Apr 2025 14:15:39 +0100 Subject: [PATCH 15/22] test: Add integration test for it --- don't change below this line --- ENG-4446 Took 12 minutes --- foundry/test/TychoRouterSingleSwap.t.sol | 18 +++++ .../evm/strategy_encoder/strategy_encoders.rs | 79 +++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/foundry/test/TychoRouterSingleSwap.t.sol b/foundry/test/TychoRouterSingleSwap.t.sol index 1a374c7..fba1f15 100644 --- a/foundry/test/TychoRouterSingleSwap.t.sol +++ b/foundry/test/TychoRouterSingleSwap.t.sol @@ -367,4 +367,22 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { assertTrue(success, "Call Failed"); assertEq(balanceAfter - balanceBefore, 1120007305574805922); } + + function testSingleSwapIntegrationNoTransferIn() public { + // Tests swapping WETH -> DAI on a USV2 pool assuming that the tokens are already inside the router + deal(WETH_ADDR, tychoRouterAddr, 1 ether); + uint256 balanceBefore = IERC20(DAI_ADDR).balanceOf(ALICE); + + vm.startPrank(ALICE); + // Encoded solution generated using `test_single_swap_strategy_encoder_no_transfer_in` + (bool success,) = tychoRouterAddr.call( + hex"20144a070000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae3740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000" + ); + + vm.stopPrank(); + + uint256 balanceAfter = IERC20(DAI_ADDR).balanceOf(ALICE); + assertTrue(success, "Call Failed"); + assertEq(balanceAfter - balanceBefore, 2659881924818443699787); + } } diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 81e9988..dd1433a 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -881,6 +881,85 @@ mod tests { println!("test_single_swap_strategy_encoder_no_permit2: {}", hex_calldata); } + #[test] + fn test_single_swap_strategy_encoder_no_transfer_in() { + // Performs a single swap from WETH to DAI on a USV2 pool assuming that the tokens are + // already in the router + + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); + let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); + + let expected_amount = Some(BigUint::from_str("2_650_000000000000000000").unwrap()); + let slippage = Some(0.01f64); + let checked_amount = Some(BigUint::from_str("2_640_000000000000000000").unwrap()); + let expected_min_amount = U256::from_str("2_640_000000000000000000").unwrap(); + + let swap = Swap { + component: ProtocolComponent { + id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0f64, + }; + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = SingleSwapStrategyEncoder::new( + eth_chain(), + swap_encoder_registry, + None, + Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + true, + ) + .unwrap(); + let solution = Solution { + exact_out: false, + given_token: weth, + given_amount: BigUint::from_str("1_000000000000000000").unwrap(), + checked_token: dai, + expected_amount, + slippage, + checked_amount, + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + swaps: vec![swap], + ..Default::default() + }; + + let (calldata, _) = encoder + .encode_strategy(solution) + .unwrap(); + let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); + let expected_input = [ + "20144a07", // Function selector + "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in + "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in + "0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out + &expected_min_amount_encoded, // min amount out + "0000000000000000000000000000000000000000000000000000000000000000", // wrap + "0000000000000000000000000000000000000000000000000000000000000000", // unwrap + "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver + "0000000000000000000000000000000000000000000000000000000000000100", // offset of swap bytes + "0000000000000000000000000000000000000000000000000000000000000052", // length of swap bytes without padding + + // Swap data + "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in + "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id + "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver + "00", // zero2one + "00", // transfer type + "0000000000000000000000000000", // padding + ] + .join(""); + + let hex_calldata = encode(&calldata); + + assert_eq!(hex_calldata, expected_input); + println!("test_single_swap_strategy_encoder_no_transfer_in: {}", hex_calldata); + } + #[test] fn test_single_swap_strategy_encoder_wrap() { // Performs a single swap from WETH to DAI on a USV2 pool, wrapping ETH From dff4a345fcfb36c1c9b87805cadc02701d2d63bb Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Tue, 22 Apr 2025 15:30:33 +0100 Subject: [PATCH 16/22] chore: Move get_receiver logic inside TransferOptimization Added tests for all cases --- don't change below this line --- ENG-4446 Took 1 hour 8 minutes --- .../evm/strategy_encoder/strategy_encoders.rs | 42 ++---- .../transfer_optimizations.rs | 131 ++++++++++++++++-- 2 files changed, 133 insertions(+), 40 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index dd1433a..af757c8 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -8,7 +8,6 @@ use crate::encoding::{ errors::EncodingError, evm::{ approvals::permit2::Permit2, - constants::{CALLBACK_CONSTRAINED_PROTOCOLS, IN_TRANSFER_REQUIRED_PROTOCOLS}, group_swaps::group_swaps, strategy_encoder::{ strategy_validators::{SequentialSwapValidator, SplitSwapValidator, SwapValidator}, @@ -64,12 +63,13 @@ impl SingleSwapStrategyEncoder { permit2, selector, swap_encoder_registry, - router_address, + router_address: router_address.clone(), transfer_optimization: TransferOptimization::new( chain.native_token()?, chain.wrapped_token()?, permit2_is_active, token_in_already_in_router, + router_address, ), }) } @@ -246,7 +246,7 @@ impl SequentialSwapStrategyEncoder { permit2, selector, swap_encoder_registry, - router_address, + router_address: router_address.clone(), native_address: chain.native_token()?, wrapped_address: chain.wrapped_token()?, sequential_swap_validator: SequentialSwapValidator, @@ -255,6 +255,7 @@ impl SequentialSwapStrategyEncoder { chain.wrapped_token()?, permit2_is_active, token_in_already_in_router, + router_address, ), }) } @@ -295,7 +296,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { } let mut swaps = vec![]; - let mut next_in_between_swap_optimization = true; + let mut next_in_between_swap_optimization_allowed = true; for (i, grouped_swap) in grouped_swaps.iter().enumerate() { let protocol = grouped_swap.protocol_system.clone(); let swap_encoder = self @@ -307,37 +308,19 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { )) })?; - let in_between_swap_optimization = next_in_between_swap_optimization; + let in_between_swap_optimization_allowed = next_in_between_swap_optimization_allowed; let next_swap = grouped_swaps.get(i + 1); - // if there is a next swap - let swap_receiver = if let Some(next) = next_swap { - // if the protocol of the next swap supports transfer in optimization - if IN_TRANSFER_REQUIRED_PROTOCOLS.contains(&next.protocol_system.as_str()) { - // if the protocol does not allow for chained swaps, we can't optimize the - // receiver of this swap nor the transfer in of the next swap - if CALLBACK_CONSTRAINED_PROTOCOLS.contains(&next.protocol_system.as_str()) { - next_in_between_swap_optimization = false; - self.router_address.clone() - } else { - Bytes::from_str(&next.swaps[0].component.id.clone()).map_err(|_| { - EncodingError::FatalError("Invalid component id".to_string()) - })? - } - } else { - // the protocol of the next swap does not support transfer in optimization - self.router_address.clone() - } - } else { - solution.receiver.clone() // last swap - there is not next swap - }; - + let (swap_receiver, next_swap_optimization) = self + .transfer_optimization + .get_receiver(solution.receiver.clone(), next_swap)?; + next_in_between_swap_optimization_allowed = next_swap_optimization; let transfer_type = self .transfer_optimization .get_transfer_type( grouped_swap.clone(), solution.given_token.clone(), wrap, - in_between_swap_optimization, + in_between_swap_optimization_allowed, ); let encoding_context = EncodingContext { receiver: swap_receiver.clone(), @@ -463,12 +446,13 @@ impl SplitSwapStrategyEncoder { native_address: chain.native_token()?, wrapped_address: chain.wrapped_token()?, split_swap_validator: SplitSwapValidator, - router_address, + router_address: router_address.clone(), transfer_optimization: TransferOptimization::new( chain.native_token()?, chain.wrapped_token()?, permit2_is_active, token_in_already_in_router, + router_address, ), }) } diff --git a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs index 4b4586f..62bfca1 100644 --- a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs +++ b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs @@ -1,7 +1,13 @@ +use std::str::FromStr; + use tycho_common::Bytes; use crate::encoding::{ - evm::{constants::IN_TRANSFER_REQUIRED_PROTOCOLS, group_swaps::SwapGroup}, + errors::EncodingError, + evm::{ + constants::{CALLBACK_CONSTRAINED_PROTOCOLS, IN_TRANSFER_REQUIRED_PROTOCOLS}, + group_swaps::SwapGroup, + }, models::TransferType, }; @@ -12,6 +18,7 @@ pub struct TransferOptimization { wrapped_token: Bytes, permit2: bool, token_in_already_in_router: bool, + router_address: Bytes, } impl TransferOptimization { @@ -20,9 +27,17 @@ impl TransferOptimization { wrapped_token: Bytes, permit2: bool, token_in_already_in_router: bool, + router_address: Bytes, ) -> Self { - TransferOptimization { native_token, wrapped_token, permit2, token_in_already_in_router } + TransferOptimization { + native_token, + wrapped_token, + permit2, + token_in_already_in_router, + router_address, + } } + /// Returns the transfer method that should be used for the given swap and solution. pub fn get_transfer_type( &self, @@ -75,13 +90,50 @@ impl TransferOptimization { TransferType::TransferToProtocol } } + + // Returns the optimized receiver of the swap. This is used to chain swaps together and avoid + // unnecessary token transfers. + // Returns the receiver address and a boolean indicating whether the receiver is optimized (this + // is necessary for the next swap transfer type decision). + pub fn get_receiver( + &self, + solution_receiver: Bytes, + next_swap: Option<&SwapGroup>, + ) -> Result<(Bytes, bool), EncodingError> { + if let Some(next) = next_swap { + // if the protocol of the next swap supports transfer in optimization + if IN_TRANSFER_REQUIRED_PROTOCOLS.contains(&next.protocol_system.as_str()) { + // if the protocol does not allow for chained swaps, we can't optimize the + // receiver of this swap nor the transfer in of the next swap + if CALLBACK_CONSTRAINED_PROTOCOLS.contains(&next.protocol_system.as_str()) { + Ok((self.router_address.clone(), false)) + } else { + Ok(( + Bytes::from_str(&next.swaps[0].component.id.clone()).map_err(|_| { + EncodingError::FatalError("Invalid component id".to_string()) + })?, + true, + )) + } + } else { + // the protocol of the next swap does not support transfer in optimization + Ok((self.router_address.clone(), false)) + } + } else { + // last swap - there is no next swap + Ok((solution_receiver, false)) + } + } } #[cfg(test)] mod tests { use alloy_primitives::hex; + use rstest::rstest; + use tycho_common::models::protocol::ProtocolComponent; use super::*; + use crate::encoding::models::Swap; fn weth() -> Bytes { Bytes::from(hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").to_vec()) @@ -99,6 +151,10 @@ mod tests { Bytes::from(hex!("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").to_vec()) } + fn router_address() -> Bytes { + Bytes::from("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f") + } + #[test] fn test_first_swap_transfer_from_permit2() { // The swap token is the same as the given token, which is not the native token @@ -109,7 +165,7 @@ mod tests { split: 0f64, swaps: vec![], }; - let optimization = TransferOptimization::new(eth(), weth(), true, false); + let optimization = TransferOptimization::new(eth(), weth(), true, false, router_address()); let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); assert_eq!(transfer_method, TransferType::TransferPermit2ToProtocol); } @@ -124,7 +180,7 @@ mod tests { split: 0f64, swaps: vec![], }; - let optimization = TransferOptimization::new(eth(), weth(), false, false); + let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address()); let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); assert_eq!(transfer_method, TransferType::TransferFromToProtocol); } @@ -140,7 +196,7 @@ mod tests { split: 0f64, swaps: vec![], }; - let optimization = TransferOptimization::new(eth(), weth(), false, false); + let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address()); let transfer_method = optimization.get_transfer_type(swap.clone(), eth(), false, false); assert_eq!(transfer_method, TransferType::None); } @@ -156,7 +212,7 @@ mod tests { split: 0f64, swaps: vec![], }; - let optimization = TransferOptimization::new(eth(), weth(), false, false); + let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address()); let transfer_method = optimization.get_transfer_type(swap.clone(), eth(), true, false); assert_eq!(transfer_method, TransferType::TransferToProtocol); } @@ -172,7 +228,7 @@ mod tests { split: 0f64, swaps: vec![], }; - let optimization = TransferOptimization::new(eth(), weth(), false, false); + let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address()); let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); assert_eq!(transfer_method, TransferType::TransferToProtocol); } @@ -188,7 +244,7 @@ mod tests { split: 0f64, swaps: vec![], }; - let optimization = TransferOptimization::new(eth(), weth(), false, false); + let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address()); let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); assert_eq!(transfer_method, TransferType::None); } @@ -204,7 +260,7 @@ mod tests { split: 0f64, swaps: vec![], }; - let optimization = TransferOptimization::new(eth(), weth(), false, false); + let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address()); let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, true); assert_eq!(transfer_method, TransferType::None); } @@ -220,7 +276,7 @@ mod tests { split: 0f64, swaps: vec![], }; - let optimization = TransferOptimization::new(eth(), weth(), false, true); + let optimization = TransferOptimization::new(eth(), weth(), false, true, router_address()); let transfer_method = optimization.get_transfer_type(swap.clone(), usdc(), false, false); assert_eq!(transfer_method, TransferType::TransferToProtocol); } @@ -236,8 +292,61 @@ mod tests { split: 0f64, swaps: vec![], }; - let optimization = TransferOptimization::new(eth(), weth(), false, true); + let optimization = TransferOptimization::new(eth(), weth(), false, true, router_address()); let transfer_method = optimization.get_transfer_type(swap.clone(), usdc(), false, false); assert_eq!(transfer_method, TransferType::None); } + + fn receiver() -> Bytes { + Bytes::from("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2") + } + + fn component_id() -> Bytes { + Bytes::from("0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11") + } + + #[rstest] + // there is no next swap -> receiver is the solution receiver + #[case(None, receiver(), false)] + // protocol of next swap supports transfer in optimization + #[case(Some("uniswap_v2"), component_id(), true)] + // protocol of next swap supports transfer in optimization but is callback constrained + #[case(Some("uniswap_v3"), router_address(), false)] + // protocol of next swap does not support transfer in optimization + #[case(Some("vm:curve"), router_address(), false)] + fn test_get_receiver( + #[case] protocol: Option<&str>, + #[case] expected_receiver: Bytes, + #[case] expected_optimization: bool, + ) { + let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address()); + + let next_swap = if protocol.is_none() { + None + } else { + Some(SwapGroup { + protocol_system: protocol.unwrap().to_string(), + token_in: usdc(), + token_out: dai(), + split: 0f64, + swaps: vec![Swap { + component: ProtocolComponent { + protocol_system: protocol.unwrap().to_string(), + id: component_id().to_string(), + ..Default::default() + }, + token_in: usdc(), + token_out: dai(), + split: 0f64, + }], + }) + }; + + let result = optimization.get_receiver(receiver(), next_swap.as_ref()); + + assert!(result.is_ok()); + let (actual_receiver, optimization_flag) = result.unwrap(); + assert_eq!(actual_receiver, expected_receiver); + assert_eq!(optimization_flag, expected_optimization); + } } From 5562dd210eb31efcb855e29d18245248e9e83411 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 21 Apr 2025 22:39:17 -0400 Subject: [PATCH 17/22] feat: Make USV3 callback work with direct executor call - We have decided to now support calling our executors normally (without using delegatecalls) since they now support specifying token transfers. - This was disabled for UniswapV3 in the past also because of data decoding issues. This seems to be the solution, though. --- foundry/src/executors/UniswapV3Executor.sol | 8 +------ .../test/executors/UniswapV3Executor.t.sol | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/foundry/src/executors/UniswapV3Executor.sol b/foundry/src/executors/UniswapV3Executor.sol index 4609bc1..c1d570d 100644 --- a/foundry/src/executors/UniswapV3Executor.sol +++ b/foundry/src/executors/UniswapV3Executor.sol @@ -129,13 +129,7 @@ contract UniswapV3Executor is IExecutor, ICallback, TokenTransfer { int256, /* amount1Delta */ bytes calldata /* data */ ) external { - uint256 dataOffset = 4 + 32 + 32 + 32; // Skip selector + 2 ints + data_offset - uint256 dataLength = - uint256(bytes32(msg.data[dataOffset:dataOffset + 32])); - - bytes calldata fullData = msg.data[4:dataOffset + 32 + dataLength]; - - handleCallback(fullData); + handleCallback(msg.data); } function _decodeData(bytes calldata data) diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index cd77697..e9d95e6 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -96,6 +96,29 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { ); } + function testSwapIntegration() public { + uint256 amountIn = 10 ** 18; + deal(WETH_ADDR, address(uniswapV3Exposed), amountIn); + + uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI + bool zeroForOne = false; + + bytes memory data = encodeUniswapV3Swap( + WETH_ADDR, + DAI_ADDR, + address(this), + DAI_WETH_USV3, + zeroForOne, + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + ); + + uint256 amountOut = uniswapV3Exposed.swap(amountIn, data); + + assertGe(amountOut, expAmountOut); + assertEq(IERC20(WETH_ADDR).balanceOf(address(uniswapV3Exposed)), 0); + assertGe(IERC20(DAI_ADDR).balanceOf(address(this)), expAmountOut); + } + function testDecodeParamsInvalidDataLength() public { bytes memory invalidParams = abi.encodePacked(WETH_ADDR, address(2), address(3)); From 4f9785fdacb309feb689abc874f458eb06540a1b Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Fri, 18 Apr 2025 16:23:03 -0400 Subject: [PATCH 18/22] fix: Configurable fee on USV2 executor. - This fee is not the same depending on the fork. For example, Pancake V2 uses a 0.25% fee, while the USV2 fee is 0.3%. We were wrongly hard-coding this fee to 0.3%. --- foundry/scripts/deploy-executors.js | 35 +++++++++++-------- foundry/src/executors/UniswapV2Executor.sol | 12 +++++-- foundry/test/TychoRouterTestSetup.sol | 2 +- .../test/executors/UniswapV2Executor.t.sol | 12 ++++--- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/foundry/scripts/deploy-executors.js b/foundry/scripts/deploy-executors.js index 7149d63..9aa242f 100644 --- a/foundry/scripts/deploy-executors.js +++ b/foundry/scripts/deploy-executors.js @@ -5,25 +5,28 @@ const hre = require("hardhat"); // Comment out the executors you don't want to deploy const executors_to_deploy = { "ethereum": [ - // USV2 - Args: Factory, Pool Init Code Hash + // USV2 - Args: Factory, Pool Init Code Hash, Fee BPS { exchange: "UniswapV2Executor", args: [ "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", - "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" + "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f", + 30 ] }, - // SUSHISWAP - Args: Factory, Pool Init Code Hash + // SUSHISWAP - Args: Factory, Pool Init Code Hash, Fee BPS, Fee BPS { exchange: "UniswapV2Executor", args: [ "0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac", - "0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303" + "0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303", + 30 ] }, - // PANCAKESWAP V2 - Args: Factory, Pool Init Code Hash + // PANCAKESWAP V2 - Args: Factory, Pool Init Code Hash, Fee BPS { exchange: "UniswapV2Executor", args: [ "0x1097053Fd2ea711dad45caCcc45EfF7548fCB362", - "0x57224589c67f3f30a6b0d7a1b54cf3153ab84563bc609ef41dfb34f8b2974d2d" + "0x57224589c67f3f30a6b0d7a1b54cf3153ab84563bc609ef41dfb34f8b2974d2d", + 25 ] }, // USV3 -Args: Factory, Pool Init Code Hash @@ -57,25 +60,28 @@ const executors_to_deploy = { } ], "base": [ - // Args: Factory, Pool Init Code Hash + // Args: Factory, Pool Init Code Hash, Fee BPS { exchange: "UniswapV2Executor", args: [ "0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6", - "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" + "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f", + 30 ] }, - // SUSHISWAP V2 - Args: Factory, Pool Init Code Hash + // SUSHISWAP V2 - Args: Factory, Pool Init Code Hash, Fee BPS { exchange: "UniswapV2Executor", args: [ "0x71524B4f93c58fcbF659783284E38825f0622859", - "0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303" + "0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303", + 30 ] }, - // PANCAKESWAP V2 - Args: Factory, Pool Init Code Hash + // PANCAKESWAP V2 - Args: Factory, Pool Init Code Hash, Fee BPS { exchange: "UniswapV2Executor", args: [ "0x02a84c1b3BBD7401a5f7fa98a384EBC70bB5749E", - "0x57224589c67f3f30a6b0d7a1b54cf3153ab84563bc609ef41dfb34f8b2974d2d" + "0x57224589c67f3f30a6b0d7a1b54cf3153ab84563bc609ef41dfb34f8b2974d2d", + 25 ] }, // USV3 - Args: Factory, Pool Init Code Hash @@ -97,11 +103,12 @@ const executors_to_deploy = { {exchange: "BalancerV2Executor", args: []}, ], "unichain": [ - // Args: Factory, Pool Init Code Hash + // Args: Factory, Pool Init Code Hash, Fee BPS { exchange: "UniswapV2Executor", args: [ "0x1f98400000000000000000000000000000000002", - "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" + "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f", + 30 ] }, // USV3 - Args: Factory, Pool Init Code Hash diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index 863216d..ce172a7 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -10,6 +10,7 @@ error UniswapV2Executor__InvalidDataLength(); error UniswapV2Executor__InvalidTarget(); error UniswapV2Executor__InvalidFactory(); error UniswapV2Executor__InvalidInitCode(); +error UniswapV2Executor__InvalidFee(); contract UniswapV2Executor is IExecutor, TokenTransfer { using SafeERC20 for IERC20; @@ -17,8 +18,9 @@ contract UniswapV2Executor is IExecutor, TokenTransfer { address public immutable factory; bytes32 public immutable initCode; address private immutable self; + uint256 public immutable feeBps; - constructor(address _factory, bytes32 _initCode, address _permit2) + constructor(address _factory, bytes32 _initCode, address _permit2, uint256 _feeBps) TokenTransfer(_permit2) { if (_factory == address(0)) { @@ -29,6 +31,10 @@ contract UniswapV2Executor is IExecutor, TokenTransfer { } factory = _factory; initCode = _initCode; + if (_feeBps > 10000) { + revert UniswapV2Executor__InvalidFee(); + } + feeBps = _feeBps; self = address(this); } @@ -100,9 +106,9 @@ contract UniswapV2Executor is IExecutor, TokenTransfer { } require(reserveIn > 0 && reserveOut > 0, "L"); - uint256 amountInWithFee = amountIn * 997; + uint256 amountInWithFee = amountIn * (10000 - feeBps); uint256 numerator = amountInWithFee * uint256(reserveOut); - uint256 denominator = (uint256(reserveIn) * 1000) + amountInWithFee; + uint256 denominator = (uint256(reserveIn) * 10000) + amountInWithFee; amount = numerator / denominator; } diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index c97cb56..0252c2b 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -99,7 +99,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper { IPoolManager poolManager = IPoolManager(poolManagerAddress); usv2Executor = - new UniswapV2Executor(factoryV2, initCodeV2, PERMIT2_ADDRESS); + new UniswapV2Executor(factoryV2, initCodeV2, PERMIT2_ADDRESS, 30); usv3Executor = new UniswapV3Executor(factoryV3, initCodeV3, PERMIT2_ADDRESS); usv4Executor = new UniswapV4Executor(poolManager, PERMIT2_ADDRESS); diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 64be1d2..70f16c8 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -8,8 +8,8 @@ import {Constants} from "../Constants.sol"; import {Permit2TestHelper} from "../Permit2TestHelper.sol"; contract UniswapV2ExecutorExposed is UniswapV2Executor { - constructor(address _factory, bytes32 _initCode, address _permit2) - UniswapV2Executor(_factory, _initCode, _permit2) + constructor(address _factory, bytes32 _initCode, address _permit2, uint256 _feeBps) + UniswapV2Executor(_factory, _initCode, _permit2, _feeBps) {} function decodeParams(bytes calldata data) @@ -63,17 +63,19 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { uint256 forkBlock = 17323404; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); uniswapV2Exposed = new UniswapV2ExecutorExposed( - USV2_FACTORY_ETHEREUM, USV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS + USV2_FACTORY_ETHEREUM, USV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS, 30 ); sushiswapV2Exposed = new UniswapV2ExecutorExposed( SUSHISWAPV2_FACTORY_ETHEREUM, SUSHIV2_POOL_CODE_INIT_HASH, - PERMIT2_ADDRESS + PERMIT2_ADDRESS, + 30 ); pancakeswapV2Exposed = new UniswapV2ExecutorExposed( PANCAKESWAPV2_FACTORY_ETHEREUM, PANCAKEV2_POOL_CODE_INIT_HASH, - PERMIT2_ADDRESS + PERMIT2_ADDRESS, + 25 ); permit2 = IAllowanceTransfer(PERMIT2_ADDRESS); } From af68016223cb74b4896ae627be798e6d211ef4e6 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Tue, 22 Apr 2025 17:45:56 -0400 Subject: [PATCH 19/22] fix: Tighten max feeBps in USV2 executor Value was too lenient. We are assuming no forks will have higher fees than the original USV2. --- foundry/src/executors/UniswapV2Executor.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index ce172a7..518b761 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -31,7 +31,7 @@ contract UniswapV2Executor is IExecutor, TokenTransfer { } factory = _factory; initCode = _initCode; - if (_feeBps > 10000) { + if (_feeBps > 30) { revert UniswapV2Executor__InvalidFee(); } feeBps = _feeBps; From d5d6e37041c316c460a3cf216c71eae1987a953f Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Tue, 22 Apr 2025 17:50:09 -0400 Subject: [PATCH 20/22] fix: Changes after rebase --- foundry/src/executors/UniswapV2Executor.sol | 9 ++++++--- foundry/test/executors/UniswapV2Executor.t.sol | 13 ++++++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index 518b761..dd04dd1 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -20,9 +20,12 @@ contract UniswapV2Executor is IExecutor, TokenTransfer { address private immutable self; uint256 public immutable feeBps; - constructor(address _factory, bytes32 _initCode, address _permit2, uint256 _feeBps) - TokenTransfer(_permit2) - { + constructor( + address _factory, + bytes32 _initCode, + address _permit2, + uint256 _feeBps + ) TokenTransfer(_permit2) { if (_factory == address(0)) { revert UniswapV2Executor__InvalidFactory(); } diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 70f16c8..ea0d825 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -8,9 +8,12 @@ import {Constants} from "../Constants.sol"; import {Permit2TestHelper} from "../Permit2TestHelper.sol"; contract UniswapV2ExecutorExposed is UniswapV2Executor { - constructor(address _factory, bytes32 _initCode, address _permit2, uint256 _feeBps) - UniswapV2Executor(_factory, _initCode, _permit2, _feeBps) - {} + constructor( + address _factory, + bytes32 _initCode, + address _permit2, + uint256 _feeBps + ) UniswapV2Executor(_factory, _initCode, _permit2, _feeBps) {} function decodeParams(bytes calldata data) external @@ -69,13 +72,13 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { SUSHISWAPV2_FACTORY_ETHEREUM, SUSHIV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS, - 30 + 30 ); pancakeswapV2Exposed = new UniswapV2ExecutorExposed( PANCAKESWAPV2_FACTORY_ETHEREUM, PANCAKEV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS, - 25 + 25 ); permit2 = IAllowanceTransfer(PERMIT2_ADDRESS); } From 104e1ecd4358ed13a36d30f430026f21252e6898 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 22 Apr 2025 15:36:11 +0000 Subject: [PATCH 21/22] chore(release): 1.0.0 [skip ci] ## [1.0.0](https://github.com/propeller-heads/tycho-execution/compare/0.81.0...1.0.0) (2025-04-22) ### Features * (WIP) Support selection of transfer into router ([528bd74](https://github.com/propeller-heads/tycho-execution/commit/528bd746f7546ff2cca35c0397af073c8cbdb39b)) * Add sequential swap methods ([2e3ffa2](https://github.com/propeller-heads/tycho-execution/commit/2e3ffa2c70f5af63a5f3b11556cd87ce535da3dc)) * Add SequentialSwap integration test with regular approvals ([8836598](https://github.com/propeller-heads/tycho-execution/commit/8836598e5348d9ef48b1777619b89bdb26db842e)) * Add SingleSwap integration test and fix bug in method signatures ([9c4dfef](https://github.com/propeller-heads/tycho-execution/commit/9c4dfef80de051f24979bc81f4f66f22f93e5f02)) * Add TokenTransfer class to BalancerV2 ([8e7dd0b](https://github.com/propeller-heads/tycho-execution/commit/8e7dd0b9e7749996f25c82a4be4cd9e4101e6308)) * Add TokenTransfer class to Curve ([0e0dff7](https://github.com/propeller-heads/tycho-execution/commit/0e0dff7f5a712a1271c030e968d263239f450c6d)) * Add transfer out for Curve ([005c5a6](https://github.com/propeller-heads/tycho-execution/commit/005c5a6207d449ac59fd117156e487fb228a08c7)) * Add transfer out for Uniswap V4 ([534e4f0](https://github.com/propeller-heads/tycho-execution/commit/534e4f00db3005e6d4eda363721818b9661d11d6)) * Allow for token_in_already_in_router ([655c0b2](https://github.com/propeller-heads/tycho-execution/commit/655c0b263590d84d2f86c7b86db1a6be2308107c)) * allow to pass msg.sender to USV3 callback ([bf4b229](https://github.com/propeller-heads/tycho-execution/commit/bf4b229268bcdd4aeb7e127a01dada3328634da0)) * allow to pass msg.sender to USV3 callback ([e71a0bb](https://github.com/propeller-heads/tycho-execution/commit/e71a0bb3549fea326996fc378442a353d30d620f)) * Decode single and sequential swaps in LibSwap ([9be4845](https://github.com/propeller-heads/tycho-execution/commit/9be48456e5b573b74ade5b3cedd4e58a1a2967dc)) * Delete EVMStrategyEncoder (this is now unnecessary) ([1fabef7](https://github.com/propeller-heads/tycho-execution/commit/1fabef7a972e795e3adbbbcd63a38cb597de8f12)) * Do not use V4Router for uniswap v4 ([09b5a73](https://github.com/propeller-heads/tycho-execution/commit/09b5a732efe32e36cca4f7bf406bbaf394932284)) * ExecutorTransferMethods helper contract ([abd9db9](https://github.com/propeller-heads/tycho-execution/commit/abd9db937df58ea76bc45744d7c0fa34226d9357)) * ExecutorTransferMethods in UniswapV3Executor ([dbbd30e](https://github.com/propeller-heads/tycho-execution/commit/dbbd30e5964fbae6a82ff2a09a82e0d041774362)) * ExecutorTransferMethods in UniswapV3Executor ([e5426f3](https://github.com/propeller-heads/tycho-execution/commit/e5426f3038325038b63e8c5389b83b205b18ffe4)) * Fix rollFork usage for Ekubo test ([d7b5e6d](https://github.com/propeller-heads/tycho-execution/commit/d7b5e6dba5e0ae8fbf67dfb102fca51477f0127b)) * Make USV3 callback work with direct executor call ([51a7910](https://github.com/propeller-heads/tycho-execution/commit/51a791084448842e70c22e457e5318c778cfebb3)) * No more fee taking ([89f9121](https://github.com/propeller-heads/tycho-execution/commit/89f9121e4ca2d91260bb883a3a62c2caaae3205c)) * Optimize transfer to first pool ([bf63696](https://github.com/propeller-heads/tycho-execution/commit/bf63696142aa33452690bb7a7088716a9a8dac7b)) * Proper USV2Executor transfer decoding + tests ([b7ff870](https://github.com/propeller-heads/tycho-execution/commit/b7ff870a7cb318bdd89d6563bdbd2a73a9f67925)) * Proper USV2Executor transfer decoding + tests ([9dce2c7](https://github.com/propeller-heads/tycho-execution/commit/9dce2c7465eaf6a68ccdd40134bf9c82336f3401)) * Proper USV3Executor transfer decoding + tests ([1e37320](https://github.com/propeller-heads/tycho-execution/commit/1e373200875097526a558c8b0aa2b93389c138bc)) * Refactor callback for pancakeV3 and Ekubo to use transient storage ([deb10b8](https://github.com/propeller-heads/tycho-execution/commit/deb10b82deba12ee7d68d586ba04498a3f3ca208)) * Refactor callback to use transient storage ([af44956](https://github.com/propeller-heads/tycho-execution/commit/af449562b0c4038cf666194a52eea582855495fc)) * Refactor TychoEncoder ([1996fd3](https://github.com/propeller-heads/tycho-execution/commit/1996fd39e848aede11fbff7c8f3f914931c6b28a)) * sequential swap solution validation ([efa5096](https://github.com/propeller-heads/tycho-execution/commit/efa50966613f94abcc27b74e8f08b6322ec75b28)) * Single swap methods. ([72ccadc](https://github.com/propeller-heads/tycho-execution/commit/72ccadcaaebda9ca3ea16a9eb88dc0e73651b76e)) * SingleSwapStrategyEncoder ([5efeb8b](https://github.com/propeller-heads/tycho-execution/commit/5efeb8b305d20aefb7876b34d232bbe92f65624f)) * SingleSwapStrategyEncoder ([03f6961](https://github.com/propeller-heads/tycho-execution/commit/03f6961b13d064352b2dd8ec7ab7d591d1e12ba5)) * SingleSwapStrategyEncoder ([dbf5d1d](https://github.com/propeller-heads/tycho-execution/commit/dbf5d1ddda1d84325d4562a1660a92d5524ab853)) * Support in between swaps optimizations ([83d3721](https://github.com/propeller-heads/tycho-execution/commit/83d3721bf19ee6fc6a4fffd58e9e89b515c2473e)) * Support out transfer straight to the receiver ([d28c254](https://github.com/propeller-heads/tycho-execution/commit/d28c254225e3388adcf6df596c40b15e7d335ddc)) * Support returning values from the callback ([fb35a53](https://github.com/propeller-heads/tycho-execution/commit/fb35a5305a91c377b9617965b4ed36c06be3bd42)) * Support using the TransferType in uniswap v4 ([2ecbabe](https://github.com/propeller-heads/tycho-execution/commit/2ecbabeafc36b7d47247e44388c53f3015f832d7)) * **tycho-router-encoder:** Select strategy depending on the solution ([7f14f1a](https://github.com/propeller-heads/tycho-execution/commit/7f14f1a4ffe2f108fb2e25f030a40e3e344eefce)) * Use TokenTransfer optimization helper in Ekubo ([d4e8642](https://github.com/propeller-heads/tycho-execution/commit/d4e864272d346c9020f4e21c6953a980f58b6e5b)) ### Bug Fixes * Add slither ignore for loop call ([15f4ed5](https://github.com/propeller-heads/tycho-execution/commit/15f4ed5d36ba743c1a5f82b2ca903a0272f42f8f)) * After rebase fixes ([ae30218](https://github.com/propeller-heads/tycho-execution/commit/ae30218842d23ecea0331f6bc086e9be08250911)) * bad merge ([03ef744](https://github.com/propeller-heads/tycho-execution/commit/03ef744373429fc245d1aa6b4a726f1c0bfed20a)) * Bring back receiver address zero check ([8e60b6b](https://github.com/propeller-heads/tycho-execution/commit/8e60b6beba77e0dd729b9621c320d7abd5454733)) * Calldata size for Ekubo pay callback ([fb855d0](https://github.com/propeller-heads/tycho-execution/commit/fb855d00a445904301e2b5a7dd6004ce7b58edbc)) * Conscious slither silencing ([a645fc7](https://github.com/propeller-heads/tycho-execution/commit/a645fc72ab3082e9d8526428c7738aca7d5a07be)) * consider wrapping scenario when getting transfer type ([028e860](https://github.com/propeller-heads/tycho-execution/commit/028e8605a173b2407e7a3113337d3937656a2fe6)) * Fix after merge with main ([3de5a19](https://github.com/propeller-heads/tycho-execution/commit/3de5a192b645ed337addc2b2923109d45339a890)) * Fix executor address in test and remove duplicated test ([9456dc7](https://github.com/propeller-heads/tycho-execution/commit/9456dc7b0bb3df70c36811366c94a1716123ed57)) * Fix integration tests with transfer in method support ([0f3a910](https://github.com/propeller-heads/tycho-execution/commit/0f3a9101b9e3f98ae27fa55bcd14d4ce78449982)) * fix slither CI action ([7431b26](https://github.com/propeller-heads/tycho-execution/commit/7431b266e169e442aacbb8113273151aca5145b8)) * Fixes after merge with feature branch ([26e6c6c](https://github.com/propeller-heads/tycho-execution/commit/26e6c6c2664105869ae6a418477d8ae37fc6da84)) * Integration tests after merge ([55242fb](https://github.com/propeller-heads/tycho-execution/commit/55242fb8c4bc506c0cb9f649d00a88469f28f72a)) * No more EVMStrategyEncoder ([fbbc4c5](https://github.com/propeller-heads/tycho-execution/commit/fbbc4c5277eb747608b5157117f11e175f113dab)) * Post rebase fixes ([01483c4](https://github.com/propeller-heads/tycho-execution/commit/01483c4407d048e8225944b6218d6115de214669)) * Prevent multiple callbacks ([f91b101](https://github.com/propeller-heads/tycho-execution/commit/f91b101a94c85b78b5b3033915c74649d6916070)) * properly add ekubo_v2 to constants ([24dd814](https://github.com/propeller-heads/tycho-execution/commit/24dd814098768f8b3abf8c7a9f2dba091199bb90)) * Remove --ignore-compile flag for slither ([353667e](https://github.com/propeller-heads/tycho-execution/commit/353667e56506fe042c82098eca6d55b4d0714b01)) * Remove router_address from Solution object ([9eb18f4](https://github.com/propeller-heads/tycho-execution/commit/9eb18f4474f82c8af65c9da75580184e25e87e57)) * Rename constants and update docstrings for clarity ([11dffdc](https://github.com/propeller-heads/tycho-execution/commit/11dffdcb2861499901b714a79a308185cb6e4041)) * test fix after rebase ([e05ffed](https://github.com/propeller-heads/tycho-execution/commit/e05ffedd5dd38473677d465f50afed40b222422a)) * Test+formatting fixes after rebase. ([6cf0f52](https://github.com/propeller-heads/tycho-execution/commit/6cf0f523c1c2f474574860c3d1c313f0ec2d5f98)) * TransferType renaming after rebase ([12c410b](https://github.com/propeller-heads/tycho-execution/commit/12c410bb03853acecc6255f3089e988b416917a1)) * unsupported protocols for chained swaps are always unsupported ([9ce6fc0](https://github.com/propeller-heads/tycho-execution/commit/9ce6fc015334f91e50b9ec4597c383eba3ad006c)) * USV3 encoding/decoding after rebase ([2d0a3ac](https://github.com/propeller-heads/tycho-execution/commit/2d0a3ac3fd82ac45086f402916c7591e09b616f9)) --- CHANGELOG.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8cd8aa..0cb54b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,76 @@ +## [1.0.0](https://github.com/propeller-heads/tycho-execution/compare/0.81.0...1.0.0) (2025-04-22) + + +### Features + +* (WIP) Support selection of transfer into router ([528bd74](https://github.com/propeller-heads/tycho-execution/commit/528bd746f7546ff2cca35c0397af073c8cbdb39b)) +* Add sequential swap methods ([2e3ffa2](https://github.com/propeller-heads/tycho-execution/commit/2e3ffa2c70f5af63a5f3b11556cd87ce535da3dc)) +* Add SequentialSwap integration test with regular approvals ([8836598](https://github.com/propeller-heads/tycho-execution/commit/8836598e5348d9ef48b1777619b89bdb26db842e)) +* Add SingleSwap integration test and fix bug in method signatures ([9c4dfef](https://github.com/propeller-heads/tycho-execution/commit/9c4dfef80de051f24979bc81f4f66f22f93e5f02)) +* Add TokenTransfer class to BalancerV2 ([8e7dd0b](https://github.com/propeller-heads/tycho-execution/commit/8e7dd0b9e7749996f25c82a4be4cd9e4101e6308)) +* Add TokenTransfer class to Curve ([0e0dff7](https://github.com/propeller-heads/tycho-execution/commit/0e0dff7f5a712a1271c030e968d263239f450c6d)) +* Add transfer out for Curve ([005c5a6](https://github.com/propeller-heads/tycho-execution/commit/005c5a6207d449ac59fd117156e487fb228a08c7)) +* Add transfer out for Uniswap V4 ([534e4f0](https://github.com/propeller-heads/tycho-execution/commit/534e4f00db3005e6d4eda363721818b9661d11d6)) +* Allow for token_in_already_in_router ([655c0b2](https://github.com/propeller-heads/tycho-execution/commit/655c0b263590d84d2f86c7b86db1a6be2308107c)) +* allow to pass msg.sender to USV3 callback ([bf4b229](https://github.com/propeller-heads/tycho-execution/commit/bf4b229268bcdd4aeb7e127a01dada3328634da0)) +* allow to pass msg.sender to USV3 callback ([e71a0bb](https://github.com/propeller-heads/tycho-execution/commit/e71a0bb3549fea326996fc378442a353d30d620f)) +* Decode single and sequential swaps in LibSwap ([9be4845](https://github.com/propeller-heads/tycho-execution/commit/9be48456e5b573b74ade5b3cedd4e58a1a2967dc)) +* Delete EVMStrategyEncoder (this is now unnecessary) ([1fabef7](https://github.com/propeller-heads/tycho-execution/commit/1fabef7a972e795e3adbbbcd63a38cb597de8f12)) +* Do not use V4Router for uniswap v4 ([09b5a73](https://github.com/propeller-heads/tycho-execution/commit/09b5a732efe32e36cca4f7bf406bbaf394932284)) +* ExecutorTransferMethods helper contract ([abd9db9](https://github.com/propeller-heads/tycho-execution/commit/abd9db937df58ea76bc45744d7c0fa34226d9357)) +* ExecutorTransferMethods in UniswapV3Executor ([dbbd30e](https://github.com/propeller-heads/tycho-execution/commit/dbbd30e5964fbae6a82ff2a09a82e0d041774362)) +* ExecutorTransferMethods in UniswapV3Executor ([e5426f3](https://github.com/propeller-heads/tycho-execution/commit/e5426f3038325038b63e8c5389b83b205b18ffe4)) +* Fix rollFork usage for Ekubo test ([d7b5e6d](https://github.com/propeller-heads/tycho-execution/commit/d7b5e6dba5e0ae8fbf67dfb102fca51477f0127b)) +* Make USV3 callback work with direct executor call ([51a7910](https://github.com/propeller-heads/tycho-execution/commit/51a791084448842e70c22e457e5318c778cfebb3)) +* No more fee taking ([89f9121](https://github.com/propeller-heads/tycho-execution/commit/89f9121e4ca2d91260bb883a3a62c2caaae3205c)) +* Optimize transfer to first pool ([bf63696](https://github.com/propeller-heads/tycho-execution/commit/bf63696142aa33452690bb7a7088716a9a8dac7b)) +* Proper USV2Executor transfer decoding + tests ([b7ff870](https://github.com/propeller-heads/tycho-execution/commit/b7ff870a7cb318bdd89d6563bdbd2a73a9f67925)) +* Proper USV2Executor transfer decoding + tests ([9dce2c7](https://github.com/propeller-heads/tycho-execution/commit/9dce2c7465eaf6a68ccdd40134bf9c82336f3401)) +* Proper USV3Executor transfer decoding + tests ([1e37320](https://github.com/propeller-heads/tycho-execution/commit/1e373200875097526a558c8b0aa2b93389c138bc)) +* Refactor callback for pancakeV3 and Ekubo to use transient storage ([deb10b8](https://github.com/propeller-heads/tycho-execution/commit/deb10b82deba12ee7d68d586ba04498a3f3ca208)) +* Refactor callback to use transient storage ([af44956](https://github.com/propeller-heads/tycho-execution/commit/af449562b0c4038cf666194a52eea582855495fc)) +* Refactor TychoEncoder ([1996fd3](https://github.com/propeller-heads/tycho-execution/commit/1996fd39e848aede11fbff7c8f3f914931c6b28a)) +* sequential swap solution validation ([efa5096](https://github.com/propeller-heads/tycho-execution/commit/efa50966613f94abcc27b74e8f08b6322ec75b28)) +* Single swap methods. ([72ccadc](https://github.com/propeller-heads/tycho-execution/commit/72ccadcaaebda9ca3ea16a9eb88dc0e73651b76e)) +* SingleSwapStrategyEncoder ([5efeb8b](https://github.com/propeller-heads/tycho-execution/commit/5efeb8b305d20aefb7876b34d232bbe92f65624f)) +* SingleSwapStrategyEncoder ([03f6961](https://github.com/propeller-heads/tycho-execution/commit/03f6961b13d064352b2dd8ec7ab7d591d1e12ba5)) +* SingleSwapStrategyEncoder ([dbf5d1d](https://github.com/propeller-heads/tycho-execution/commit/dbf5d1ddda1d84325d4562a1660a92d5524ab853)) +* Support in between swaps optimizations ([83d3721](https://github.com/propeller-heads/tycho-execution/commit/83d3721bf19ee6fc6a4fffd58e9e89b515c2473e)) +* Support out transfer straight to the receiver ([d28c254](https://github.com/propeller-heads/tycho-execution/commit/d28c254225e3388adcf6df596c40b15e7d335ddc)) +* Support returning values from the callback ([fb35a53](https://github.com/propeller-heads/tycho-execution/commit/fb35a5305a91c377b9617965b4ed36c06be3bd42)) +* Support using the TransferType in uniswap v4 ([2ecbabe](https://github.com/propeller-heads/tycho-execution/commit/2ecbabeafc36b7d47247e44388c53f3015f832d7)) +* **tycho-router-encoder:** Select strategy depending on the solution ([7f14f1a](https://github.com/propeller-heads/tycho-execution/commit/7f14f1a4ffe2f108fb2e25f030a40e3e344eefce)) +* Use TokenTransfer optimization helper in Ekubo ([d4e8642](https://github.com/propeller-heads/tycho-execution/commit/d4e864272d346c9020f4e21c6953a980f58b6e5b)) + + +### Bug Fixes + +* Add slither ignore for loop call ([15f4ed5](https://github.com/propeller-heads/tycho-execution/commit/15f4ed5d36ba743c1a5f82b2ca903a0272f42f8f)) +* After rebase fixes ([ae30218](https://github.com/propeller-heads/tycho-execution/commit/ae30218842d23ecea0331f6bc086e9be08250911)) +* bad merge ([03ef744](https://github.com/propeller-heads/tycho-execution/commit/03ef744373429fc245d1aa6b4a726f1c0bfed20a)) +* Bring back receiver address zero check ([8e60b6b](https://github.com/propeller-heads/tycho-execution/commit/8e60b6beba77e0dd729b9621c320d7abd5454733)) +* Calldata size for Ekubo pay callback ([fb855d0](https://github.com/propeller-heads/tycho-execution/commit/fb855d00a445904301e2b5a7dd6004ce7b58edbc)) +* Conscious slither silencing ([a645fc7](https://github.com/propeller-heads/tycho-execution/commit/a645fc72ab3082e9d8526428c7738aca7d5a07be)) +* consider wrapping scenario when getting transfer type ([028e860](https://github.com/propeller-heads/tycho-execution/commit/028e8605a173b2407e7a3113337d3937656a2fe6)) +* Fix after merge with main ([3de5a19](https://github.com/propeller-heads/tycho-execution/commit/3de5a192b645ed337addc2b2923109d45339a890)) +* Fix executor address in test and remove duplicated test ([9456dc7](https://github.com/propeller-heads/tycho-execution/commit/9456dc7b0bb3df70c36811366c94a1716123ed57)) +* Fix integration tests with transfer in method support ([0f3a910](https://github.com/propeller-heads/tycho-execution/commit/0f3a9101b9e3f98ae27fa55bcd14d4ce78449982)) +* fix slither CI action ([7431b26](https://github.com/propeller-heads/tycho-execution/commit/7431b266e169e442aacbb8113273151aca5145b8)) +* Fixes after merge with feature branch ([26e6c6c](https://github.com/propeller-heads/tycho-execution/commit/26e6c6c2664105869ae6a418477d8ae37fc6da84)) +* Integration tests after merge ([55242fb](https://github.com/propeller-heads/tycho-execution/commit/55242fb8c4bc506c0cb9f649d00a88469f28f72a)) +* No more EVMStrategyEncoder ([fbbc4c5](https://github.com/propeller-heads/tycho-execution/commit/fbbc4c5277eb747608b5157117f11e175f113dab)) +* Post rebase fixes ([01483c4](https://github.com/propeller-heads/tycho-execution/commit/01483c4407d048e8225944b6218d6115de214669)) +* Prevent multiple callbacks ([f91b101](https://github.com/propeller-heads/tycho-execution/commit/f91b101a94c85b78b5b3033915c74649d6916070)) +* properly add ekubo_v2 to constants ([24dd814](https://github.com/propeller-heads/tycho-execution/commit/24dd814098768f8b3abf8c7a9f2dba091199bb90)) +* Remove --ignore-compile flag for slither ([353667e](https://github.com/propeller-heads/tycho-execution/commit/353667e56506fe042c82098eca6d55b4d0714b01)) +* Remove router_address from Solution object ([9eb18f4](https://github.com/propeller-heads/tycho-execution/commit/9eb18f4474f82c8af65c9da75580184e25e87e57)) +* Rename constants and update docstrings for clarity ([11dffdc](https://github.com/propeller-heads/tycho-execution/commit/11dffdcb2861499901b714a79a308185cb6e4041)) +* test fix after rebase ([e05ffed](https://github.com/propeller-heads/tycho-execution/commit/e05ffedd5dd38473677d465f50afed40b222422a)) +* Test+formatting fixes after rebase. ([6cf0f52](https://github.com/propeller-heads/tycho-execution/commit/6cf0f523c1c2f474574860c3d1c313f0ec2d5f98)) +* TransferType renaming after rebase ([12c410b](https://github.com/propeller-heads/tycho-execution/commit/12c410bb03853acecc6255f3089e988b416917a1)) +* unsupported protocols for chained swaps are always unsupported ([9ce6fc0](https://github.com/propeller-heads/tycho-execution/commit/9ce6fc015334f91e50b9ec4597c383eba3ad006c)) +* USV3 encoding/decoding after rebase ([2d0a3ac](https://github.com/propeller-heads/tycho-execution/commit/2d0a3ac3fd82ac45086f402916c7591e09b616f9)) + ## [0.81.0](https://github.com/propeller-heads/tycho-execution/compare/0.80.0...0.81.0) (2025-04-18) diff --git a/Cargo.lock b/Cargo.lock index ae054ce..82d6d4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4341,7 +4341,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.81.0" +version = "1.0.0" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index 0421e50..f8c8b8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.81.0" +version = "1.0.0" edition = "2021" description = "Provides tools for encoding and executing swaps against Tycho router and protocol executors." repository = "https://github.com/propeller-heads/tycho-execution" From 48565f3460295adb2eaba59433caa0a821d03409 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 23 Apr 2025 08:43:18 +0000 Subject: [PATCH 22/22] chore(release): 1.0.1 [skip ci] ## [1.0.1](https://github.com/propeller-heads/tycho-execution/compare/1.0.0...1.0.1) (2025-04-23) ### Bug Fixes * Changes after rebase ([d39a2ac](https://github.com/propeller-heads/tycho-execution/commit/d39a2accce2d533f8d26223156853b442c02355b)) * Configurable fee on USV2 executor. ([826aa54](https://github.com/propeller-heads/tycho-execution/commit/826aa54631b71fd209c6cc56c90dc373a4da966d)) * Tighten max feeBps in USV2 executor ([e07573b](https://github.com/propeller-heads/tycho-execution/commit/e07573bbb8db7ab5c88728dcaea8f537bc2520d4)) --- CHANGELOG.md | 9 +++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cb54b2..eb25ca7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [1.0.1](https://github.com/propeller-heads/tycho-execution/compare/1.0.0...1.0.1) (2025-04-23) + + +### Bug Fixes + +* Changes after rebase ([d39a2ac](https://github.com/propeller-heads/tycho-execution/commit/d39a2accce2d533f8d26223156853b442c02355b)) +* Configurable fee on USV2 executor. ([826aa54](https://github.com/propeller-heads/tycho-execution/commit/826aa54631b71fd209c6cc56c90dc373a4da966d)) +* Tighten max feeBps in USV2 executor ([e07573b](https://github.com/propeller-heads/tycho-execution/commit/e07573bbb8db7ab5c88728dcaea8f537bc2520d4)) + ## [1.0.0](https://github.com/propeller-heads/tycho-execution/compare/0.81.0...1.0.0) (2025-04-22) diff --git a/Cargo.lock b/Cargo.lock index 82d6d4b..372c275 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4341,7 +4341,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index f8c8b8e..15dc129 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "1.0.0" +version = "1.0.1" edition = "2021" description = "Provides tools for encoding and executing swaps against Tycho router and protocol executors." repository = "https://github.com/propeller-heads/tycho-execution"