diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..14cf8ec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,29 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. .. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Full error traceback** +Please provide the full error traceback/failed transaction/failed tenderly simulation diff --git a/CHANGELOG.md b/CHANGELOG.md index 97d7c0a..e109013 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,40 @@ +## [0.128.0](https://github.com/propeller-heads/tycho-execution/compare/0.127.0...0.128.0) (2025-09-25) + + +### Features + +* Use tycho-common from crates.io ([89435b5](https://github.com/propeller-heads/tycho-execution/commit/89435b5c76bbd1fbf2c946686ede442c4647cd29)) + +## [0.127.0](https://github.com/propeller-heads/tycho-execution/compare/0.126.0...0.127.0) (2025-09-25) + + +### Features + +* Use tycho-common from github ([5d73e3b](https://github.com/propeller-heads/tycho-execution/commit/5d73e3b47c5cbff4fa0c88e71f15f48f3831f199)) + +## [0.126.0](https://github.com/propeller-heads/tycho-execution/compare/0.125.0...0.126.0) (2025-09-25) + + +### Features + +* Add historical_trade option to encoding ([c51c6f5](https://github.com/propeller-heads/tycho-execution/commit/c51c6f52a5c1a7e47caab3bfa721f7c373a8229e)) +* Pass the file contents instead of the file path for executors ([e78a362](https://github.com/propeller-heads/tycho-execution/commit/e78a362894955a8b0e676bbcb189195d00815aad)) +* Update tycho common to point to hooks feature branch ([a98e8d2](https://github.com/propeller-heads/tycho-execution/commit/a98e8d21ccd2eafeb42805acb6ce157b60374a0c)) + +## [0.125.0](https://github.com/propeller-heads/tycho-execution/compare/0.124.0...0.125.0) (2025-09-25) + + +### Features + +* bump tycho-common version ([fa3bb6d](https://github.com/propeller-heads/tycho-execution/commit/fa3bb6daf74c6df254a4b65718663ec0ca7339d6)) + +## [0.124.0](https://github.com/propeller-heads/tycho-execution/compare/0.123.0...0.124.0) (2025-09-11) + + +### Features + +* bump tycho-common version ([bbd732d](https://github.com/propeller-heads/tycho-execution/commit/bbd732d15a4405fc358eedbb3cbb3b98ec3f61b5)) + ## [0.123.0](https://github.com/propeller-heads/tycho-execution/compare/0.122.0...0.123.0) (2025-09-02) diff --git a/Cargo.lock b/Cargo.lock index 258d4cb..eca6a6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4634,9 +4634,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tycho-common" -version = "0.82.0" +version = "0.88.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "096c87ebe011785fcd7ed59ec501ac12b465a64fbd2914b8c0c57125c253682b" +checksum = "ac0c309443e15797f3b713f10b8f00e79067abe91ea7c58421e4ce345b022e0d" dependencies = [ "anyhow", "async-trait", @@ -4659,7 +4659,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.123.0" +version = "0.128.0" dependencies = [ "alloy", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index cda8746..c5455ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.123.0" +version = "0.128.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" @@ -11,12 +11,12 @@ license = "MIT" categories = ["finance", "cryptography::cryptocurrencies"] readme = "README.md" exclude = [ - "foundry/*", - "foundry", - "tests/*", - "tests/common", - ".github/*", - ".gitmodules", + "foundry/*", + "foundry", + "tests/*", + "tests/common", + ".github/*", + ".gitmodules", ] [[bin]] @@ -37,7 +37,7 @@ tokio = { version = "1.38.0", features = ["full"] } chrono = "0.4.39" clap = { version = "4.5.3", features = ["derive"] } once_cell = "1.20.2" -tycho-common = "0.82.0" +tycho-common = "0.88.0" alloy = { version = "1.0.6", features = ["providers", "rpc-types-eth", "eip712", "signer-local", "node-bindings"], optional = true } async-trait = { version = "0.1.88", optional = true } diff --git a/foundry/test/.gitignore b/foundry/test/.gitignore new file mode 100644 index 0000000..47e2914 --- /dev/null +++ b/foundry/test/.gitignore @@ -0,0 +1 @@ +*.runtime.json diff --git a/foundry/test/protocols/BalancerV2.t.sol b/foundry/test/protocols/BalancerV2.t.sol index 1540ce1..7c056f1 100644 --- a/foundry/test/protocols/BalancerV2.t.sol +++ b/foundry/test/protocols/BalancerV2.t.sol @@ -136,7 +136,6 @@ contract BalancerV2ExecutorTest is Constants, TestUtils { } function testExportContract() public { - vm.skip(true); exportRuntimeBytecode(address(balancerV2Exposed), "BalancerV2"); } } diff --git a/foundry/test/protocols/BalancerV3.t.sol b/foundry/test/protocols/BalancerV3.t.sol index 3a1a182..d28a94b 100644 --- a/foundry/test/protocols/BalancerV3.t.sol +++ b/foundry/test/protocols/BalancerV3.t.sol @@ -119,6 +119,10 @@ contract BalancerV3ExecutorTest is Constants, TestUtils { assertGt(balanceAfter, balanceBefore); assertEq(balanceAfter - balanceBefore, amountOut); } + + function testExportContract() public { + exportRuntimeBytecode(address(balancerV3Exposed), "BalancerV3"); + } } contract TychoRouterForBalancerV3Test is TychoRouterTestSetup { diff --git a/foundry/test/protocols/Curve.t.sol b/foundry/test/protocols/Curve.t.sol index bf288d4..666ca37 100644 --- a/foundry/test/protocols/Curve.t.sol +++ b/foundry/test/protocols/Curve.t.sol @@ -46,7 +46,7 @@ contract CurveExecutorExposed is CurveExecutor { } } -contract CurveExecutorTest is Test, Constants { +contract CurveExecutorTest is Test, TestUtils, Constants { using SafeERC20 for IERC20; CurveExecutorExposed curveExecutorExposed; @@ -393,6 +393,10 @@ contract CurveExecutorTest is Test, Constants { metaRegistry.get_coin_indices(pool, tokenIn, tokenOut); return (coinInIndex, coinOutIndex); } + + function testExportContract() public { + exportRuntimeBytecode(address(curveExecutorExposed), "Curve"); + } } contract TychoRouterForBalancerV3Test is TychoRouterTestSetup { diff --git a/foundry/test/protocols/Ekubo.t.sol b/foundry/test/protocols/Ekubo.t.sol index f1e5da9..04b4b18 100644 --- a/foundry/test/protocols/Ekubo.t.sol +++ b/foundry/test/protocols/Ekubo.t.sol @@ -202,6 +202,10 @@ contract EkuboExecutorTest is Constants, TestUtils { function testMultiHopSwapIntegration() public setUpFork(22082754) { multiHopSwap(loadCallDataFromFile("test_ekubo_encode_swap_multi")); } + + function testExportContract() public { + exportRuntimeBytecode(address(executor), "Ekubo"); + } } contract TychoRouterForBalancerV3Test is TychoRouterTestSetup { diff --git a/foundry/test/protocols/Hashflow.t.sol b/foundry/test/protocols/Hashflow.t.sol index dee68d8..69c03d4 100644 --- a/foundry/test/protocols/Hashflow.t.sol +++ b/foundry/test/protocols/Hashflow.t.sol @@ -41,7 +41,7 @@ contract HashflowUtils is Test { } } -contract HashflowExecutorECR20Test is Constants, HashflowUtils { +contract HashflowExecutorECR20Test is Constants, TestUtils, HashflowUtils { using SafeERC20 for IERC20; HashflowExecutorExposed executor; diff --git a/foundry/test/protocols/MaverickV2.t.sol b/foundry/test/protocols/MaverickV2.t.sol index 131994a..998a154 100644 --- a/foundry/test/protocols/MaverickV2.t.sol +++ b/foundry/test/protocols/MaverickV2.t.sol @@ -126,6 +126,10 @@ contract MaverickV2ExecutorTest is TestUtils, Constants { assertGt(balanceAfter, balanceBefore); assertEq(balanceAfter - balanceBefore, amountOut); } + + function testExportContract() public { + exportRuntimeBytecode(address(maverickV2Exposed), "MaverickV2"); + } } contract TychoRouterForBalancerV3Test is TychoRouterTestSetup { diff --git a/foundry/test/protocols/UniswapV2.t.sol b/foundry/test/protocols/UniswapV2.t.sol index a509877..4e0ec28 100644 --- a/foundry/test/protocols/UniswapV2.t.sol +++ b/foundry/test/protocols/UniswapV2.t.sol @@ -263,4 +263,8 @@ contract UniswapV2ExecutorTest is Constants, Permit2TestHelper, TestUtils { assertEq(IERC20(BASE_MAG7).balanceOf(BOB), 1379830606); } + + function testExportContract() public { + exportRuntimeBytecode(address(uniswapV2Exposed), "UniswapV2"); + } } diff --git a/foundry/test/protocols/UniswapV3.t.sol b/foundry/test/protocols/UniswapV3.t.sol index cbc081d..4f92dfe 100644 --- a/foundry/test/protocols/UniswapV3.t.sol +++ b/foundry/test/protocols/UniswapV3.t.sol @@ -39,7 +39,12 @@ contract UniswapV3ExecutorExposed is UniswapV3Executor { } } -contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { +contract UniswapV3ExecutorTest is + Test, + TestUtils, + Constants, + Permit2TestHelper +{ using SafeERC20 for IERC20; UniswapV3ExecutorExposed uniswapV3Exposed; @@ -210,6 +215,10 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { transferType ); } + + function testExportContract() public { + exportRuntimeBytecode(address(uniswapV3Exposed), "UniswapV3"); + } } contract TychoRouterForBalancerV3Test is TychoRouterTestSetup { diff --git a/foundry/test/protocols/UniswapV4.t.sol b/foundry/test/protocols/UniswapV4.t.sol index 508283f..656fd65 100644 --- a/foundry/test/protocols/UniswapV4.t.sol +++ b/foundry/test/protocols/UniswapV4.t.sol @@ -261,6 +261,10 @@ contract UniswapV4ExecutorTest is Constants, TestUtils { ); assertTrue(IERC20(WETH_ADDR).balanceOf(ALICE) == amountOut); } + + function testExportContract() public { + exportRuntimeBytecode(address(uniswapV4Exposed), "UniswapV4"); + } } contract TychoRouterForBalancerV3Test is TychoRouterTestSetup { diff --git a/src/bin/tycho-encode.rs b/src/bin/tycho-encode.rs index 161f7ae..d0252dc 100644 --- a/src/bin/tycho-encode.rs +++ b/src/bin/tycho-encode.rs @@ -1,9 +1,13 @@ -use std::io::{self, Read}; +use std::{ + fs, + io::{self, Read}, +}; use alloy::sol_types::SolValue; use clap::{Parser, Subcommand}; use tycho_common::{hex_bytes::Bytes, models::Chain}; use tycho_execution::encoding::{ + errors::EncodingError, evm::{ approvals::permit2::PermitSingle, encoder_builders::{TychoExecutorEncoderBuilder, TychoRouterEncoderBuilder}, @@ -83,7 +87,12 @@ fn main() -> Result<(), Box> { Commands::TychoRouter => { let mut builder = TychoRouterEncoderBuilder::new().chain(chain); if let Some(config_path) = cli.executors_file_path { - builder = builder.executors_file_path(config_path); + let executors_addresses = fs::read_to_string(&config_path).map_err(|e| { + EncodingError::FatalError(format!( + "Error reading executors file from {config_path:?}: {e}", + )) + })?; + builder = builder.executors_addresses(executors_addresses); } if let Some(router_address) = cli.router_address { builder = builder.router_address(router_address); diff --git a/src/encoding/evm/encoder_builders.rs b/src/encoding/evm/encoder_builders.rs index b6b6497..703f083 100644 --- a/src/encoding/evm/encoder_builders.rs +++ b/src/encoding/evm/encoder_builders.rs @@ -20,9 +20,10 @@ use crate::encoding::{ pub struct TychoRouterEncoderBuilder { chain: Option, user_transfer_type: Option, - executors_file_path: Option, + executors_addresses: Option, router_address: Option, swapper_pk: Option, + historical_trade: bool, } impl Default for TychoRouterEncoderBuilder { @@ -35,10 +36,11 @@ impl TychoRouterEncoderBuilder { pub fn new() -> Self { TychoRouterEncoderBuilder { chain: None, - executors_file_path: None, + executors_addresses: None, router_address: None, swapper_pk: None, user_transfer_type: None, + historical_trade: false, } } pub fn chain(mut self, chain: Chain) -> Self { @@ -51,10 +53,10 @@ impl TychoRouterEncoderBuilder { self } - /// Sets the `executors_file_path` manually. - /// If it's not set, the default path will be used (config/executor_addresses.json) - pub fn executors_file_path(mut self, executors_file_path: String) -> Self { - self.executors_file_path = Some(executors_file_path); + /// Sets the `executors_addresses` manually. + /// If it's not set, the default value will be used (contents of config/executor_addresses.json) + pub fn executors_addresses(mut self, executors_addresses: String) -> Self { + self.executors_addresses = Some(executors_addresses); self } @@ -65,6 +67,15 @@ impl TychoRouterEncoderBuilder { self } + /// Sets the `historical_trade` manually to true. + /// If set to true, it means that the encoded trade will be used in an historical block (as a + /// test) and not in the current one. This is relevant for checking token approvals in some + /// protocols (like Balancer v2). + pub fn historical_trade(mut self) -> Self { + self.historical_trade = true; + self + } + /// Sets the `swapper_pk` for the encoder. This is used to sign permit2 objects. This is only /// needed if you intend to get the full calldata for the transfer. We do not recommend /// using this option, you should sign and create the function calldata entirely on your @@ -96,7 +107,7 @@ impl TychoRouterEncoderBuilder { } let swap_encoder_registry = - SwapEncoderRegistry::new(self.executors_file_path.clone(), chain)?; + SwapEncoderRegistry::new(self.executors_addresses.clone(), chain)?; let signer = if let Some(pk) = self.swapper_pk { let pk = B256::from_str(&pk).map_err(|_| { @@ -115,6 +126,7 @@ impl TychoRouterEncoderBuilder { tycho_router_address, user_transfer_type, signer, + self.historical_trade, )?)) } else { Err(EncodingError::FatalError( @@ -128,7 +140,7 @@ impl TychoRouterEncoderBuilder { /// Builder pattern for constructing a `TychoExecutorEncoder` with customizable options. pub struct TychoExecutorEncoderBuilder { chain: Option, - executors_file_path: Option, + executors_addresses: Option, } impl Default for TychoExecutorEncoderBuilder { @@ -139,17 +151,17 @@ impl Default for TychoExecutorEncoderBuilder { impl TychoExecutorEncoderBuilder { pub fn new() -> Self { - TychoExecutorEncoderBuilder { chain: None, executors_file_path: None } + TychoExecutorEncoderBuilder { chain: None, executors_addresses: None } } pub fn chain(mut self, chain: Chain) -> Self { self.chain = Some(chain); self } - /// Sets the `executors_file_path` manually. + /// Sets the `executors_addresses` manually. /// If it's not set, the default path will be used (config/executor_addresses.json) - pub fn executors_file_path(mut self, executors_file_path: String) -> Self { - self.executors_file_path = Some(executors_file_path); + pub fn executors_addresses(mut self, executors_addresses: String) -> Self { + self.executors_addresses = Some(executors_addresses); self } @@ -158,7 +170,7 @@ impl TychoExecutorEncoderBuilder { pub fn build(self) -> Result, EncodingError> { if let Some(chain) = self.chain { let swap_encoder_registry = - SwapEncoderRegistry::new(self.executors_file_path.clone(), chain)?; + SwapEncoderRegistry::new(self.executors_addresses.clone(), chain)?; Ok(Box::new(TychoExecutorEncoder::new(swap_encoder_registry)?)) } else { Err(EncodingError::FatalError( diff --git a/src/encoding/evm/encoding_utils.rs b/src/encoding/evm/encoding_utils.rs index 1e31f2c..5b636c2 100644 --- a/src/encoding/evm/encoding_utils.rs +++ b/src/encoding/evm/encoding_utils.rs @@ -58,11 +58,12 @@ use crate::encoding::{ /// funds. /// /// # Parameters +/// - `chain_id`: Chain ID /// - `encoded_solution`: The solution already encoded by Tycho. /// - `solution`: The high-level solution including tokens, amounts, and receiver info. -/// - `token_in_already_in_router`: Whether the input token is already present in the router. -/// - `router_address`: The address of the Tycho Router contract. +/// - `user_transfer_type`: The desired transfer method. /// - `native_address`: The address used to represent the native token +/// - `signer`: Optional signer for permit2 /// /// # Returns /// A `Result` that either contains the full transaction data (to, diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index fa47be8..44f5503 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -26,12 +26,15 @@ use crate::encoding::{ /// * `function_signature`: String, the signature for the swap function in the router contract /// * `router_address`: Address of the router to be used to execute swaps /// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers +/// * `historical_trade`: Whether the swap is to be done in the current block or in an historical +/// one. This is relevant for checking token approvals in some protocols (like Balancer v2). #[derive(Clone)] pub struct SingleSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, function_signature: String, router_address: Bytes, transfer_optimization: TransferOptimization, + historical_trade: bool, } impl SingleSwapStrategyEncoder { @@ -40,6 +43,7 @@ impl SingleSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, user_transfer_type: UserTransferType, router_address: Bytes, + historical_trade: bool, ) -> Result { let function_signature = if user_transfer_type == UserTransferType::TransferFromPermit2 { "singleSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)" @@ -57,6 +61,7 @@ impl SingleSwapStrategyEncoder { user_transfer_type, router_address, ), + historical_trade, }) } @@ -119,6 +124,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { group_token_in: grouped_swap.token_in.clone(), group_token_out: grouped_swap.token_out.clone(), transfer_type: transfer, + historical_trade: self.historical_trade, }; let mut grouped_protocol_data: Vec> = vec![]; @@ -171,6 +177,8 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { /// * `sequential_swap_validator`: SequentialSwapValidator, responsible for checking validity of /// sequential swap solutions /// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers +/// * `historical_trade`: Whether the swap is to be done in the current block or in an historical +/// one. This is relevant for checking token approvals in some protocols (like Balancer v2). #[derive(Clone)] pub struct SequentialSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, @@ -180,6 +188,7 @@ pub struct SequentialSwapStrategyEncoder { wrapped_address: Bytes, sequential_swap_validator: SequentialSwapValidator, transfer_optimization: TransferOptimization, + historical_trade: bool, } impl SequentialSwapStrategyEncoder { @@ -188,6 +197,7 @@ impl SequentialSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, user_transfer_type: UserTransferType, router_address: Bytes, + historical_trade: bool, ) -> Result { let function_signature = if user_transfer_type == UserTransferType::TransferFromPermit2 { "sequentialSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)" @@ -210,6 +220,7 @@ impl SequentialSwapStrategyEncoder { user_transfer_type, router_address, ), + historical_trade, }) } @@ -279,6 +290,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { group_token_in: grouped_swap.token_in.clone(), group_token_out: grouped_swap.token_out.clone(), transfer_type: transfer, + historical_trade: self.historical_trade, }; let mut grouped_protocol_data: Vec> = vec![]; @@ -336,6 +348,8 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { /// solutions /// * `router_address`: Address of the router to be used to execute swaps /// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers +/// * `historical_trade`: Whether the swap is to be done in the current block or in an historical +/// one. This is relevant for checking token approvals in some protocols (like Balancer v2). #[derive(Clone)] pub struct SplitSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, @@ -345,6 +359,7 @@ pub struct SplitSwapStrategyEncoder { split_swap_validator: SplitSwapValidator, router_address: Bytes, transfer_optimization: TransferOptimization, + historical_trade: bool, } impl SplitSwapStrategyEncoder { @@ -353,6 +368,7 @@ impl SplitSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, user_transfer_type: UserTransferType, router_address: Bytes, + historical_trade: bool, ) -> Result { let function_signature = if user_transfer_type == UserTransferType::TransferFromPermit2 { "splitSwapPermit2(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)" @@ -374,6 +390,7 @@ impl SplitSwapStrategyEncoder { user_transfer_type, router_address, ), + historical_trade, }) } @@ -479,6 +496,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { group_token_in: grouped_swap.token_in.clone(), group_token_out: grouped_swap.token_out.clone(), transfer_type: transfer, + historical_trade: self.historical_trade, }; let mut grouped_protocol_data: Vec> = vec![]; @@ -535,7 +553,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { #[cfg(test)] mod tests { - use std::{collections::HashMap, str::FromStr}; + use std::{collections::HashMap, fs, str::FromStr}; use alloy::{hex::encode, primitives::hex}; use num_bigint::{BigInt, BigUint}; @@ -555,9 +573,10 @@ mod tests { } fn get_swap_encoder_registry() -> SwapEncoderRegistry { + let executors_addresses = + fs::read_to_string("config/test_executor_addresses.json").unwrap(); let eth_chain = eth_chain(); - SwapEncoderRegistry::new(Some("config/test_executor_addresses.json".to_string()), eth_chain) - .unwrap() + SwapEncoderRegistry::new(Some(executors_addresses), eth_chain).unwrap() } fn router_address() -> Bytes { @@ -591,6 +610,7 @@ mod tests { swap_encoder_registry, UserTransferType::TransferFromPermit2, router_address(), + false, ) .unwrap(); let solution = Solution { @@ -651,6 +671,7 @@ mod tests { swap_encoder_registry, UserTransferType::None, router_address(), + false, ) .unwrap(); let solution = Solution { @@ -732,6 +753,7 @@ mod tests { swap_encoder_registry, UserTransferType::TransferFrom, router_address(), + false, ) .unwrap(); let solution = Solution { @@ -867,6 +889,7 @@ mod tests { swap_encoder_registry, UserTransferType::TransferFromPermit2, Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + false, ) .unwrap(); @@ -1015,6 +1038,7 @@ mod tests { swap_encoder_registry, UserTransferType::TransferFrom, Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + false, ) .unwrap(); diff --git a/src/encoding/evm/swap_encoder/swap_encoder_registry.rs b/src/encoding/evm/swap_encoder/swap_encoder_registry.rs index 221ea76..a9955a3 100644 --- a/src/encoding/evm/swap_encoder/swap_encoder_registry.rs +++ b/src/encoding/evm/swap_encoder/swap_encoder_registry.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, fs}; +use std::collections::HashMap; use tycho_common::models::Chain; @@ -21,13 +21,9 @@ pub struct SwapEncoderRegistry { impl SwapEncoderRegistry { /// Populates the registry with the `SwapEncoders` for the given blockchain by parsing the /// executors' addresses in the file at the given path. - pub fn new(executors_file_path: Option, chain: Chain) -> Result { - let config_str = if let Some(ref path) = executors_file_path { - fs::read_to_string(path).map_err(|e| { - EncodingError::FatalError(format!( - "Error reading executors file from {executors_file_path:?}: {e}", - )) - })? + pub fn new(executors_addresses: Option, chain: Chain) -> Result { + let config_str = if let Some(addresses) = executors_addresses { + addresses } else { DEFAULT_EXECUTORS_JSON.to_string() }; diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 134a5d6..eb8375b 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -280,19 +280,20 @@ impl SwapEncoder for BalancerV2SwapEncoder { ) -> Result, EncodingError> { let token_approvals_manager = ProtocolApprovalsManager::new()?; let token = bytes_to_address(&swap.token_in)?; - let approval_needed: bool; + let mut approval_needed: bool = true; if let Some(router_address) = &encoding_context.router_address { - let tycho_router_address = bytes_to_address(router_address)?; - approval_needed = token_approvals_manager.approval_needed( - token, - tycho_router_address, - Address::from_str(&self.vault_address) - .map_err(|_| EncodingError::FatalError("Invalid vault address".to_string()))?, - )?; - } else { - approval_needed = true; - } + if !encoding_context.historical_trade { + let tycho_router_address = bytes_to_address(router_address)?; + approval_needed = token_approvals_manager.approval_needed( + token, + tycho_router_address, + Address::from_str(&self.vault_address).map_err(|_| { + EncodingError::FatalError("Invalid vault address".to_string()) + })?, + )?; + } + }; let component_id = AlloyBytes::from_str(&swap.component.id) .map_err(|_| EncodingError::FatalError("Invalid component ID".to_string()))?; @@ -1026,6 +1027,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = UniswapV2SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -1081,6 +1083,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = UniswapV3SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -1138,6 +1141,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::None, + historical_trade: true, }; let encoder = BalancerV2SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -1207,6 +1211,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = UniswapV4SwapEncoder::new( String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"), @@ -1275,6 +1280,7 @@ mod tests { // Token out is the same as the group token out group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = UniswapV4SwapEncoder::new( @@ -1318,6 +1324,7 @@ mod tests { group_token_in: usde_address.clone(), group_token_out: wbtc_address.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; // Setup - First sequence: USDE -> USDT @@ -1448,6 +1455,7 @@ mod tests { exact_out: false, router_address: Some(Bytes::default()), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = EkuboSwapEncoder::new(String::default(), Chain::Ethereum, None).unwrap(); @@ -1490,6 +1498,7 @@ mod tests { exact_out: false, router_address: Some(Bytes::default()), transfer_type: TransferType::Transfer, + historical_trade: false, }; let first_swap = SwapBuilder::new( @@ -1687,6 +1696,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::None, + historical_trade: false, }; let encoder = CurveSwapEncoder::new( String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"), @@ -1753,6 +1763,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::None, + historical_trade: false, }; let encoder = CurveSwapEncoder::new( String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"), @@ -1820,6 +1831,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::None, + historical_trade: false, }; let encoder = CurveSwapEncoder::new( String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"), @@ -1888,6 +1900,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = BalancerV3SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -1940,6 +1953,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = MaverickV2SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -2033,6 +2047,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = BebopSwapEncoder::new( @@ -2107,6 +2122,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = HashflowSwapEncoder::new( @@ -2200,6 +2216,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = HashflowSwapEncoder::new( diff --git a/src/encoding/evm/tycho_encoders.rs b/src/encoding/evm/tycho_encoders.rs index b6aaa02..89ae7df 100644 --- a/src/encoding/evm/tycho_encoders.rs +++ b/src/encoding/evm/tycho_encoders.rs @@ -54,6 +54,7 @@ impl TychoRouterEncoder { router_address: Bytes, user_transfer_type: UserTransferType, signer: Option, + historical_trade: bool, ) -> Result { let permit2 = if user_transfer_type == UserTransferType::TransferFromPermit2 { Some(Permit2::new()?) @@ -66,18 +67,21 @@ impl TychoRouterEncoder { swap_encoder_registry.clone(), user_transfer_type.clone(), router_address.clone(), + historical_trade, )?, sequential_swap_strategy: SequentialSwapStrategyEncoder::new( chain, swap_encoder_registry.clone(), user_transfer_type.clone(), router_address.clone(), + historical_trade, )?, split_swap_strategy: SplitSwapStrategyEncoder::new( chain, swap_encoder_registry, user_transfer_type.clone(), router_address.clone(), + historical_trade, )?, router_address, permit2, @@ -333,6 +337,7 @@ impl TychoExecutorEncoder { group_token_in: grouped_swap.token_in.clone(), group_token_out: grouped_swap.token_out.clone(), transfer_type: transfer, + historical_trade: false, }; let mut grouped_protocol_data: Vec> = vec![]; let mut initial_protocol_data: Vec = vec![]; @@ -402,7 +407,7 @@ impl TychoEncoder for TychoExecutorEncoder { #[cfg(test)] mod tests { - use std::{collections::HashMap, str::FromStr}; + use std::{collections::HashMap, fs, str::FromStr}; use num_bigint::{BigInt, BigUint}; use tycho_common::models::{protocol::ProtocolComponent, Chain}; @@ -486,11 +491,9 @@ mod tests { } fn get_swap_encoder_registry() -> SwapEncoderRegistry { - SwapEncoderRegistry::new( - Some("config/test_executor_addresses.json".to_string()), - eth_chain(), - ) - .unwrap() + let executors_addresses = + fs::read_to_string("config/test_executor_addresses.json").unwrap(); + SwapEncoderRegistry::new(Some(executors_addresses), eth_chain()).unwrap() } fn get_tycho_router_encoder(user_transfer_type: UserTransferType) -> TychoRouterEncoder { @@ -500,6 +503,7 @@ mod tests { router_address(), user_transfer_type, None, + false, ) .unwrap() } diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 48b224c..b7886ba 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -273,6 +273,8 @@ impl PartialEq for PermitDetails { /// * `group_token_in`: Token to be used as the input for the group swap. /// * `group_token_out`: Token to be used as the output for the group swap. /// * `transfer`: Type of transfer to be performed. See `TransferType` for more details. +/// * `historical_trade`: Whether the swap is to be done in the current block or in an historical +/// one. This is relevant for checking token approvals in some protocols (like Balancer v2). #[derive(Clone, Debug)] pub struct EncodingContext { pub receiver: Bytes, @@ -281,6 +283,7 @@ pub struct EncodingContext { pub group_token_in: Bytes, pub group_token_out: Bytes, pub transfer_type: TransferType, + pub historical_trade: bool, } /// Represents the type of transfer to be performed into the pool. diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 0949b7e..a6be289 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] pub mod encoding; -use std::str::FromStr; +use std::{fs, str::FromStr}; use alloy::{ primitives::{B256, U256}, @@ -71,10 +71,11 @@ pub fn get_signer() -> PrivateKeySigner { } pub fn get_tycho_router_encoder(user_transfer_type: UserTransferType) -> Box { + let executors_addresses = fs::read_to_string("config/test_executor_addresses.json").unwrap(); TychoRouterEncoderBuilder::new() .chain(Chain::Ethereum) .user_transfer_type(user_transfer_type) - .executors_file_path("config/test_executor_addresses.json".to_string()) + .executors_addresses(executors_addresses) .router_address(router_address()) .build() .expect("Failed to build encoder")