From 31891397c725f71343787bf8e081fa45174a23a1 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Thu, 13 Mar 2025 11:59:13 -0400 Subject: [PATCH 01/13] feat: ExecutorTransferMethods in UniswapV3Executor --- foundry/test/executors/UniswapV3Executor.t.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index 24bfd22..5da5dea 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -56,8 +56,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { ); pancakeV3Exposed = new UniswapV3ExecutorExposed( PANCAKESWAPV3_DEPLOYER_ETHEREUM, - PANCAKEV3_POOL_CODE_INIT_HASH, - PERMIT2_ADDRESS + PANCAKEV3_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS ); permit2 = IAllowanceTransfer(PERMIT2_ADDRESS); } @@ -134,8 +133,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { int256(0), // amount1Delta dataOffset, dataLength, - protocolData, - address(uniswapV3Exposed) // transferFrom sender (irrelevant in this case) + protocolData ); uniswapV3Exposed.handleCallback(callbackData); vm.stopPrank(); From a37805d0469ee67b2685b7f9b83bdd042bc6d6d9 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Thu, 13 Mar 2025 12:19:55 -0400 Subject: [PATCH 02/13] feat: allow to pass msg.sender to USV3 callback - So that we can possibly do a transferFrom - This should still be safe since the user can't control what is passed here --- foundry/src/executors/ExecutorTransferMethods.sol | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 foundry/src/executors/ExecutorTransferMethods.sol diff --git a/foundry/src/executors/ExecutorTransferMethods.sol b/foundry/src/executors/ExecutorTransferMethods.sol new file mode 100644 index 0000000..e69de29 From e8f56ff08860f3f7d248ba60bfbbd130f3e12082 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 7 Apr 2025 20:42:54 -0400 Subject: [PATCH 03/13] feat: Proper USV2Executor transfer decoding + tests - Properly decode, update tests with proper decoding - Added test case for each transfer method - Also fully tested permit2 transferFrom and it works perfectly. TODO: - Fix integration tests once encoding is implemented. --- foundry/test/executors/UniswapV3Executor.t.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index 5da5dea..819fbbc 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -56,7 +56,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { ); pancakeV3Exposed = new UniswapV3ExecutorExposed( PANCAKESWAPV3_DEPLOYER_ETHEREUM, - PANCAKEV3_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS + PANCAKEV3_POOL_CODE_INIT_HASH, + PERMIT2_ADDRESS ); permit2 = IAllowanceTransfer(PERMIT2_ADDRESS); } From ee7495551ec1304f336f46375a667f40d3e7c66f Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Thu, 10 Apr 2025 11:02:04 -0400 Subject: [PATCH 04/13] chore: Renamings + comment fixes - Renamed ExecutorTransferMethods to TokenTransfer to avoid leaking information about how the transfer happens into the executor. The executor shouldn't care if there are multiple methods or one single method that takes care of everything. - Also renamed TransferMethod to TransferType to match the rust encoding --- foundry/src/executors/ExecutorTransferMethods.sol | 0 foundry/src/executors/TokenTransfer.sol | 1 + foundry/test/executors/UniswapV3Executor.t.sol | 4 +--- 3 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 foundry/src/executors/ExecutorTransferMethods.sol diff --git a/foundry/src/executors/ExecutorTransferMethods.sol b/foundry/src/executors/ExecutorTransferMethods.sol deleted file mode 100644 index e69de29..0000000 diff --git a/foundry/src/executors/TokenTransfer.sol b/foundry/src/executors/TokenTransfer.sol index f529b69..683fedb 100644 --- a/foundry/src/executors/TokenTransfer.sol +++ b/foundry/src/executors/TokenTransfer.sol @@ -50,3 +50,4 @@ contract TokenTransfer { } } } + diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index 819fbbc..19aa02a 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -90,9 +90,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { assertEq(receiver, address(2)); assertEq(target, address(3)); assertEq(zeroForOne, false); - assertEq( - uint8(transferType), uint8(TokenTransfer.TransferType.TRANSFER) - ); + assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.TRANSFER)); } function testDecodeParamsInvalidDataLength() public { From 59a80dc3929ce75910dce13ba837a8c2445048fb Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Wed, 9 Apr 2025 18:02:53 -0400 Subject: [PATCH 05/13] feat: Optimize transfer to first pool TODO: Update integration tests on solidity side, since a few were now updated on the encoding side to use a transferFrom. --- src/encoding/evm/constants.rs | 9 ++ src/encoding/evm/strategy_encoder/mod.rs | 2 + .../evm/strategy_encoder/strategy_encoders.rs | 57 +++++++---- .../transfer_optimizations.rs | 95 +++++++++++++++++++ .../evm/swap_encoder/swap_encoders.rs | 22 ++--- src/encoding/evm/tycho_encoders.rs | 10 +- src/encoding/models.rs | 18 ++++ 7 files changed, 182 insertions(+), 31 deletions(-) create mode 100644 src/encoding/evm/strategy_encoder/transfer_optimizations.rs diff --git a/src/encoding/evm/constants.rs b/src/encoding/evm/constants.rs index feba1cc..ef44482 100644 --- a/src/encoding/evm/constants.rs +++ b/src/encoding/evm/constants.rs @@ -17,3 +17,12 @@ pub static GROUPABLE_PROTOCOLS: LazyLock> = LazyLock::new( set.insert("ekubo_v2"); set }); + +/// These protocols support the optimization of transferring straight from the user. +pub static IN_TRANSFER_OPTIMIZABLE_PROTOCOLS: LazyLock> = + LazyLock::new(|| { + let mut set = HashSet::new(); + set.insert("uniswap_v2"); + set.insert("uniswap_v3"); + set + }); diff --git a/src/encoding/evm/strategy_encoder/mod.rs b/src/encoding/evm/strategy_encoder/mod.rs index 8f53524..260436b 100644 --- a/src/encoding/evm/strategy_encoder/mod.rs +++ b/src/encoding/evm/strategy_encoder/mod.rs @@ -1,2 +1,4 @@ pub mod strategy_encoders; mod strategy_validators; + +mod transfer_optimizations; diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 3151034..e5174af 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -9,8 +9,9 @@ use crate::encoding::{ evm::{ approvals::permit2::Permit2, group_swaps::group_swaps, - strategy_encoder::strategy_validators::{ - SequentialSwapValidator, SplitSwapValidator, SwapValidator, + strategy_encoder::{ + strategy_validators::{SequentialSwapValidator, SplitSwapValidator, SwapValidator}, + transfer_optimizations::TransferOptimization, }, swap_encoder::swap_encoder_registry::SwapEncoderRegistry, utils::{ @@ -69,6 +70,8 @@ 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); @@ -111,12 +114,19 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { let mut grouped_protocol_data: Vec = vec![]; for swap in grouped_swap.swaps.iter() { + let transfer_type = self.get_transfer_method( + swap.clone(), + solution.given_token.clone(), + self.permit2.clone().is_some(), + ); + let encoding_context = EncodingContext { receiver: self.router_address.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); @@ -199,6 +209,8 @@ pub struct SequentialSwapStrategyEncoder { sequential_swap_validator: SequentialSwapValidator, } +impl TransferOptimization for SequentialSwapStrategyEncoder {} + impl SequentialSwapStrategyEncoder { pub fn new( chain: Chain, @@ -274,12 +286,19 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { let mut grouped_protocol_data: Vec = vec![]; for swap in grouped_swap.swaps.iter() { + let transfer_type = self.get_transfer_method( + swap.clone(), + solution.given_token.clone(), + self.permit2.clone().is_some(), + ); + let encoding_context = EncodingContext { receiver: self.router_address.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())?; @@ -415,6 +434,8 @@ impl SplitSwapStrategyEncoder { } } +impl TransferOptimization for SplitSwapStrategyEncoder {} + impl StrategyEncoder for SplitSwapStrategyEncoder { fn encode_strategy(&self, solution: Solution) -> Result<(Vec, Bytes), EncodingError> { self.split_swap_validator @@ -491,12 +512,19 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { let mut grouped_protocol_data: Vec = vec![]; for swap in grouped_swap.swaps.iter() { + let transfer_type = self.get_transfer_method( + swap.clone(), + solution.given_token.clone(), + self.permit2.clone().is_some(), + ); + let encoding_context = EncodingContext { receiver: self.router_address.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())?; @@ -722,7 +750,7 @@ mod tests { "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one - "00", // transfer type + "02", // transfer type "00000000000000", // padding )); let hex_calldata = encode(&calldata); @@ -825,9 +853,8 @@ mod tests { "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one - "00", // exact out - "00", // transfer type - "00000000000000000000000000", // padding + "02", // transfer type + "0000000000000000000000000000", // padding )); let hex_calldata = encode(&calldata); @@ -1484,9 +1511,8 @@ mod tests { "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one - "00", // exact out - "00", // transfer type - "00000000000000000000000000", // padding + "01", // transfer type + "0000000000000000000000000000", // padding ] .join(""); @@ -1568,9 +1594,8 @@ mod tests { "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one - "00", // exact out - "00", // transfer type - "000000000000", // padding + "01", // transfer type + "00000000000000", // padding ] .join(""); @@ -1821,7 +1846,7 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id "01", // zero2one - "00", // transfer type + "02", // transfer type "006e", // ple encoded swaps "01", // token in index "00000000", // split @@ -1975,7 +2000,7 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id "01", // zero2one - "00", // transfer type + "02", // transfer type "006e", // ple encoded swaps "00", // token in index "01", // token out index @@ -1987,7 +2012,7 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id "01", // zero2one - "00", // transfer type + "02", // transfer type "0057", // ple encoded swaps "01", // token in index "00", // token out index @@ -2135,7 +2160,7 @@ mod tests { "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "01", // zero2one - "00", // transfer type + "02", // transfer type "006e", // ple encoded swaps "01", // token in index "00", // token out index diff --git a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs new file mode 100644 index 0000000..e3692ec --- /dev/null +++ b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs @@ -0,0 +1,95 @@ +use tycho_common::Bytes; + +use crate::encoding::{ + evm::constants::IN_TRANSFER_OPTIMIZABLE_PROTOCOLS, + models::{Swap, TransferType}, +}; + +/// A trait that defines how the tokens will be transferred into the given pool given the solution. +pub trait TransferOptimization { + /// Returns the transfer method that should be used for the given swap and solution. + /// + /// If the swap is for the in token of the solution and the protocol supports transferring + /// straight from the user, it will return `TransferType::Permit2Transfer` or + /// `TransferType::TransferFrom`. + fn get_transfer_method(&self, swap: Swap, given_token: Bytes, permit2: bool) -> TransferType { + let optimize_in_transfer = + IN_TRANSFER_OPTIMIZABLE_PROTOCOLS.contains(&swap.component.protocol_system.as_str()); + if (swap.token_in == given_token) && optimize_in_transfer { + if permit2 { + TransferType::Permit2Transfer + } else { + TransferType::TransferFrom + } + } else { + TransferType::Transfer + } + } +} + +#[cfg(test)] +mod tests { + use alloy_primitives::hex; + use tycho_common::{models::protocol::ProtocolComponent, Bytes}; + + use super::*; + + struct MockStrategy {} + impl TransferOptimization for MockStrategy {} + + fn weth() -> Bytes { + Bytes::from(hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").to_vec()) + } + + fn dai() -> Bytes { + Bytes::from(hex!("6b175474e89094c44da98b954eedeac495271d0f").to_vec()) + } + + #[test] + fn test_first_swap_transfer_from_permit2() { + let swap = Swap { + component: ProtocolComponent { + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth(), + token_out: dai(), + split: 0f64, + }; + let strategy = MockStrategy {}; + let transfer_method = strategy.get_transfer_method(swap.clone(), weth(), true); + assert_eq!(transfer_method, TransferType::Permit2Transfer); + } + + #[test] + fn test_first_swap_transfer_from() { + let swap = Swap { + component: ProtocolComponent { + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth(), + token_out: dai(), + split: 0f64, + }; + let strategy = MockStrategy {}; + let transfer_method = strategy.get_transfer_method(swap.clone(), weth(), false); + assert_eq!(transfer_method, TransferType::TransferFrom); + } + + #[test] + fn test_first_swap_transfer() { + let swap = Swap { + component: ProtocolComponent { + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth(), + token_out: dai(), + split: 0f64, + }; + let strategy = MockStrategy {}; + let transfer_method = strategy.get_transfer_method(swap.clone(), dai(), false); + assert_eq!(transfer_method, TransferType::Transfer); + } +} diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index bdc170e..b05199a 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -22,15 +22,6 @@ use crate::encoding::{ swap_encoder::SwapEncoder, }; -#[allow(dead_code)] -#[repr(u8)] -pub enum TransferType { - Transfer = 0, - TransferFrom = 1, - Permit2Transfer = 2, - None = 3, -} - /// Encodes a swap on a Uniswap V2 pool through the given executor address. /// /// # Fields @@ -75,7 +66,7 @@ impl SwapEncoder for UniswapV2SwapEncoder { component_id, bytes_to_address(&encoding_context.receiver)?, zero_to_one, - (TransferType::Transfer as u8).to_be_bytes(), + (encoding_context.transfer_type as u8).to_be_bytes(), ); Ok(args.abi_encode_packed()) @@ -138,7 +129,7 @@ impl SwapEncoder for UniswapV3SwapEncoder { bytes_to_address(&encoding_context.receiver)?, component_id, zero_to_one, - (TransferType::Transfer as u8).to_be_bytes(), + (encoding_context.transfer_type as u8).to_be_bytes(), ); Ok(args.abi_encode_packed()) @@ -573,6 +564,7 @@ mod tests { }; use super::*; + use crate::encoding::models::TransferType; #[test] fn test_encode_uniswap_v2() { @@ -595,6 +587,7 @@ mod tests { router_address: Some(Bytes::zero(20)), group_token_in: token_in.clone(), group_token_out: token_out.clone(), + transfer_type: TransferType::Transfer, }; let encoder = UniswapV2SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -648,6 +641,7 @@ mod tests { router_address: Some(Bytes::zero(20)), group_token_in: token_in.clone(), group_token_out: token_out.clone(), + transfer_type: TransferType::Transfer, }; let encoder = UniswapV3SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -702,6 +696,7 @@ mod tests { router_address: Some(Bytes::zero(20)), group_token_in: token_in.clone(), group_token_out: token_out.clone(), + transfer_type: TransferType::Transfer, }; let encoder = BalancerV2SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -768,6 +763,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), + transfer_type: TransferType::Transfer, }; let encoder = UniswapV4SwapEncoder::new( String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"), @@ -834,6 +830,7 @@ mod tests { group_token_in: group_token_in.clone(), // Token out is the same as the group token out group_token_out: token_out.clone(), + transfer_type: TransferType::Transfer, }; let encoder = UniswapV4SwapEncoder::new( @@ -876,6 +873,7 @@ mod tests { router_address: Some(router_address.clone()), group_token_in: usde_address.clone(), group_token_out: wbtc_address.clone(), + transfer_type: TransferType::Transfer, }; // Setup - First sequence: USDE -> USDT @@ -1004,6 +1002,7 @@ mod tests { group_token_out: token_out.clone(), exact_out: false, router_address: Some(Bytes::default()), + transfer_type: TransferType::Transfer, }; let encoder = @@ -1046,6 +1045,7 @@ mod tests { group_token_out: group_token_out.clone(), exact_out: false, router_address: Some(Bytes::default()), + transfer_type: TransferType::Transfer, }; let first_swap = Swap { diff --git a/src/encoding/evm/tycho_encoders.rs b/src/encoding/evm/tycho_encoders.rs index 92706fc..6ec4e36 100644 --- a/src/encoding/evm/tycho_encoders.rs +++ b/src/encoding/evm/tycho_encoders.rs @@ -12,7 +12,7 @@ use crate::encoding::{ }, swap_encoder::swap_encoder_registry::SwapEncoderRegistry, }, - models::{Chain, EncodingContext, NativeAction, Solution, Transaction}, + models::{Chain, EncodingContext, NativeAction, Solution, Transaction, TransferType}, strategy_encoder::StrategyEncoder, tycho_encoder::TychoEncoder, }; @@ -201,9 +201,10 @@ impl TychoEncoder for TychoRouterEncoder { } } -/// Represents an encoder for one swap to be executed directly against an Executor. This is useful -/// when you want to bypass the Tycho Router, use your own Router contract and just need the -/// calldata for a particular swap. +/// Represents an encoder for one swap to be executed directly against an Executor. +/// +/// This is useful when you want to bypass the Tycho Router, use your own Router contract and +/// just need the calldata for a particular swap. /// /// # Fields /// * `swap_encoder_registry`: Registry of swap encoders @@ -259,6 +260,7 @@ impl TychoExecutorEncoder { router_address: None, group_token_in: grouped_swap.input_token.clone(), group_token_out: grouped_swap.output_token.clone(), + transfer_type: TransferType::Transfer, }; let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?; grouped_protocol_data.extend(protocol_data); diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 9da64dd..7f33009 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -96,6 +96,23 @@ pub struct Transaction { pub data: Vec, } +/// Represents the type of transfer to be performed into the pool. +/// +/// # Fields +/// +/// * `Transfer`: Transfer the token from the router into the pool. +/// * `TransferFrom`: Transfer the token from the swapper to the pool. +/// * `Permit2Transfer`: Transfer the token from the sender to the pool using Permit2. +/// * `None`: No transfer is needed. Tokens are already in the pool. +#[repr(u8)] +#[derive(Clone, Debug, PartialEq)] +pub enum TransferType { + Transfer = 0, + TransferFrom = 1, + Permit2Transfer = 2, + None = 3, +} + /// Represents necessary attributes for encoding an order. /// /// # Fields @@ -113,6 +130,7 @@ pub struct EncodingContext { pub router_address: Option, pub group_token_in: Bytes, pub group_token_out: Bytes, + pub transfer_type: TransferType, } #[derive(Clone, PartialEq, Eq, Hash)] From a301a1cef3897ca1feeb6ec2245a2602fe649c9c Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Fri, 11 Apr 2025 23:25:45 -0400 Subject: [PATCH 06/13] feat: (WIP) Support selection of transfer into router - For protocols like Balancer and Curve, which expect funds to be in the router at the time of swap, we must support also transferring funds from the user into the router. Doing this in the router would mean we are dealing with transfers in two different places: in the router main methods and in the executors. To avoid this, we are now performing transfers just in the executors, and two transfer types have been added to support transfers into the router. TODO: - Add this for Balancer and Curve (only added for USV4 atm). - TODO consider renaming TRANSFER_FROM and TRANSFER_PERMIT2 to include "pool" in the name --- foundry/src/TychoRouter.sol | 71 +------ foundry/src/executors/TokenTransfer.sol | 34 ++- foundry/src/executors/UniswapV2Executor.sol | 4 +- foundry/src/executors/UniswapV3Executor.sol | 2 +- foundry/src/executors/UniswapV4Executor.sol | 24 ++- foundry/test/TychoRouterIntegration.t.sol | 24 +-- foundry/test/TychoRouterSequentialSwap.t.sol | 91 ++++++-- foundry/test/TychoRouterSingleSwap.t.sol | 49 ++++- foundry/test/TychoRouterSplitSwap.t.sol | 195 ++++++++++-------- foundry/test/TychoRouterTestSetup.sol | 19 +- .../test/executors/UniswapV2Executor.t.sol | 4 +- .../test/executors/UniswapV4Executor.t.sol | 39 +++- foundry/test/executors/UniswapV4Utils.sol | 9 +- src/encoding/evm/constants.rs | 11 + .../evm/strategy_encoder/strategy_encoders.rs | 20 +- .../transfer_optimizations.rs | 49 +++-- .../evm/swap_encoder/swap_encoders.rs | 13 +- src/encoding/evm/tycho_encoders.rs | 2 + src/encoding/models.rs | 6 +- 19 files changed, 426 insertions(+), 240 deletions(-) diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index e6c798b..4221d79 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -99,7 +99,7 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { /** * @notice Executes a swap operation based on a predefined swap graph, supporting internal token amount splits. * This function enables multi-step swaps, optional ETH wrapping/unwrapping, and validates the output amount - * against a user-specified minimum. This function performs a transferFrom to retrieve tokens from the caller. + * against a user-specified minimum. * * @dev * - If `wrapEth` is true, the contract wraps the provided native ETH into WETH and uses it as the sell token. @@ -130,11 +130,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { address receiver, bytes calldata swaps ) public payable whenNotPaused nonReentrant returns (uint256 amountOut) { - if (address(tokenIn) != address(0)) { - IERC20(tokenIn).safeTransferFrom( - msg.sender, address(this), amountIn - ); - } return _splitSwapChecked( amountIn, tokenIn, @@ -187,15 +182,9 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { bytes calldata signature, bytes calldata swaps ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { - // For native ETH, assume funds already in our router. Else, transfer and handle approval. + // For native ETH, assume funds already in our router. Else, handle approval. if (tokenIn != address(0)) { permit2.permit(msg.sender, permitSingle, signature); - permit2.transferFrom( - msg.sender, - address(this), - uint160(amountIn), - permitSingle.details.token - ); } return _splitSwapChecked( @@ -214,7 +203,7 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { /** * @notice Executes a swap operation based on a predefined swap graph with no split routes. * This function enables multi-step swaps, optional ETH wrapping/unwrapping, and validates the output amount - * against a user-specified minimum. This function performs a transferFrom to retrieve tokens from the caller. + * against a user-specified minimum. * * @dev * - If `wrapEth` is true, the contract wraps the provided native ETH into WETH and uses it as the sell token. @@ -243,7 +232,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { address receiver, bytes calldata swaps ) public payable whenNotPaused nonReentrant returns (uint256 amountOut) { - IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn); return _sequentialSwapChecked( amountIn, tokenIn, @@ -292,15 +280,9 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { bytes calldata signature, bytes calldata swaps ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { - // For native ETH, assume funds already in our router. Else, transfer and handle approval. + // For native ETH, assume funds already in our router. Else, handle approval. if (tokenIn != address(0)) { permit2.permit(msg.sender, permitSingle, signature); - permit2.transferFrom( - msg.sender, - address(this), - uint160(amountIn), - permitSingle.details.token - ); } return _sequentialSwapChecked( @@ -318,7 +300,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { /** * @notice Executes a single swap operation. * This function enables optional ETH wrapping/unwrapping, and validates the output amount against a user-specified minimum. - * This function performs a transferFrom to retrieve tokens from the caller. * * @dev * - If `wrapEth` is true, the contract wraps the provided native ETH into WETH and uses it as the sell token. @@ -346,7 +327,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { address receiver, bytes calldata swapData ) public payable whenNotPaused nonReentrant returns (uint256 amountOut) { - IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn); return _singleSwap( amountIn, tokenIn, @@ -395,15 +375,9 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { bytes calldata signature, bytes calldata swapData ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { - // For native ETH, assume funds already in our router. Else, transfer and handle approval. + // For native ETH, assume funds already in our router. Else, handle approval. if (tokenIn != address(0)) { permit2.permit(msg.sender, permitSingle, signature); - permit2.transferFrom( - msg.sender, - address(this), - uint160(amountIn), - permitSingle.details.token - ); } return _singleSwap( @@ -449,23 +423,7 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { _wrapETH(amountIn); tokenIn = address(_weth); } - - uint256 initialBalance = tokenIn == address(0) - ? address(this).balance - : IERC20(tokenIn).balanceOf(address(this)); - amountOut = _splitSwap(amountIn, nTokens, swaps); - uint256 currentBalance = tokenIn == address(0) - ? address(this).balance - : IERC20(tokenIn).balanceOf(address(this)); - - uint256 amountConsumed = initialBalance - currentBalance; - - if (tokenIn != tokenOut && amountConsumed != amountIn) { - revert TychoRouter__AmountInDiffersFromConsumed( - amountIn, amountConsumed - ); - } if (amountOut < minAmountOut) { revert TychoRouter__NegativeSlippage(amountOut, minAmountOut); @@ -512,25 +470,10 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { tokenIn = address(_weth); } - uint256 initialBalance = tokenIn == address(0) - ? address(this).balance - : IERC20(tokenIn).balanceOf(address(this)); - (address executor, bytes calldata protocolData) = swap_.decodeSingleSwap(); amountOut = _callExecutor(executor, amountIn, protocolData); - uint256 currentBalance = tokenIn == address(0) - ? address(this).balance - : IERC20(tokenIn).balanceOf(address(this)); - - uint256 amountConsumed = initialBalance - currentBalance; - - if (amountConsumed != amountIn) { - revert TychoRouter__AmountInDiffersFromConsumed( - amountIn, amountConsumed - ); - } if (amountOut < minAmountOut) { revert TychoRouter__NegativeSlippage(amountOut, minAmountOut); @@ -577,10 +520,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { tokenIn = address(_weth); } - uint256 initialBalance = tokenIn == address(0) - ? address(this).balance - : IERC20(tokenIn).balanceOf(address(this)); - amountOut = _sequentialSwap(amountIn, swaps); uint256 currentBalance = tokenIn == address(0) diff --git a/foundry/src/executors/TokenTransfer.sol b/foundry/src/executors/TokenTransfer.sol index 683fedb..0fb1f6d 100644 --- a/foundry/src/executors/TokenTransfer.sol +++ b/foundry/src/executors/TokenTransfer.sol @@ -16,9 +16,17 @@ contract TokenTransfer { // Assume funds are in the TychoRouter - transfer into the pool TRANSFER, // Assume funds are in msg.sender's wallet - transferFrom into the pool - TRANSFERFROM, + TRANSFER_FROM, // Assume funds are in msg.sender's wallet - permit2TransferFrom into the pool - TRANSFERPERMIT2, + TRANSFER_PERMIT2, + // Assume funds are in msg.sender's wallet - but the pool requires it to be + // in the router contract when calling swap - transferFrom into the router + // contract + TRANSFER_TO_ROUTER, + // Assume funds are in msg.sender's wallet - but the pool requires it to be + // in the router contract when calling swap - transferFrom into the router + // contract using permit2 + TRANSFER_PERMIT2_TO_ROUTER, // Assume funds have already been transferred into the pool. Do nothing. NONE } @@ -31,21 +39,31 @@ contract TokenTransfer { } function _transfer( - IERC20 tokenIn, + address tokenIn, address sender, address receiver, uint256 amount, TransferType transferType ) internal { if (transferType == TransferType.TRANSFER) { - tokenIn.safeTransfer(receiver, amount); - } else if (transferType == TransferType.TRANSFERFROM) { + if (tokenIn == address(0)) { + payable(receiver).transfer(amount); + } else { + IERC20(tokenIn).safeTransfer(receiver, amount); + } + } else if (transferType == TransferType.TRANSFER_FROM) { // slither-disable-next-line arbitrary-send-erc20 - tokenIn.safeTransferFrom(sender, receiver, amount); - } else if (transferType == TransferType.TRANSFERPERMIT2) { + IERC20(tokenIn).safeTransferFrom(sender, receiver, amount); + } else if (transferType == TransferType.TRANSFER_PERMIT2) { + // Permit2.permit is already called from the TychoRouter + permit2.transferFrom(sender, receiver, uint160(amount), tokenIn); + } else if (transferType == TransferType.TRANSFER_TO_ROUTER) { + // slither-disable-next-line arbitrary-send-erc20 + IERC20(tokenIn).safeTransferFrom(sender, address(this), amount); + } else if (transferType == TransferType.TRANSFER_PERMIT2_TO_ROUTER) { // Permit2.permit is already called from the TychoRouter permit2.transferFrom( - sender, receiver, uint160(amount), address(tokenIn) + sender, address(this), uint160(amount), tokenIn ); } } diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index 2ddcd97..863216d 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -50,7 +50,9 @@ contract UniswapV2Executor is IExecutor, TokenTransfer { _verifyPairAddress(target); calculatedAmount = _getAmountOut(target, givenAmount, zeroForOne); - _transfer(tokenIn, msg.sender, target, givenAmount, transferType); + _transfer( + address(tokenIn), msg.sender, target, givenAmount, transferType + ); IUniswapV2Pair pool = IUniswapV2Pair(target); if (zeroForOne) { diff --git a/foundry/src/executors/UniswapV3Executor.sol b/foundry/src/executors/UniswapV3Executor.sol index dbc794e..4609bc1 100644 --- a/foundry/src/executors/UniswapV3Executor.sol +++ b/foundry/src/executors/UniswapV3Executor.sol @@ -111,7 +111,7 @@ contract UniswapV3Executor is IExecutor, ICallback, TokenTransfer { uint256 amountOwed = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); - _transfer(IERC20(tokenIn), sender, msg.sender, amountOwed, transferType); + _transfer(tokenIn, sender, msg.sender, amountOwed, transferType); return abi.encode(amountOwed, tokenIn); } diff --git a/foundry/src/executors/UniswapV4Executor.sol b/foundry/src/executors/UniswapV4Executor.sol index 694937f..adcea98 100644 --- a/foundry/src/executors/UniswapV4Executor.sol +++ b/foundry/src/executors/UniswapV4Executor.sol @@ -17,10 +17,11 @@ import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol"; import {IV4Router} from "@uniswap/v4-periphery/src/interfaces/IV4Router.sol"; import {PathKey} from "@uniswap/v4-periphery/src/libraries/PathKey.sol"; import {ICallback} from "@interfaces/ICallback.sol"; +import {TokenTransfer} from "./TokenTransfer.sol"; error UniswapV4Executor__InvalidDataLength(); -contract UniswapV4Executor is IExecutor, V4Router, ICallback { +contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer { using SafeERC20 for IERC20; using CurrencyLibrary for Currency; @@ -30,7 +31,10 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback { int24 tickSpacing; } - constructor(IPoolManager _poolManager) V4Router(_poolManager) {} + constructor(IPoolManager _poolManager, address _permit2) + V4Router(_poolManager) + TokenTransfer(_permit2) + {} function swap(uint256 amountIn, bytes calldata data) external @@ -41,9 +45,19 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback { address tokenIn, address tokenOut, bool zeroForOne, + TransferType transferType, UniswapV4Executor.UniswapV4Pool[] memory pools ) = _decodeData(data); + // TODO move this into callback when we implement callback transfer type support + _transfer( + tokenIn, + msg.sender, + address(this), // irrelevant attribute + amountIn, + transferType + ); + bytes memory swapData; if (pools.length == 1) { PoolKey memory key = PoolKey({ @@ -138,6 +152,7 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback { address tokenIn, address tokenOut, bool zeroForOne, + TransferType transferType, UniswapV4Pool[] memory pools ) { @@ -148,10 +163,11 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback { tokenIn = address(bytes20(data[0:20])); tokenOut = address(bytes20(data[20:40])); zeroForOne = (data[40] != 0); + transferType = TransferType(uint8(data[41])); - uint256 poolsLength = (data.length - 41) / 26; // 26 bytes per pool object + uint256 poolsLength = (data.length - 42) / 26; // 26 bytes per pool object pools = new UniswapV4Pool[](poolsLength); - bytes memory poolsData = data[41:]; + bytes memory poolsData = data[42:]; uint256 offset = 0; for (uint256 i = 0; i < poolsLength; i++) { address intermediaryToken; diff --git a/foundry/test/TychoRouterIntegration.t.sol b/foundry/test/TychoRouterIntegration.t.sol index 29f2cb9..7f9a983 100644 --- a/foundry/test/TychoRouterIntegration.t.sol +++ b/foundry/test/TychoRouterIntegration.t.sol @@ -12,7 +12,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { uint256 balancerBefore = IERC20(DAI_ADDR).balanceOf(ALICE); // Encoded solution generated using `test_split_swap_strategy_encoder_no_permit2` (bool success,) = tychoRouterAddr.call( - hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae37400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000059005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" + hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae37400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000059005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395000100000000000000" ); vm.stopPrank(); @@ -37,7 +37,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_split_encoding_strategy_usv4` (bool success,) = tychoRouterAddr.call( - hex"7c553846000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000000000000000681f1d6200000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f7976a0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000416af8ab519cbe8f466aed6188c8966ca4910d72d40d2f4360f62dbe75864b77ce5ec168870bb1540673b3f720c4f2bfaa5de2a79813c857ad5cc49b20217c65041c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007800760001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d2311933000000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000" + hex"7c553846000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000000000000000682163b600000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9ddbe000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041e9e58e3facf99cd2c64b834d3b646b8cf9377c47540d65b5e180a06bca6f42851cf320a205cf466c7943abe45c2998afa6fd3d870043a108578e71256831ca1c1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d008b0001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d231193300f62849f9a0b5bf2913b396098f7c7019b51a820a040000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f400000000000000000000000000000000000000" ); vm.stopPrank(); @@ -60,7 +60,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { // Encoded solution generated using `test_split_encoding_strategy_usv4_eth_in` (bool success,) = tychoRouterAddr.call{value: 1 ether}( - hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933000000000000000000000000000000000000000000c87c939ae635f92dc2379c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000681f1d7100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f79779000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041c9ad125cc7c7188d1f0cd61dd8ee0d752d45c89ade1df275c5e0ce17525c6ff137bd976ebd0d17223c007e1f0a2806d086b4c7015460ebd21fef8a6caf8368d51c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e005c0001000000f62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933016982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000" + hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933000000000000000000000000000000000000000000c87c939ae635f92dc2379c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006821689800000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9e2a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000412da8d5aab101bbdf256d785a42db176328e8298ee6d0906e0ef1998cfcaa332460f8409d9b298dff73c947796a22c8de21caa17405ea157ced090da2b6cb27431c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007300710001000000f62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d231193301f62849f9a0b5bf2913b396098f7c7019b51a820a056982508145454ce325ddbe47a25d4ec3d23119330061a80001f400000000000000000000000000" ); vm.stopPrank(); @@ -87,7 +87,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { // Encoded solution generated using `test_split_encoding_strategy_usv4_eth_out` (bool success,) = tychoRouterAddr.call( - hex"7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e0000000000000000000000000000000000000000000000000000000000681f1d7e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f797860000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000412beedebe0200a3d4ca03f34697af8ec027fe6c164e3d3b78e17d1c327dcfc8241ce8bda513f092e54b35856637e5e485a06cc8c1c6287f15f469d47e84fd91341b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e005c0001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000000000000000000000000000bb800003c0000" + hex"7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e0000000000000000000000000000000000000000000000000000000000682163ee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9ddf60000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000416056d925e7906c11b865992ac5c853532f5058bb57b67cd000a53b899503dd8a6fd4c0e5ea44c1ca4137753589bf89f66824796e719e807adee7567a707ee6681b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007300710001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000f62849f9a0b5bf2913b396098f7c7019b51a820a040000000000000000000000000000000000000000000bb800003c00000000000000000000000000" ); vm.stopPrank(); @@ -109,7 +109,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { vm.startPrank(ALICE); // Encoded solution generated using `test_split_swap_strategy_encoder_wrap` (bool success,) = tychoRouterAddr.call{value: 1 ether}( - hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000681e435000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6bd580000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000415563c90eb0f8a79e234e300520b50879242b42622cabd5d01c19e67aba4854a723e0bd8333774062ae03a22b8c5de4a0dfd70bffd9197f56b2063f390610e5891b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000059005700020000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" + hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006821640400000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9de0c000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041b34e1f3d4e78942b2429b776073a5dfab1420f763de7d7e2a2296ca8abf684f923f7ae7945e824d8a084b9610d33ed49246a36e8e0efbce8ae210b0474f9fe3a1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000059005700020000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" ); vm.stopPrank(); @@ -131,7 +131,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(DAI_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_split_swap_strategy_encoder_unwrap` (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000a2a15d09519be000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000a2a15d09519be0000000000000000000000000000000000000000000000000000000000000681e436b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6bd730000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000411150ac5d21c61fcd35b486034a433e44d045950a52e991a7ad4035f3ad52beee5d9ecbc5ed9c370730cd2631a050fbe1219dd606e39c8aca40d588aa3eab03411c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000059005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395010000000000000000" + hex"7c5538460000000000000000000000000000000000000000000000a2a15d09519be000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000a2a15d09519be00000000000000000000000000000000000000000000000000000000000006821641200000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9de1a00000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004181d23336d0cacd47a4d228590a825a1d92a48378cd481ff308b6d235e14b925c584f31e420682879bea58363ca4aa44a3b79557b15a9f73078a4696e00f55f911b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000059005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395010200000000000000" ); vm.stopPrank(); @@ -183,7 +183,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_split_swap_strategy_encoder_complex` (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000681e2d8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6a793000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041e9868ce97a4c09488710ce748362ca38418bda44148bb5faf7820445d47efaff66f8f3667a0b3091c9d67d240db4cc2c95db27bdf019e4d5ff76b7b6620514691b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164005700028000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950000005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950000005702030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fae461ca67b15dc8dc81ce7615e0320da1a9ab8d53ede3eca2a72b3aecc820e955b36f38437d013950100005701030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d01395010000000000000000000000000000000000000000000000000000000000" + hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006821643700000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9de3f00000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004112f41a590796702b322fa5a9ee1602daef9b22d732e4fd8f122f072b65dda325271f630759db500db8a42bd4f41ddc18ddda63650deaf36228dca702e28eefd31b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164005700028000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950002005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950002005702030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fae461ca67b15dc8dc81ce7615e0320da1a9ab8d53ede3eca2a72b3aecc820e955b36f38437d013950100005701030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d01395010000000000000000000000000000000000000000000000000000000000" ); vm.stopPrank(); @@ -210,7 +210,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_sequential_swap_strategy_encoder_complex_route` (bool success,) = tychoRouterAddr.call( - hex"51bcc7b60000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000681e493d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6c34500000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000004189757b528c9874627ca7ecd64bd662db5fd7855837ec98cca5cff2cbd599f9af15baff1d389b5de4ca0dc1106b9d3795a1695934cd5fa1158fffcc8d6495dfaa1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d01395000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d013950100000000000000000000000000000000000000000000000000" + hex"51bcc7b60000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006821644a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9de5200000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041530bdcde0c687eacb51a339f30c7b3eff7c078a3bbd4bc852519568dcdf271bb4c6ac05583f32c4a8d1a99be3a2817fe86c15ad2a06c5cf938bde9c22bc80f301c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d01395000200525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d013950100000000000000000000000000000000000000000000000000" ); vm.stopPrank(); @@ -234,7 +234,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max); // Encoded solution generated using `test_sequential_swap_strategy_encoder_no_permit2` (bool success,) = tychoRouterAddr.call( - hex"e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d01395000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d013950100000000000000000000000000000000000000000000000000" + hex"e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d01395000100525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d013950100000000000000000000000000000000000000000000000000" ); vm.stopPrank(); @@ -254,7 +254,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_cyclic_sequential_swap` (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f4308e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000681e4a0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6c40a0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000416a11f27f0546e9cc9d27e4383a3f860d9822f0d7d0117e73abbf03007b3e235b36c2288ff083a04f51285092ff23422b3e00a5a292d5627172dfd031308fc3a11b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400100006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80000" + hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f4308e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006821647000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9de780000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000413c8b048dc7b7614106a5aa1fa13e48c02a6a9714dfa07d2c424f68b81a5f828c39ace62f2dd57d7bfad10910ae44f77d68aec5c079fce456028b1bd7f72053151c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400102006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80000" ); assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99889294); @@ -270,7 +270,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_split_input_cyclic_swap` (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ef619b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000681e2d1600000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6a71e00000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004129478d2bffac3e378054d024ca3461f61e55552e2e8b0c7e0869f38ee834462f157f761e1a67c713f5749a6f6210dc4ac998a0521dda8dcb780b35d774250a141c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139006e00019999992e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400100006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80100005701000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" + hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ef619b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006821659d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9dfa5000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041dd84c5cdc51719e377598eccd8eac0aae036e7e0745a7c65b5d44cc817071a7460ccc73934363f33cc7af71dc07545aeff1d92f8c2f0b2973e1fc37e7b2de3551c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139006e00019999992e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400102006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80102005701000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" ); assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99574171); @@ -286,7 +286,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_split_output_cyclic_swap` (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005eea514000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000681e2d3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6a738000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041196745237299bfab79f038c0c12decec9c46b264621e1f00bbef32b15a22f15543defe95694dd1616aa8785bbb6f91a1557eebc97c0f5ca968c99f250da1b2ce1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d013950100006e01009999992e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400000006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d8000000000000000000" + hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005eea514000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000682165ac00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9dfb400000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004107f2b0f9c2e4e308ab43b288d69de30d84b10c8075e4dd9a2cf66594f97a52fb34de2534b89bf1887da74c92fd03464f45baff700dd32e213e3add1a3f351e891b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d013950102006e01009999992e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400000006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d8000000000000000000" ); assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99525908); diff --git a/foundry/test/TychoRouterSequentialSwap.t.sol b/foundry/test/TychoRouterSequentialSwap.t.sol index 46a9936..031f66c 100644 --- a/foundry/test/TychoRouterSequentialSwap.t.sol +++ b/foundry/test/TychoRouterSequentialSwap.t.sol @@ -8,23 +8,38 @@ import "./executors/UniswapV4Utils.sol"; import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol"; contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { - function _getSequentialSwaps() internal view returns (bytes[] memory) { + function _getSequentialSwaps(bool permit2) + internal + view + returns (bytes[] memory) + { // Trade 1 WETH for USDC through DAI with 2 swaps on Uniswap V2 // 1 WETH -> DAI -> USDC // (univ2) (univ2) + + TokenTransfer.TransferType transferType = permit2 + ? TokenTransfer.TransferType.TRANSFER_PERMIT2 + : TokenTransfer.TransferType.TRANSFER_FROM; + bytes[] memory swaps = new bytes[](2); // WETH -> DAI swaps[0] = encodeSequentialSwap( address(usv2Executor), encodeUniswapV2Swap( - WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false + WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false, transferType ) ); // DAI -> USDC swaps[1] = encodeSequentialSwap( address(usv2Executor), - encodeUniswapV2Swap(DAI_ADDR, DAI_USDC_POOL, tychoRouterAddr, true) + encodeUniswapV2Swap( + DAI_ADDR, + DAI_USDC_POOL, + tychoRouterAddr, + true, + TokenTransfer.TransferType.TRANSFER + ) ); return swaps; } @@ -32,10 +47,13 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { function testSequentialSwapInternalMethod() public { // Trade 1 WETH for USDC through DAI - see _getSequentialSwaps for more info uint256 amountIn = 1 ether; - deal(WETH_ADDR, tychoRouterAddr, amountIn); + deal(WETH_ADDR, ALICE, amountIn); + vm.startPrank(ALICE); + IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSequentialSwaps(); + bytes[] memory swaps = _getSequentialSwaps(false); tychoRouter.exposedSequentialSwap(amountIn, pleEncode(swaps)); + vm.stopPrank(); uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(tychoRouterAddr); assertEq(usdcBalance, 2644659787); @@ -53,7 +71,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { bytes memory signature ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSequentialSwaps(); + bytes[] memory swaps = _getSequentialSwaps(true); tychoRouter.sequentialSwapPermit2( amountIn, WETH_ADDR, @@ -80,7 +98,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSequentialSwaps(); + bytes[] memory swaps = _getSequentialSwaps(false); tychoRouter.sequentialSwap( amountIn, WETH_ADDR, @@ -105,7 +123,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSequentialSwaps(); + bytes[] memory swaps = _getSequentialSwaps(false); vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector); tychoRouter.sequentialSwap( amountIn, @@ -127,7 +145,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn - 1); - bytes[] memory swaps = _getSequentialSwaps(); + bytes[] memory swaps = _getSequentialSwaps(false); vm.expectRevert(); tychoRouter.sequentialSwap( amountIn, @@ -152,7 +170,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { bytes memory signature ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSequentialSwaps(); + bytes[] memory swaps = _getSequentialSwaps(true); uint256 minAmountOut = 3000 * 1e18; @@ -195,7 +213,30 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { sigDeadline: 0 }); - bytes[] memory swaps = _getSequentialSwaps(); + bytes[] memory swaps = new bytes[](2); + // WETH -> DAI + swaps[0] = encodeSequentialSwap( + address(usv2Executor), + encodeUniswapV2Swap( + WETH_ADDR, + WETH_DAI_POOL, + tychoRouterAddr, + false, + TokenTransfer.TransferType.TRANSFER + ) + ); + + // DAI -> USDC + swaps[1] = encodeSequentialSwap( + address(usv2Executor), + encodeUniswapV2Swap( + DAI_ADDR, + DAI_USDC_POOL, + tychoRouterAddr, + true, + TokenTransfer.TransferType.TRANSFER + ) + ); uint256 amountOut = tychoRouter.sequentialSwapPermit2{value: amountIn}( amountIn, @@ -237,14 +278,24 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { swaps[0] = encodeSequentialSwap( address(usv2Executor), encodeUniswapV2Swap( - USDC_ADDR, DAI_USDC_POOL, tychoRouterAddr, false + USDC_ADDR, + DAI_USDC_POOL, + tychoRouterAddr, + false, + TokenTransfer.TransferType.TRANSFER_PERMIT2 ) ); // DAI -> WETH swaps[1] = encodeSequentialSwap( address(usv2Executor), - encodeUniswapV2Swap(DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true) + encodeUniswapV2Swap( + DAI_ADDR, + WETH_DAI_POOL, + tychoRouterAddr, + true, + TokenTransfer.TransferType.TRANSFER + ) ); uint256 amountOut = tychoRouter.sequentialSwapPermit2( @@ -275,11 +326,21 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { deal(USDC_ADDR, tychoRouterAddr, amountIn); bytes memory usdcWethV3Pool1ZeroOneData = encodeUniswapV3Swap( - USDC_ADDR, WETH_ADDR, tychoRouterAddr, USDC_WETH_USV3, true + USDC_ADDR, + WETH_ADDR, + tychoRouterAddr, + USDC_WETH_USV3, + true, + TokenTransfer.TransferType.TRANSFER ); bytes memory usdcWethV3Pool2OneZeroData = encodeUniswapV3Swap( - WETH_ADDR, USDC_ADDR, tychoRouterAddr, USDC_WETH_USV3_2, false + WETH_ADDR, + USDC_ADDR, + tychoRouterAddr, + USDC_WETH_USV3_2, + false, + TokenTransfer.TransferType.TRANSFER ); bytes[] memory swaps = new bytes[](2); diff --git a/foundry/test/TychoRouterSingleSwap.t.sol b/foundry/test/TychoRouterSingleSwap.t.sol index 246a95c..2c85586 100644 --- a/foundry/test/TychoRouterSingleSwap.t.sol +++ b/foundry/test/TychoRouterSingleSwap.t.sol @@ -22,7 +22,11 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap( - WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false + WETH_ADDR, + WETH_DAI_POOL, + tychoRouterAddr, + false, + TokenTransfer.TransferType.TRANSFER_PERMIT2 ); bytes memory swap = @@ -59,7 +63,11 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn); bytes memory protocolData = encodeUniswapV2Swap( - WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false + WETH_ADDR, + WETH_DAI_POOL, + tychoRouterAddr, + false, + TokenTransfer.TransferType.TRANSFER_FROM ); bytes memory swap = @@ -96,7 +104,11 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn); bytes memory protocolData = encodeUniswapV2Swap( - WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false + WETH_ADDR, + WETH_DAI_POOL, + tychoRouterAddr, + false, + TokenTransfer.TransferType.TRANSFER_FROM ); bytes memory swap = @@ -118,7 +130,11 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn - 1); bytes memory protocolData = encodeUniswapV2Swap( - WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false + WETH_ADDR, + WETH_DAI_POOL, + tychoRouterAddr, + false, + TokenTransfer.TransferType.TRANSFER_FROM ); bytes memory swap = @@ -149,7 +165,11 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn); bytes memory protocolData = encodeUniswapV2Swap( - WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false + WETH_ADDR, + WETH_DAI_POOL, + tychoRouterAddr, + false, + TokenTransfer.TransferType.TRANSFER_FROM ); bytes memory swap = @@ -194,7 +214,11 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { }); bytes memory protocolData = encodeUniswapV2Swap( - WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false + WETH_ADDR, + WETH_DAI_POOL, + tychoRouterAddr, + false, + TokenTransfer.TransferType.TRANSFER ); bytes memory swap = @@ -232,8 +256,13 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { bytes memory signature ) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn); - bytes memory protocolData = - encodeUniswapV2Swap(DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true); + bytes memory protocolData = encodeUniswapV2Swap( + DAI_ADDR, + WETH_DAI_POOL, + tychoRouterAddr, + true, + TokenTransfer.TransferType.TRANSFER_PERMIT2 + ); bytes memory swap = encodeSingleSwap(address(usv2Executor), protocolData); @@ -267,7 +296,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max); // Encoded solution generated using `test_single_swap_strategy_encoder_no_permit2` (bool success,) = tychoRouterAddr.call( - hex"20144a070000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae3740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000" + hex"20144a070000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae3740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139500010000000000000000000000000000" ); vm.stopPrank(); @@ -286,7 +315,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_single_swap_strategy_encoder` (bool success,) = tychoRouterAddr.call( - hex"30ace1b10000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006817833200000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067effd3a00000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000417efea09004d5d40d8d072e1ce0a425507717ea485c765eb90c170859197d362b502fb039b4f5cdce57318ecfe3ab276d1ac87771eb5d017b253a8f4107e6a20b1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000" + hex"30ace1b10000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000682169c100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9e3c900000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000412d3f0fee3fc61987512f024f20b1448eb934f82105a91653dd169179c693aaf95d09ef666ce1d38be70b8156fa6e4ea3e8717204e02fe7ba99a1fc4e5a26e6e11b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139500020000000000000000000000000000" ); vm.stopPrank(); diff --git a/foundry/test/TychoRouterSplitSwap.t.sol b/foundry/test/TychoRouterSplitSwap.t.sol index a9c593a..2a9d6df 100644 --- a/foundry/test/TychoRouterSplitSwap.t.sol +++ b/foundry/test/TychoRouterSplitSwap.t.sol @@ -8,13 +8,22 @@ import "./executors/UniswapV4Utils.sol"; import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol"; contract TychoRouterSplitSwapTest is TychoRouterTestSetup { - function _getSplitSwaps() private view returns (bytes[] memory) { + function _getSplitSwaps(bool permit2) + private + view + returns (bytes[] memory) + { // Trade 1 WETH for USDC through DAI and WBTC with 4 swaps on Uniswap V2 // -> DAI -> // 1 WETH USDC // -> WBTC -> // (univ2) (univ2) bytes[] memory swaps = new bytes[](4); + + TokenTransfer.TransferType inTransferType = permit2 + ? TokenTransfer.TransferType.TRANSFER_PERMIT2 + : TokenTransfer.TransferType.TRANSFER_FROM; + // WETH -> WBTC (60%) swaps[0] = encodeSplitSwap( uint8(0), @@ -22,7 +31,11 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { (0xffffff * 60) / 100, // 60% address(usv2Executor), encodeUniswapV2Swap( - WETH_ADDR, WETH_WBTC_POOL, tychoRouterAddr, false + WETH_ADDR, + WETH_WBTC_POOL, + tychoRouterAddr, + false, + inTransferType ) ); // WBTC -> USDC @@ -32,7 +45,11 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { uint24(0), address(usv2Executor), encodeUniswapV2Swap( - WBTC_ADDR, USDC_WBTC_POOL, tychoRouterAddr, true + WBTC_ADDR, + USDC_WBTC_POOL, + tychoRouterAddr, + true, + TokenTransfer.TransferType.TRANSFER ) ); // WETH -> DAI @@ -42,7 +59,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { uint24(0), address(usv2Executor), encodeUniswapV2Swap( - WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false + WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false, inTransferType ) ); @@ -52,7 +69,13 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { uint8(2), uint24(0), address(usv2Executor), - encodeUniswapV2Swap(DAI_ADDR, DAI_USDC_POOL, tychoRouterAddr, true) + encodeUniswapV2Swap( + DAI_ADDR, + DAI_USDC_POOL, + tychoRouterAddr, + true, + TokenTransfer.TransferType.TRANSFER + ) ); return swaps; @@ -62,9 +85,12 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { // Trade 1 WETH for USDC through DAI and WBTC - see _getSplitSwaps for more info uint256 amountIn = 1 ether; - deal(WETH_ADDR, tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSplitSwaps(); + deal(WETH_ADDR, ALICE, amountIn); + vm.startPrank(ALICE); + IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn); + bytes[] memory swaps = _getSplitSwaps(false); tychoRouter.exposedSplitSwap(amountIn, 4, pleEncode(swaps)); + vm.stopPrank(); uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(tychoRouterAddr); assertEq(usdcBalance, 2615491639); @@ -83,7 +109,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { bytes memory signature ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSplitSwaps(); + bytes[] memory swaps = _getSplitSwaps(true); tychoRouter.splitSwapPermit2( amountIn, @@ -112,7 +138,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn); - bytes[] memory swaps = _getSplitSwaps(); + bytes[] memory swaps = _getSplitSwaps(false); tychoRouter.splitSwap( amountIn, @@ -139,7 +165,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn); - bytes[] memory swaps = _getSplitSwaps(); + bytes[] memory swaps = _getSplitSwaps(false); vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector); tychoRouter.splitSwap( @@ -164,7 +190,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); // Approve less than the amountIn IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn - 1); - bytes[] memory swaps = _getSplitSwaps(); + bytes[] memory swaps = _getSplitSwaps(false); vm.expectRevert(); tychoRouter.splitSwap( @@ -193,7 +219,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { bytes memory signature ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSplitSwaps(); + bytes[] memory swaps = _getSplitSwaps(true); uint256 minAmountOut = 3000 * 1e18; @@ -240,7 +266,11 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { sigDeadline: 0 }); bytes memory protocolData = encodeUniswapV2Swap( - WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false + WETH_ADDR, + WETH_DAI_POOL, + tychoRouterAddr, + false, + TokenTransfer.TransferType.TRANSFER ); bytes memory swap = encodeSplitSwap( @@ -284,8 +314,13 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { bytes memory signature ) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn); - bytes memory protocolData = - encodeUniswapV2Swap(DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true); + bytes memory protocolData = encodeUniswapV2Swap( + DAI_ADDR, + WETH_DAI_POOL, + tychoRouterAddr, + true, + TokenTransfer.TransferType.TRANSFER_PERMIT2 + ); bytes memory swap = encodeSplitSwap( uint8(0), uint8(1), uint24(0), address(usv2Executor), protocolData @@ -330,7 +365,12 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI bool zeroForOne = false; bytes memory protocolData = encodeUniswapV3Swap( - WETH_ADDR, DAI_ADDR, tychoRouterAddr, DAI_WETH_USV3, zeroForOne + WETH_ADDR, + DAI_ADDR, + tychoRouterAddr, + DAI_WETH_USV3, + zeroForOne, + TokenTransfer.TransferType.TRANSFER_PERMIT2 ); bytes memory swap = encodeSplitSwap( uint8(0), uint8(1), uint24(0), address(usv3Executor), protocolData @@ -366,59 +406,6 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tychoRouter.exposedSplitSwap(amountIn, 2, swaps); } - function testSplitSwapAmountInNotFullySpent() public { - // Trade 1 WETH for DAI with 1 swap on Uniswap V2 - // Has invalid data as input! There is only one swap with 60% of the input amount - uint256 amountIn = 1 ether; - deal(WETH_ADDR, ALICE, amountIn); - - vm.startPrank(ALICE); - - ( - IAllowanceTransfer.PermitSingle memory permitSingle, - bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); - - bytes memory protocolData = encodeUniswapV2Swap( - WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false - ); - - bytes memory swap = encodeSplitSwap( - uint8(0), - uint8(1), - (0xffffff * 60) / 100, // 60% - address(usv2Executor), - protocolData - ); - - bytes[] memory swaps = new bytes[](1); - swaps[0] = swap; - - vm.expectRevert( - abi.encodeWithSelector( - TychoRouter__AmountInDiffersFromConsumed.selector, - 1000000000000000000, - 600000000000000000 - ) - ); - - tychoRouter.splitSwapPermit2( - amountIn, - WETH_ADDR, - DAI_ADDR, - 1, - false, - false, - 2, - ALICE, - permitSingle, - signature, - pleEncode(swaps) - ); - - vm.stopPrank(); - } - function testSplitSwapSingleUSV4CallbackPermit2() public { vm.startPrank(ALICE); uint256 amountIn = 100 ether; @@ -436,8 +423,13 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tickSpacing: int24(1) }); - bytes memory protocolData = - UniswapV4Utils.encodeExactInput(USDE_ADDR, USDT_ADDR, true, pools); + bytes memory protocolData = UniswapV4Utils.encodeExactInput( + USDE_ADDR, + USDT_ADDR, + true, + TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_ROUTER, + pools + ); bytes memory swap = encodeSplitSwap( uint8(0), uint8(1), uint24(0), address(usv4Executor), protocolData @@ -483,8 +475,13 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tickSpacing: int24(60) }); - bytes memory protocolData = - UniswapV4Utils.encodeExactInput(USDE_ADDR, WBTC_ADDR, true, pools); + bytes memory protocolData = UniswapV4Utils.encodeExactInput( + USDE_ADDR, + WBTC_ADDR, + true, + TokenTransfer.TransferType.NONE, + pools + ); bytes memory swap = encodeSplitSwap( uint8(0), uint8(1), uint24(0), address(usv4Executor), protocolData @@ -507,18 +504,35 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { // │ │ // └─ (USV3, 40% split) ──> WETH ─┘ uint256 amountIn = 100 * 10 ** 6; - deal(USDC_ADDR, tychoRouterAddr, amountIn); + deal(USDC_ADDR, ALICE, amountIn); + vm.startPrank(ALICE); + // Approve the TychoRouter to spend USDC + IERC20(USDC_ADDR).approve(tychoRouterAddr, amountIn); bytes memory usdcWethV3Pool1ZeroOneData = encodeUniswapV3Swap( - USDC_ADDR, WETH_ADDR, tychoRouterAddr, USDC_WETH_USV3, true + USDC_ADDR, + WETH_ADDR, + tychoRouterAddr, + USDC_WETH_USV3, + true, + TokenTransfer.TransferType.TRANSFER_FROM ); bytes memory usdcWethV3Pool2ZeroOneData = encodeUniswapV3Swap( - USDC_ADDR, WETH_ADDR, tychoRouterAddr, USDC_WETH_USV3_2, true + USDC_ADDR, + WETH_ADDR, + tychoRouterAddr, + USDC_WETH_USV3_2, + true, + TokenTransfer.TransferType.TRANSFER_FROM ); bytes memory wethUsdcV2OneZeroData = encodeUniswapV2Swap( - WETH_ADDR, USDC_WETH_USV2, tychoRouterAddr, false + WETH_ADDR, + USDC_WETH_USV2, + tychoRouterAddr, + false, + TokenTransfer.TransferType.TRANSFER ); bytes[] memory swaps = new bytes[](3); @@ -547,6 +561,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { wethUsdcV2OneZeroData ); tychoRouter.exposedSplitSwap(amountIn, 2, pleEncode(swaps)); + vm.stopPrank(); assertEq(IERC20(USDC_ADDR).balanceOf(tychoRouterAddr), 99574171); } @@ -563,15 +578,29 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { deal(USDC_ADDR, tychoRouterAddr, amountIn); bytes memory usdcWethV2Data = encodeUniswapV2Swap( - USDC_ADDR, USDC_WETH_USV2, tychoRouterAddr, true + USDC_ADDR, + USDC_WETH_USV2, + tychoRouterAddr, + true, + TokenTransfer.TransferType.TRANSFER ); bytes memory usdcWethV3Pool1OneZeroData = encodeUniswapV3Swap( - WETH_ADDR, USDC_ADDR, tychoRouterAddr, USDC_WETH_USV3, false + WETH_ADDR, + USDC_ADDR, + tychoRouterAddr, + USDC_WETH_USV3, + false, + TokenTransfer.TransferType.TRANSFER ); bytes memory usdcWethV3Pool2OneZeroData = encodeUniswapV3Swap( - WETH_ADDR, USDC_ADDR, tychoRouterAddr, USDC_WETH_USV3_2, false + WETH_ADDR, + USDC_ADDR, + tychoRouterAddr, + USDC_WETH_USV3_2, + false, + TokenTransfer.TransferType.TRANSFER ); bytes[] memory swaps = new bytes[](3); @@ -610,7 +639,11 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { deal(BASE_USDC, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap( - BASE_USDC, USDC_MAG7_POOL, tychoRouterAddr, true + BASE_USDC, + USDC_MAG7_POOL, + tychoRouterAddr, + true, + TokenTransfer.TransferType.TRANSFER_FROM ); bytes memory swap = encodeSplitSwap( diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index 961f0f8..d556e35 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -102,7 +102,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper { new UniswapV2Executor(factoryV2, initCodeV2, PERMIT2_ADDRESS); usv3Executor = new UniswapV3Executor(factoryV3, initCodeV3, PERMIT2_ADDRESS); - usv4Executor = new UniswapV4Executor(poolManager); + usv4Executor = new UniswapV4Executor(poolManager, PERMIT2_ADDRESS); pancakev3Executor = new UniswapV3Executor( factoryPancakeV3, initCodePancakeV3, PERMIT2_ADDRESS ); @@ -178,15 +178,11 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper { address tokenIn, address target, address receiver, - bool zero2one + bool zero2one, + TokenTransfer.TransferType transferType ) internal pure returns (bytes memory) { - return abi.encodePacked( - tokenIn, - target, - receiver, - zero2one, - TokenTransfer.TransferType.TRANSFER - ); + return + abi.encodePacked(tokenIn, target, receiver, zero2one, transferType); } function encodeUniswapV3Swap( @@ -194,7 +190,8 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper { address tokenOut, address receiver, address target, - bool zero2one + bool zero2one, + TokenTransfer.TransferType transferType ) internal view returns (bytes memory) { IUniswapV3Pool pool = IUniswapV3Pool(target); return abi.encodePacked( @@ -204,7 +201,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper { receiver, target, zero2one, - TokenTransfer.TransferType.TRANSFER + transferType ); } } diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index c993ff9..a001a1e 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -176,7 +176,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { WETH_DAI_POOL, BOB, zeroForOne, - uint8(TokenTransfer.TransferType.TRANSFERFROM) + uint8(TokenTransfer.TransferType.TRANSFER_FROM) ); deal(WETH_ADDR, address(this), amountIn); @@ -197,7 +197,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { WETH_DAI_POOL, ALICE, zeroForOne, - uint8(TokenTransfer.TransferType.TRANSFERPERMIT2) + uint8(TokenTransfer.TransferType.TRANSFER_PERMIT2) ); deal(WETH_ADDR, ALICE, amountIn); diff --git a/foundry/test/executors/UniswapV4Executor.t.sol b/foundry/test/executors/UniswapV4Executor.t.sol index 487e251..912223e 100644 --- a/foundry/test/executors/UniswapV4Executor.t.sol +++ b/foundry/test/executors/UniswapV4Executor.t.sol @@ -7,9 +7,12 @@ import "@src/executors/UniswapV4Executor.sol"; import {Constants} from "../Constants.sol"; import {Test} from "../../lib/forge-std/src/Test.sol"; import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol"; +import "@src/executors/TokenTransfer.sol"; contract UniswapV4ExecutorExposed is UniswapV4Executor { - constructor(IPoolManager _poolManager) UniswapV4Executor(_poolManager) {} + constructor(IPoolManager _poolManager, address _permit2) + UniswapV4Executor(_poolManager, _permit2) + {} function decodeData(bytes calldata data) external @@ -18,6 +21,7 @@ contract UniswapV4ExecutorExposed is UniswapV4Executor { address tokenIn, address tokenOut, bool zeroForOne, + TokenTransfer.TransferType transferType, UniswapV4Pool[] memory pools ) { @@ -36,8 +40,9 @@ contract UniswapV4ExecutorTest is Test, Constants { function setUp() public { uint256 forkBlock = 21817316; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); - uniswapV4Exposed = - new UniswapV4ExecutorExposed(IPoolManager(poolManager)); + uniswapV4Exposed = new UniswapV4ExecutorExposed( + IPoolManager(poolManager), PERMIT2_ADDRESS + ); } function testDecodeParams() public view { @@ -46,6 +51,8 @@ contract UniswapV4ExecutorTest is Test, Constants { int24 tickSpacing1 = 60; uint24 pool2Fee = 1000; int24 tickSpacing2 = -10; + TokenTransfer.TransferType transferType = + TokenTransfer.TransferType.TRANSFER_FROM; UniswapV4Executor.UniswapV4Pool[] memory pools = new UniswapV4Executor.UniswapV4Pool[](2); @@ -61,19 +68,21 @@ contract UniswapV4ExecutorTest is Test, Constants { }); bytes memory data = UniswapV4Utils.encodeExactInput( - USDE_ADDR, USDT_ADDR, zeroForOne, pools + USDE_ADDR, USDT_ADDR, zeroForOne, transferType, pools ); ( address tokenIn, address tokenOut, bool zeroForOneDecoded, + TokenTransfer.TransferType transferTypeDecoded, UniswapV4Executor.UniswapV4Pool[] memory decodedPools ) = uniswapV4Exposed.decodeData(data); assertEq(tokenIn, USDE_ADDR); assertEq(tokenOut, USDT_ADDR); assertEq(zeroForOneDecoded, zeroForOne); + assertEq(uint8(transferTypeDecoded), uint8(transferType)); assertEq(decodedPools.length, 2); assertEq(decodedPools[0].intermediaryToken, USDT_ADDR); assertEq(decodedPools[0].fee, pool1Fee); @@ -98,8 +107,13 @@ contract UniswapV4ExecutorTest is Test, Constants { tickSpacing: int24(1) }); - bytes memory data = - UniswapV4Utils.encodeExactInput(USDE_ADDR, USDT_ADDR, true, pools); + bytes memory data = UniswapV4Utils.encodeExactInput( + USDE_ADDR, + USDT_ADDR, + true, + TokenTransfer.TransferType.NONE, + pools + ); uint256 amountOut = uniswapV4Exposed.swap(amountIn, data); assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn); @@ -114,7 +128,7 @@ contract UniswapV4ExecutorTest is Test, Constants { // USDE -> USDT // Generated by the Tycho swap encoder - test_encode_uniswap_v4_simple_swap bytes memory protocolData = - hex"4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec701dac17f958d2ee523a2206206994597c13d831ec7000064000001"; + hex"4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec701f62849f9a0b5bf2913b396098f7c7019b51a820a00dac17f958d2ee523a2206206994597c13d831ec7000064000001"; uint256 amountIn = 100 ether; deal(USDE_ADDR, address(uniswapV4Exposed), amountIn); uint256 usdeBalanceBeforePool = USDE.balanceOf(poolManager); @@ -151,8 +165,13 @@ contract UniswapV4ExecutorTest is Test, Constants { tickSpacing: int24(60) }); - bytes memory data = - UniswapV4Utils.encodeExactInput(USDE_ADDR, WBTC_ADDR, true, pools); + bytes memory data = UniswapV4Utils.encodeExactInput( + USDE_ADDR, + WBTC_ADDR, + true, + TokenTransfer.TransferType.NONE, + pools + ); uint256 amountOut = uniswapV4Exposed.swap(amountIn, data); assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn); @@ -170,7 +189,7 @@ contract UniswapV4ExecutorTest is Test, Constants { // Generated by the Tycho swap encoder - test_encode_uniswap_v4_sequential_swap bytes memory protocolData = - hex"4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c59901dac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c"; + hex"4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c59901f62849f9a0b5bf2913b396098f7c7019b51a820a00dac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c"; uint256 amountIn = 100 ether; deal(USDE_ADDR, address(uniswapV4Exposed), amountIn); diff --git a/foundry/test/executors/UniswapV4Utils.sol b/foundry/test/executors/UniswapV4Utils.sol index 67c7c7f..5f13530 100644 --- a/foundry/test/executors/UniswapV4Utils.sol +++ b/foundry/test/executors/UniswapV4Utils.sol @@ -8,6 +8,7 @@ library UniswapV4Utils { address tokenIn, address tokenOut, bool zeroForOne, + UniswapV4Executor.TransferType transferType, UniswapV4Executor.UniswapV4Pool[] memory pools ) public pure returns (bytes memory) { bytes memory encodedPools; @@ -21,6 +22,12 @@ library UniswapV4Utils { ); } - return abi.encodePacked(tokenIn, tokenOut, zeroForOne, encodedPools); + return abi.encodePacked( + tokenIn, + tokenOut, + zeroForOne, + transferType, + encodedPools + ); } } diff --git a/src/encoding/evm/constants.rs b/src/encoding/evm/constants.rs index ef44482..2e6ad96 100644 --- a/src/encoding/evm/constants.rs +++ b/src/encoding/evm/constants.rs @@ -26,3 +26,14 @@ pub static IN_TRANSFER_OPTIMIZABLE_PROTOCOLS: LazyLock> = set.insert("uniswap_v3"); set }); + +/// These protocols expect funds to be in the router at the time of swap. +pub static PROTOCOLS_EXPECTING_FUNDS_IN_ROUTER: LazyLock> = + LazyLock::new(|| { + let mut set = HashSet::new(); + set.insert("curve"); + set.insert("balancer_v2"); + // TODO remove uniswap_v4 when we add callback support for transfer optimizations + set.insert("uniswap_v4"); + set + }); diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index e5174af..99ee9ae 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -39,6 +39,7 @@ pub struct SingleSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, permit2: Option, selector: String, + native_address: Bytes, router_address: Bytes, } @@ -57,7 +58,13 @@ impl SingleSwapStrategyEncoder { "singleSwap(uint256,address,address,uint256,bool,bool,address,bytes)".to_string(), ) }; - Ok(Self { permit2, selector, swap_encoder_registry, router_address }) + Ok(Self { + permit2, + selector, + swap_encoder_registry, + native_address: chain.native_token()?, + router_address, + }) } /// Encodes information necessary for performing a single hop against a given executor for @@ -117,6 +124,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { let transfer_type = self.get_transfer_method( swap.clone(), solution.given_token.clone(), + self.native_address.clone(), self.permit2.clone().is_some(), ); @@ -289,6 +297,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { let transfer_type = self.get_transfer_method( swap.clone(), solution.given_token.clone(), + self.native_address.clone(), self.permit2.clone().is_some(), ); @@ -515,6 +524,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { let transfer_type = self.get_transfer_method( swap.clone(), solution.given_token.clone(), + self.native_address.clone(), self.permit2.clone().is_some(), ); @@ -857,6 +867,7 @@ mod tests { "0000000000000000000000000000", // padding )); let hex_calldata = encode(&calldata); + println!("{}", hex_calldata); assert_eq!(hex_calldata[..456], expected_input); assert_eq!(hex_calldata[1224..], expected_swap); @@ -1354,9 +1365,9 @@ mod tests { let expected_swaps = String::from(concat!( // length of ple encoded swaps without padding - "0000000000000000000000000000000000000000000000000000000000000078", + "0000000000000000000000000000000000000000000000000000000000000079", // ple encoded swaps - "0076", // Swap length + "0077", // Swap length "00", // token in index "01", // token out index "000000", // split @@ -1366,6 +1377,7 @@ mod tests { "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // group token in "6982508145454ce325ddbe47a25d4ec3d2311933", // group token in "00", // zero2one + "04", // transfer type (transfer to router) // First pool params "0000000000000000000000000000000000000000", // intermediary token (ETH) "000bb8", // fee @@ -1374,7 +1386,7 @@ mod tests { "6982508145454ce325ddbe47a25d4ec3d2311933", // intermediary token (PEPE) "0061a8", // fee "0001f4", // tick spacing - "0000000000000000" // padding + "00000000000000" // padding )); let hex_calldata = encode(&calldata); diff --git a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs index e3692ec..8d63612 100644 --- a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs +++ b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs @@ -1,26 +1,47 @@ use tycho_common::Bytes; use crate::encoding::{ - evm::constants::IN_TRANSFER_OPTIMIZABLE_PROTOCOLS, + evm::constants::{IN_TRANSFER_OPTIMIZABLE_PROTOCOLS, PROTOCOLS_EXPECTING_FUNDS_IN_ROUTER}, models::{Swap, TransferType}, }; /// A trait that defines how the tokens will be transferred into the given pool given the solution. pub trait TransferOptimization { /// Returns the transfer method that should be used for the given swap and solution. - /// - /// If the swap is for the in token of the solution and the protocol supports transferring - /// straight from the user, it will return `TransferType::Permit2Transfer` or - /// `TransferType::TransferFrom`. - fn get_transfer_method(&self, swap: Swap, given_token: Bytes, permit2: bool) -> TransferType { - let optimize_in_transfer = + fn get_transfer_method( + &self, + swap: Swap, + given_token: Bytes, + native_token: Bytes, + permit2: bool, + ) -> TransferType { + let send_funds_to_pool: bool = IN_TRANSFER_OPTIMIZABLE_PROTOCOLS.contains(&swap.component.protocol_system.as_str()); - if (swap.token_in == given_token) && optimize_in_transfer { - if permit2 { + let funds_expected_in_router: bool = + PROTOCOLS_EXPECTING_FUNDS_IN_ROUTER.contains(&swap.component.protocol_system.as_str()); + + if (swap.token_in == given_token) && send_funds_to_pool { + if swap.token_in == native_token { + // Funds are already in router. Transfer from router to pool. + TransferType::Transfer + } else if permit2 { + // Transfer from swapper to pool using permit2. TransferType::Permit2Transfer } else { + // Transfer from swapper to pool. TransferType::TransferFrom } + } else if (swap.token_in == given_token) && funds_expected_in_router { + if swap.token_in == native_token { + // Funds already in router. Do nothing. + TransferType::None + } else if permit2 { + // Transfer from swapper to router using permit2. + TransferType::Permit2TransferToRouter + } else { + // Transfer from swapper to router. + TransferType::TransferToRouter + } } else { TransferType::Transfer } @@ -41,6 +62,10 @@ mod tests { Bytes::from(hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").to_vec()) } + fn eth() -> Bytes { + Bytes::from(hex!("0000000000000000000000000000000000000000").to_vec()) + } + fn dai() -> Bytes { Bytes::from(hex!("6b175474e89094c44da98b954eedeac495271d0f").to_vec()) } @@ -57,7 +82,7 @@ mod tests { split: 0f64, }; let strategy = MockStrategy {}; - let transfer_method = strategy.get_transfer_method(swap.clone(), weth(), true); + let transfer_method = strategy.get_transfer_method(swap.clone(), weth(), eth(), true); assert_eq!(transfer_method, TransferType::Permit2Transfer); } @@ -73,7 +98,7 @@ mod tests { split: 0f64, }; let strategy = MockStrategy {}; - let transfer_method = strategy.get_transfer_method(swap.clone(), weth(), false); + let transfer_method = strategy.get_transfer_method(swap.clone(), weth(), eth(), false); assert_eq!(transfer_method, TransferType::TransferFrom); } @@ -89,7 +114,7 @@ mod tests { split: 0f64, }; let strategy = MockStrategy {}; - let transfer_method = strategy.get_transfer_method(swap.clone(), dai(), false); + let transfer_method = strategy.get_transfer_method(swap.clone(), dai(), eth(), false); assert_eq!(transfer_method, TransferType::Transfer); } } diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index b05199a..89662ce 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -202,7 +202,13 @@ impl SwapEncoder for UniswapV4SwapEncoder { let pool_params = (token_out_address, pool_fee_u24, pool_tick_spacing_u24).abi_encode_packed(); - let args = (group_token_in_address, group_token_out_address, zero_to_one, pool_params); + let args = ( + group_token_in_address, + group_token_out_address, + zero_to_one, + (encoding_context.transfer_type as u8).to_be_bytes(), + pool_params, + ); Ok(args.abi_encode_packed()) } @@ -786,6 +792,8 @@ mod tests { "dac17f958d2ee523a2206206994597c13d831ec7", // zero for one "01", + // transfer type + "00", // pool params: // - intermediary token "dac17f958d2ee523a2206206994597c13d831ec7", @@ -942,6 +950,7 @@ mod tests { let combined_hex = format!("{}{}", encode(&initial_encoded_swap), encode(&second_encoded_swap)); + println!("{}", combined_hex); assert_eq!( combined_hex, String::from(concat!( @@ -951,6 +960,8 @@ mod tests { "2260fac5e5542a773aa44fbcfedf7c193bc2c599", // zero for one "01", + // transfer type + "00", // pool params: // - intermediary token USDT "dac17f958d2ee523a2206206994597c13d831ec7", diff --git a/src/encoding/evm/tycho_encoders.rs b/src/encoding/evm/tycho_encoders.rs index 6ec4e36..40beb97 100644 --- a/src/encoding/evm/tycho_encoders.rs +++ b/src/encoding/evm/tycho_encoders.rs @@ -1048,6 +1048,8 @@ mod tests { "6982508145454ce325ddbe47a25d4ec3d2311933", // zero for one "00", + // transfer type + "00", // first pool intermediary token (ETH) "0000000000000000000000000000000000000000", // fee diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 7f33009..9d5fc47 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -103,6 +103,8 @@ pub struct Transaction { /// * `Transfer`: Transfer the token from the router into the pool. /// * `TransferFrom`: Transfer the token from the swapper to the pool. /// * `Permit2Transfer`: Transfer the token from the sender to the pool using Permit2. +/// * `TransferToRouter`: Transfer the token from the swapper to the router. +/// * `Permit2TransferToRouter`: Transfer the token from the swapper to the router using Permit2. /// * `None`: No transfer is needed. Tokens are already in the pool. #[repr(u8)] #[derive(Clone, Debug, PartialEq)] @@ -110,7 +112,9 @@ pub enum TransferType { Transfer = 0, TransferFrom = 1, Permit2Transfer = 2, - None = 3, + TransferToRouter = 3, + Permit2TransferToRouter = 4, + None = 5, } /// Represents necessary attributes for encoding an order. From e96ea1b10b84cb22d6732329a668f9ddd62b3276 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 14 Apr 2025 12:38:29 -0400 Subject: [PATCH 07/13] fix: Test+formatting fixes after rebase. --- foundry/src/TychoRouter.sol | 12 ------- foundry/test/TychoRouterIntegration.t.sol | 35 ++++++++++--------- foundry/test/TychoRouterSplitSwap.t.sol | 6 +--- .../test/executors/UniswapV3Executor.t.sol | 10 ++++-- .../test/executors/UniswapV4Executor.t.sol | 16 +++------ foundry/test/executors/UniswapV4Utils.sol | 6 +--- .../evm/swap_encoder/swap_encoders.rs | 3 ++ 7 files changed, 35 insertions(+), 53 deletions(-) diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 4221d79..32b54a5 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -522,18 +522,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { amountOut = _sequentialSwap(amountIn, swaps); - uint256 currentBalance = tokenIn == address(0) - ? address(this).balance - : IERC20(tokenIn).balanceOf(address(this)); - - uint256 amountConsumed = initialBalance - currentBalance; - - if (tokenIn != tokenOut && amountConsumed != amountIn) { - revert TychoRouter__AmountInDiffersFromConsumed( - amountIn, amountConsumed - ); - } - if (amountOut < minAmountOut) { revert TychoRouter__NegativeSlippage(amountOut, minAmountOut); } diff --git a/foundry/test/TychoRouterIntegration.t.sol b/foundry/test/TychoRouterIntegration.t.sol index 7f9a983..55e0ae3 100644 --- a/foundry/test/TychoRouterIntegration.t.sol +++ b/foundry/test/TychoRouterIntegration.t.sol @@ -37,7 +37,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_split_encoding_strategy_usv4` (bool success,) = tychoRouterAddr.call( - hex"7c553846000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000000000000000682163b600000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9ddbe000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041e9e58e3facf99cd2c64b834d3b646b8cf9377c47540d65b5e180a06bca6f42851cf320a205cf466c7943abe45c2998afa6fd3d870043a108578e71256831ca1c1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d008b0001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d231193300f62849f9a0b5bf2913b396098f7c7019b51a820a040000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f400000000000000000000000000000000000000" + hex"7c553846000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000006824c2ae00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067fd3cb6000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041850e1add80e90f19d7b1ac70721b5dec8a6bdcb57999ea957f831ea57c4ce1a116bec18d2cae2559421ead48186b6985176fee7f8d2e3452c1518dc2635224b41c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007900770001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d231193300040000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f400000000000000" ); vm.stopPrank(); @@ -60,7 +60,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { // Encoded solution generated using `test_split_encoding_strategy_usv4_eth_in` (bool success,) = tychoRouterAddr.call{value: 1 ether}( - hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933000000000000000000000000000000000000000000c87c939ae635f92dc2379c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006821689800000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9e2a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000412da8d5aab101bbdf256d785a42db176328e8298ee6d0906e0ef1998cfcaa332460f8409d9b298dff73c947796a22c8de21caa17405ea157ced090da2b6cb27431c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007300710001000000f62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d231193301f62849f9a0b5bf2913b396098f7c7019b51a820a056982508145454ce325ddbe47a25d4ec3d23119330061a80001f400000000000000000000000000" + hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933000000000000000000000000000000000000000000c87c939ae635f92dc2379c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006824c2cb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067fd3cd3000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041a97e156e8c7a48a968efe2f057700ce8458ecfbfb53e0319fc9223b2364aba20227d2ee00226b42e2f28aeb002a242576d75152b270d73a8926f595190828f3d1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005f005d0001000000f62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d231193301056982508145454ce325ddbe47a25d4ec3d23119330061a80001f400" ); vm.stopPrank(); @@ -87,7 +87,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { // Encoded solution generated using `test_split_encoding_strategy_usv4_eth_out` (bool success,) = tychoRouterAddr.call( - hex"7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e0000000000000000000000000000000000000000000000000000000000682163ee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9ddf60000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000416056d925e7906c11b865992ac5c853532f5058bb57b67cd000a53b899503dd8a6fd4c0e5ea44c1ca4137753589bf89f66824796e719e807adee7567a707ee6681b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007300710001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000f62849f9a0b5bf2913b396098f7c7019b51a820a040000000000000000000000000000000000000000000bb800003c00000000000000000000000000" + hex"7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000000000000000000000000000000000006824c2da00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067fd3ce20000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000416b224d97fe1a35f2698380969c22854cf7ed92881ea484df7adb438f8d8e78e66c8617131f6712c5f3b4fa07725725081b2ebe27cd44acd11332a4a72bded96c1b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005f005d0001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000040000000000000000000000000000000000000000000bb800003c00" ); vm.stopPrank(); @@ -294,20 +294,21 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { vm.stopPrank(); } - function testSplitCurveIntegration() public { - deal(UWU_ADDR, ALICE, 1 ether); - - vm.startPrank(ALICE); - IERC20(UWU_ADDR).approve(tychoRouterAddr, type(uint256).max); - // Encoded solution generated using `test_split_encoding_strategy_curve` - (bool success,) = tychoRouterAddr.call( - hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000055c08ca52497e2f1534b59e2917bf524d4765257000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000005b005900010000001d1499e622d69689cdf9004d05ec547d650ff21155c08ca52497e2f1534b59e2917bf524d4765257c02aaa39b223fe8d0a0e5c4f27ead9083c756cc277146b0a1d08b6844376df6d9da99ba7f1b19e71020100010000000000" - ); - - assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 4691958787921); - - vm.stopPrank(); - } + // TODO uncomment when Curve TokenTransfer is implemented (next commits) + // function testSplitCurveIntegration() public { + // deal(UWU_ADDR, ALICE, 1 ether); + // + // vm.startPrank(ALICE); + // IERC20(UWU_ADDR).approve(tychoRouterAddr, type(uint256).max); + // // Encoded solution generated using `test_split_encoding_strategy_curve` + // (bool success,) = tychoRouterAddr.call( + // hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000055c08ca52497e2f1534b59e2917bf524d4765257000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000005b005900010000001d1499e622d69689cdf9004d05ec547d650ff21155c08ca52497e2f1534b59e2917bf524d4765257c02aaa39b223fe8d0a0e5c4f27ead9083c756cc277146b0a1d08b6844376df6d9da99ba7f1b19e71020100010000000000" + // ); + // + // assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 4691958787921); + // + // vm.stopPrank(); + // } function testSplitCurveIntegrationStETH() public { deal(ALICE, 1 ether); diff --git a/foundry/test/TychoRouterSplitSwap.t.sol b/foundry/test/TychoRouterSplitSwap.t.sol index 2a9d6df..a182ecd 100644 --- a/foundry/test/TychoRouterSplitSwap.t.sol +++ b/foundry/test/TychoRouterSplitSwap.t.sol @@ -476,11 +476,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { }); bytes memory protocolData = UniswapV4Utils.encodeExactInput( - USDE_ADDR, - WBTC_ADDR, - true, - TokenTransfer.TransferType.NONE, - pools + USDE_ADDR, WBTC_ADDR, true, TokenTransfer.TransferType.NONE, pools ); bytes memory swap = encodeSplitSwap( diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index 19aa02a..5b134b8 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -90,7 +90,9 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { assertEq(receiver, address(2)); assertEq(target, address(3)); assertEq(zeroForOne, false); - assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.TRANSFER)); + assertEq( + uint8(transferType), uint8(TokenTransfer.TransferType.TRANSFER) + ); } function testDecodeParamsInvalidDataLength() public { @@ -121,7 +123,11 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { vm.startPrank(DAI_WETH_USV3); bytes memory protocolData = abi.encodePacked( - WETH_ADDR, DAI_ADDR, poolFee, TokenTransfer.TransferType.TRANSFER + WETH_ADDR, + DAI_ADDR, + poolFee, + TokenTransfer.TransferType.TRANSFER, + address(uniswapV3Exposed) ); uint256 dataOffset = 3; // some offset uint256 dataLength = protocolData.length; diff --git a/foundry/test/executors/UniswapV4Executor.t.sol b/foundry/test/executors/UniswapV4Executor.t.sol index 912223e..4c925f2 100644 --- a/foundry/test/executors/UniswapV4Executor.t.sol +++ b/foundry/test/executors/UniswapV4Executor.t.sol @@ -108,11 +108,7 @@ contract UniswapV4ExecutorTest is Test, Constants { }); bytes memory data = UniswapV4Utils.encodeExactInput( - USDE_ADDR, - USDT_ADDR, - true, - TokenTransfer.TransferType.NONE, - pools + USDE_ADDR, USDT_ADDR, true, TokenTransfer.TransferType.NONE, pools ); uint256 amountOut = uniswapV4Exposed.swap(amountIn, data); @@ -128,7 +124,7 @@ contract UniswapV4ExecutorTest is Test, Constants { // USDE -> USDT // Generated by the Tycho swap encoder - test_encode_uniswap_v4_simple_swap bytes memory protocolData = - hex"4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec701f62849f9a0b5bf2913b396098f7c7019b51a820a00dac17f958d2ee523a2206206994597c13d831ec7000064000001"; + hex"4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec70100dac17f958d2ee523a2206206994597c13d831ec7000064000001"; uint256 amountIn = 100 ether; deal(USDE_ADDR, address(uniswapV4Exposed), amountIn); uint256 usdeBalanceBeforePool = USDE.balanceOf(poolManager); @@ -166,11 +162,7 @@ contract UniswapV4ExecutorTest is Test, Constants { }); bytes memory data = UniswapV4Utils.encodeExactInput( - USDE_ADDR, - WBTC_ADDR, - true, - TokenTransfer.TransferType.NONE, - pools + USDE_ADDR, WBTC_ADDR, true, TokenTransfer.TransferType.NONE, pools ); uint256 amountOut = uniswapV4Exposed.swap(amountIn, data); @@ -189,7 +181,7 @@ contract UniswapV4ExecutorTest is Test, Constants { // Generated by the Tycho swap encoder - test_encode_uniswap_v4_sequential_swap bytes memory protocolData = - hex"4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c59901f62849f9a0b5bf2913b396098f7c7019b51a820a00dac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c"; + hex"4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c5990100dac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c"; uint256 amountIn = 100 ether; deal(USDE_ADDR, address(uniswapV4Exposed), amountIn); diff --git a/foundry/test/executors/UniswapV4Utils.sol b/foundry/test/executors/UniswapV4Utils.sol index 5f13530..1ccc29c 100644 --- a/foundry/test/executors/UniswapV4Utils.sol +++ b/foundry/test/executors/UniswapV4Utils.sol @@ -23,11 +23,7 @@ library UniswapV4Utils { } return abi.encodePacked( - tokenIn, - tokenOut, - zeroForOne, - transferType, - encodedPools + tokenIn, tokenOut, zeroForOne, transferType, encodedPools ); } } diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 89662ce..3fd1453 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -1233,6 +1233,7 @@ mod tests { router_address: None, group_token_in: token_in.clone(), group_token_out: token_out.clone(), + transfer_type: TransferType::None, }; let encoder = CurveSwapEncoder::new( String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"), @@ -1298,6 +1299,7 @@ mod tests { router_address: None, group_token_in: token_in.clone(), group_token_out: token_out.clone(), + transfer_type: TransferType::None, }; let encoder = CurveSwapEncoder::new( String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"), @@ -1364,6 +1366,7 @@ mod tests { router_address: None, group_token_in: token_in.clone(), group_token_out: token_out.clone(), + transfer_type: TransferType::None, }; let encoder = CurveSwapEncoder::new( String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"), From e18e38af744242a3d900efcc1904be67078ffe3a Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 14 Apr 2025 12:43:06 -0400 Subject: [PATCH 08/13] chore: forge fmt. Somehow missed. --- foundry/src/executors/TokenTransfer.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/foundry/src/executors/TokenTransfer.sol b/foundry/src/executors/TokenTransfer.sol index 0fb1f6d..886e1f9 100644 --- a/foundry/src/executors/TokenTransfer.sol +++ b/foundry/src/executors/TokenTransfer.sol @@ -68,4 +68,3 @@ contract TokenTransfer { } } } - From 7ba99561db67a3b6dc9b1d44e4a4942e3a95319d Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 14 Apr 2025 12:44:08 -0400 Subject: [PATCH 09/13] chore: nightly fmt. Somehow missed. --- src/encoding/evm/strategy_encoder/strategy_encoders.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 99ee9ae..043b9d5 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -1386,7 +1386,7 @@ mod tests { "6982508145454ce325ddbe47a25d4ec3d2311933", // intermediary token (PEPE) "0061a8", // fee "0001f4", // tick spacing - "00000000000000" // padding + "00000000000000" // padding )); let hex_calldata = encode(&calldata); From dbc9042a2f4fbe8377cee8b554c8a15da1be8a89 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 14 Apr 2025 16:21:10 -0400 Subject: [PATCH 10/13] fix: consider wrapping scenario when getting transfer type --- .../evm/strategy_encoder/strategy_encoders.rs | 14 +++- .../transfer_optimizations.rs | 74 ++++++++++++++++--- 2 files changed, 76 insertions(+), 12 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 043b9d5..2536812 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -40,6 +40,7 @@ pub struct SingleSwapStrategyEncoder { permit2: Option, selector: String, native_address: Bytes, + wrapped_address: Bytes, router_address: Bytes, } @@ -63,6 +64,7 @@ impl SingleSwapStrategyEncoder { selector, swap_encoder_registry, native_address: chain.native_token()?, + wrapped_address: chain.wrapped_token()?, router_address, }) } @@ -121,11 +123,13 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { let mut grouped_protocol_data: Vec = vec![]; for swap in grouped_swap.swaps.iter() { - let transfer_type = self.get_transfer_method( + 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, ); let encoding_context = EncodingContext { @@ -294,11 +298,13 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { let mut grouped_protocol_data: Vec = vec![]; for swap in grouped_swap.swaps.iter() { - let transfer_type = self.get_transfer_method( + 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, ); let encoding_context = EncodingContext { @@ -521,11 +527,13 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { let mut grouped_protocol_data: Vec = vec![]; for swap in grouped_swap.swaps.iter() { - let transfer_type = self.get_transfer_method( + 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, ); let encoding_context = EncodingContext { diff --git a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs index 8d63612..ff43ed1 100644 --- a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs +++ b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs @@ -8,22 +8,29 @@ use crate::encoding::{ /// A trait that defines how the tokens will be transferred into the given pool given the solution. pub trait TransferOptimization { /// Returns the transfer method that should be used for the given swap and solution. - fn get_transfer_method( + fn get_transfer_type( &self, swap: Swap, given_token: Bytes, native_token: Bytes, + wrapped_token: Bytes, permit2: bool, + wrap: bool, ) -> TransferType { let send_funds_to_pool: bool = IN_TRANSFER_OPTIMIZABLE_PROTOCOLS.contains(&swap.component.protocol_system.as_str()); let funds_expected_in_router: bool = PROTOCOLS_EXPECTING_FUNDS_IN_ROUTER.contains(&swap.component.protocol_system.as_str()); - if (swap.token_in == given_token) && send_funds_to_pool { + // In the case of wrapping, check if the swap's token in is the wrapped token to + // determine if it's the first swap. Otherwise, compare to the given token. + let is_first_swap = + (swap.token_in == given_token) || ((swap.token_in == wrapped_token) && wrap); + + if is_first_swap && send_funds_to_pool { if swap.token_in == native_token { - // Funds are already in router. Transfer from router to pool. - TransferType::Transfer + // Funds are already in router. Protocol takes care of native transfer. + TransferType::None } else if permit2 { // Transfer from swapper to pool using permit2. TransferType::Permit2Transfer @@ -31,7 +38,7 @@ pub trait TransferOptimization { // Transfer from swapper to pool. TransferType::TransferFrom } - } else if (swap.token_in == given_token) && funds_expected_in_router { + } else if is_first_swap && funds_expected_in_router { if swap.token_in == native_token { // Funds already in router. Do nothing. TransferType::None @@ -70,8 +77,13 @@ mod tests { Bytes::from(hex!("6b175474e89094c44da98b954eedeac495271d0f").to_vec()) } + fn usdc() -> Bytes { + Bytes::from(hex!("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").to_vec()) + } + #[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(), @@ -82,12 +94,14 @@ mod tests { split: 0f64, }; let strategy = MockStrategy {}; - let transfer_method = strategy.get_transfer_method(swap.clone(), weth(), eth(), true); + let transfer_method = + strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), true, false); assert_eq!(transfer_method, TransferType::Permit2Transfer); } #[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(), @@ -98,12 +112,34 @@ mod tests { split: 0f64, }; let strategy = MockStrategy {}; - let transfer_method = strategy.get_transfer_method(swap.clone(), weth(), eth(), false); + let transfer_method = + strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false); assert_eq!(transfer_method, TransferType::TransferFrom); } #[test] - fn test_first_swap_transfer() { + 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() + }, + token_in: eth(), + token_out: dai(), + split: 0f64, + }; + let strategy = MockStrategy {}; + let transfer_method = + strategy.get_transfer_type(swap.clone(), eth(), eth(), weth(), false, false); + assert_eq!(transfer_method, TransferType::None); + } + + #[test] + 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(), @@ -114,7 +150,27 @@ mod tests { split: 0f64, }; let strategy = MockStrategy {}; - let transfer_method = strategy.get_transfer_method(swap.clone(), dai(), eth(), false); + let transfer_method = + strategy.get_transfer_type(swap.clone(), eth(), eth(), weth(), false, true); + assert_eq!(transfer_method, TransferType::TransferFrom); + } + + #[test] + 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() + }, + token_in: usdc(), + token_out: dai(), + split: 0f64, + }; + let strategy = MockStrategy {}; + let transfer_method = + strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false); assert_eq!(transfer_method, TransferType::Transfer); } } From 462be5463b77b5a69289df06738053ea5f3b3ce8 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 14 Apr 2025 16:52:52 -0400 Subject: [PATCH 11/13] feat: Add TokenTransfer class to Curve - This needed to be in Curve so that the executor can also take care of transfers from the user into the tycho router, to avoid having transfer actions mixed between the router and executors --- foundry/src/executors/CurveExecutor.sol | 16 ++++++--- foundry/test/TychoRouterIntegration.t.sol | 36 ++++++++++--------- foundry/test/TychoRouterTestSetup.sol | 2 +- foundry/test/executors/CurveExecutor.t.sol | 20 +++++++---- src/encoding/evm/constants.rs | 2 +- .../transfer_optimizations.rs | 15 ++++---- .../evm/swap_encoder/swap_encoders.rs | 7 ++++ 7 files changed, 61 insertions(+), 37 deletions(-) diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index 569072f..9a8c1e6 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.26; import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "./TokenTransfer.sol"; error CurveExecutor__AddressZero(); @@ -32,12 +33,14 @@ interface CryptoPoolETH { // slither-disable-end naming-convention } -contract CurveExecutor is IExecutor { +contract CurveExecutor is IExecutor, TokenTransfer { using SafeERC20 for IERC20; address public immutable nativeToken; - constructor(address _nativeToken) { + constructor(address _nativeToken, address _permit2) + TokenTransfer(_permit2) + { if (_nativeToken == address(0)) { revert CurveExecutor__AddressZero(); } @@ -57,9 +60,12 @@ contract CurveExecutor is IExecutor { uint8 poolType, int128 i, int128 j, - bool tokenApprovalNeeded + bool tokenApprovalNeeded, + TransferType transferType ) = _decodeData(data); + _transfer(tokenIn, msg.sender, pool, amountIn, transferType); + if (tokenApprovalNeeded && tokenIn != nativeToken) { // slither-disable-next-line unused-return IERC20(tokenIn).approve(address(pool), type(uint256).max); @@ -105,7 +111,8 @@ contract CurveExecutor is IExecutor { uint8 poolType, int128 i, int128 j, - bool tokenApprovalNeeded + bool tokenApprovalNeeded, + TransferType transferType ) { tokenIn = address(bytes20(data[0:20])); @@ -115,6 +122,7 @@ contract CurveExecutor is IExecutor { i = int128(uint128(uint8(data[61]))); j = int128(uint128(uint8(data[62]))); tokenApprovalNeeded = data[63] != 0; + transferType = TransferType(uint8(data[64])); } receive() external payable { diff --git a/foundry/test/TychoRouterIntegration.t.sol b/foundry/test/TychoRouterIntegration.t.sol index 55e0ae3..0f25861 100644 --- a/foundry/test/TychoRouterIntegration.t.sol +++ b/foundry/test/TychoRouterIntegration.t.sol @@ -257,6 +257,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f4308e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006821647000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9de780000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000413c8b048dc7b7614106a5aa1fa13e48c02a6a9714dfa07d2c424f68b81a5f828c39ace62f2dd57d7bfad10910ae44f77d68aec5c079fce456028b1bd7f72053151c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400102006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80000" ); + assertTrue(success, "Call Failed"); assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99889294); vm.stopPrank(); @@ -273,6 +274,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ef619b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006821659d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9dfa5000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041dd84c5cdc51719e377598eccd8eac0aae036e7e0745a7c65b5d44cc817071a7460ccc73934363f33cc7af71dc07545aeff1d92f8c2f0b2973e1fc37e7b2de3551c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139006e00019999992e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400102006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80102005701000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" ); + assertTrue(success, "Call Failed"); assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99574171); vm.stopPrank(); @@ -289,26 +291,27 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005eea514000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000682165ac00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9dfb400000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004107f2b0f9c2e4e308ab43b288d69de30d84b10c8075e4dd9a2cf66594f97a52fb34de2534b89bf1887da74c92fd03464f45baff700dd32e213e3add1a3f351e891b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d013950102006e01009999992e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400000006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d8000000000000000000" ); + assertTrue(success, "Call Failed"); assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99525908); vm.stopPrank(); } - // TODO uncomment when Curve TokenTransfer is implemented (next commits) - // function testSplitCurveIntegration() public { - // deal(UWU_ADDR, ALICE, 1 ether); - // - // vm.startPrank(ALICE); - // IERC20(UWU_ADDR).approve(tychoRouterAddr, type(uint256).max); - // // Encoded solution generated using `test_split_encoding_strategy_curve` - // (bool success,) = tychoRouterAddr.call( - // hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000055c08ca52497e2f1534b59e2917bf524d4765257000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000005b005900010000001d1499e622d69689cdf9004d05ec547d650ff21155c08ca52497e2f1534b59e2917bf524d4765257c02aaa39b223fe8d0a0e5c4f27ead9083c756cc277146b0a1d08b6844376df6d9da99ba7f1b19e71020100010000000000" - // ); - // - // assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 4691958787921); - // - // vm.stopPrank(); - // } + function testSplitCurveIntegration() public { + deal(UWU_ADDR, ALICE, 1 ether); + + vm.startPrank(ALICE); + IERC20(UWU_ADDR).approve(tychoRouterAddr, type(uint256).max); + // Encoded solution generated using `test_split_encoding_strategy_curve` + (bool success,) = tychoRouterAddr.call( + hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000055c08ca52497e2f1534b59e2917bf524d4765257000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000005c005a00010000001d1499e622d69689cdf9004d05ec547d650ff21155c08ca52497e2f1534b59e2917bf524d4765257c02aaa39b223fe8d0a0e5c4f27ead9083c756cc277146b0a1d08b6844376df6d9da99ba7f1b19e71020100010300000000" + ); + + assertTrue(success, "Call Failed"); + assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 4691958787921); + + vm.stopPrank(); + } function testSplitCurveIntegrationStETH() public { deal(ALICE, 1 ether); @@ -316,9 +319,10 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { vm.startPrank(ALICE); // Encoded solution generated using `test_split_encoding_strategy_curve_st_eth` (bool success,) = tychoRouterAddr.call{value: 1 ether}( - hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe840000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000005b005900010000001d1499e622d69689cdf9004d05ec547d650ff211eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeae7ab96520de3a18e5e111b5eaab095312d7fe84dc24316b9ae028f1497c275eb9192a3ea0f67022010001000000000000" + hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe840000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000005c005a00010000001d1499e622d69689cdf9004d05ec547d650ff211eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeae7ab96520de3a18e5e111b5eaab095312d7fe84dc24316b9ae028f1497c275eb9192a3ea0f67022010001000500000000" ); + assertTrue(success, "Call Failed"); assertEq(IERC20(STETH_ADDR).balanceOf(ALICE), 1000754689941529590); vm.stopPrank(); diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index d556e35..d5b9651 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -108,7 +108,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper { ); balancerv2Executor = new BalancerV2Executor(); ekuboExecutor = new EkuboExecutor(ekuboCore); - curveExecutor = new CurveExecutor(ETH_ADDR_FOR_CURVE); + curveExecutor = new CurveExecutor(ETH_ADDR_FOR_CURVE, PERMIT2_ADDRESS); address[] memory executors = new address[](7); executors[0] = address(usv2Executor); diff --git a/foundry/test/executors/CurveExecutor.t.sol b/foundry/test/executors/CurveExecutor.t.sol index cd15079..a49a2dc 100644 --- a/foundry/test/executors/CurveExecutor.t.sol +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -22,7 +22,9 @@ interface MetaRegistry { } contract CurveExecutorExposed is CurveExecutor { - constructor(address _nativeToken) CurveExecutor(_nativeToken) {} + constructor(address _nativeToken, address _permit2) + CurveExecutor(_nativeToken, _permit2) + {} function decodeData(bytes calldata data) external @@ -34,7 +36,8 @@ contract CurveExecutorExposed is CurveExecutor { uint8 poolType, int128 i, int128 j, - bool tokenApprovalNeeded + bool tokenApprovalNeeded, + TokenTransfer.TransferType transferType ) { return _decodeData(data); @@ -50,7 +53,8 @@ contract CurveExecutorTest is Test, Constants { function setUp() public { uint256 forkBlock = 22031795; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); - curveExecutorExposed = new CurveExecutorExposed(ETH_ADDR_FOR_CURVE); + curveExecutorExposed = + new CurveExecutorExposed(ETH_ADDR_FOR_CURVE, PERMIT2_ADDRESS); metaRegistry = MetaRegistry(CURVE_META_REGISTRY); } @@ -62,7 +66,8 @@ contract CurveExecutorTest is Test, Constants { uint8(3), uint8(2), uint8(0), - true + true, + TokenTransfer.TransferType.NONE ); ( @@ -72,7 +77,8 @@ contract CurveExecutorTest is Test, Constants { uint8 poolType, int128 i, int128 j, - bool tokenApprovalNeeded + bool tokenApprovalNeeded, + TokenTransfer.TransferType transferType ) = curveExecutorExposed.decodeData(data); assertEq(tokenIn, WETH_ADDR); @@ -82,6 +88,7 @@ contract CurveExecutorTest is Test, Constants { assertEq(i, 2); assertEq(j, 0); assertEq(tokenApprovalNeeded, true); + assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE)); } function testTriPool() public { @@ -311,7 +318,8 @@ contract CurveExecutorTest is Test, Constants { poolType, uint8(uint256(uint128(i))), uint8(uint256(uint128(j))), - true + true, + TokenTransfer.TransferType.NONE ); } diff --git a/src/encoding/evm/constants.rs b/src/encoding/evm/constants.rs index 2e6ad96..4eadb35 100644 --- a/src/encoding/evm/constants.rs +++ b/src/encoding/evm/constants.rs @@ -31,7 +31,7 @@ pub static IN_TRANSFER_OPTIMIZABLE_PROTOCOLS: LazyLock> = pub static PROTOCOLS_EXPECTING_FUNDS_IN_ROUTER: LazyLock> = LazyLock::new(|| { let mut set = HashSet::new(); - set.insert("curve"); + set.insert("vm:curve"); set.insert("balancer_v2"); // TODO remove uniswap_v4 when we add callback support for transfer optimizations set.insert("uniswap_v4"); diff --git a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs index ff43ed1..77c23d4 100644 --- a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs +++ b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs @@ -27,11 +27,11 @@ pub trait TransferOptimization { let is_first_swap = (swap.token_in == given_token) || ((swap.token_in == wrapped_token) && wrap); - if is_first_swap && send_funds_to_pool { - if swap.token_in == native_token { - // Funds are already in router. Protocol takes care of native transfer. - TransferType::None - } else if permit2 { + if swap.token_in == native_token { + // Funds are already in router. All protocols currently take care of native transfers. + TransferType::None + } else if is_first_swap && send_funds_to_pool { + if permit2 { // Transfer from swapper to pool using permit2. TransferType::Permit2Transfer } else { @@ -39,10 +39,7 @@ pub trait TransferOptimization { TransferType::TransferFrom } } else if is_first_swap && funds_expected_in_router { - if swap.token_in == native_token { - // Funds already in router. Do nothing. - TransferType::None - } else if permit2 { + if permit2 { // Transfer from swapper to router using permit2. TransferType::Permit2TransferToRouter } else { diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 3fd1453..2ea8954 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -545,6 +545,7 @@ impl SwapEncoder for CurveSwapEncoder { i.to_be_bytes::<1>(), j.to_be_bytes::<1>(), approval_needed, + (encoding_context.transfer_type as u8).to_be_bytes(), ); Ok(args.abi_encode_packed()) @@ -1263,6 +1264,8 @@ mod tests { "01", // approval needed "01", + // transfer type + "05", )) ); } @@ -1329,6 +1332,8 @@ mod tests { "00", // approval needed "01", + // transfer type + "05", )) ); } @@ -1405,6 +1410,8 @@ mod tests { "01", // approval needed "01", + // transfer type + "05", )) ); } From 3a73fef9334f3da49247f666866e5b76d44c40dd Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 14 Apr 2025 17:15:19 -0400 Subject: [PATCH 12/13] feat: Add TokenTransfer class to BalancerV2 - This needed to be in BalancerV2 so that the executor can also take care of transfers from the user into the tycho router, to avoid having transfer actions mixed between the router and executors --- foundry/src/executors/BalancerV2Executor.sol | 24 +++++++++-- foundry/src/executors/CurveExecutor.sol | 10 ++++- foundry/src/executors/UniswapV4Executor.sol | 4 +- foundry/test/TychoRouterTestSetup.sol | 2 +- .../test/executors/BalancerV2Executor.t.sol | 42 ++++++++++++------- .../evm/swap_encoder/swap_encoders.rs | 7 +++- 6 files changed, 66 insertions(+), 23 deletions(-) diff --git a/foundry/src/executors/BalancerV2Executor.sol b/foundry/src/executors/BalancerV2Executor.sol index 0f69fcd..817c98b 100644 --- a/foundry/src/executors/BalancerV2Executor.sol +++ b/foundry/src/executors/BalancerV2Executor.sol @@ -10,14 +10,17 @@ import { import {IAsset} from "@balancer-labs/v2-interfaces/contracts/vault/IAsset.sol"; // slither-disable-next-line solc-version import {IVault} from "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol"; +import {TokenTransfer} from "./TokenTransfer.sol"; error BalancerV2Executor__InvalidDataLength(); -contract BalancerV2Executor is IExecutor { +contract BalancerV2Executor is IExecutor, TokenTransfer { using SafeERC20 for IERC20; address private constant VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8; + constructor(address _permit2) TokenTransfer(_permit2) {} + // slither-disable-next-line locked-ether function swap(uint256 givenAmount, bytes calldata data) external @@ -29,9 +32,20 @@ contract BalancerV2Executor is IExecutor { IERC20 tokenOut, bytes32 poolId, address receiver, - bool needsApproval + bool needsApproval, + TransferType transferType ) = _decodeData(data); + _transfer( + address(tokenIn), + msg.sender, + // Receiver can never be the pool, since the pool expects funds in the router contract + // Thus, this call will only ever be used to transfer funds from the user into the router. + address(this), + givenAmount, + transferType + ); + if (needsApproval) { // slither-disable-next-line unused-return tokenIn.approve(VAULT, type(uint256).max); @@ -67,10 +81,11 @@ contract BalancerV2Executor is IExecutor { IERC20 tokenOut, bytes32 poolId, address receiver, - bool needsApproval + bool needsApproval, + TransferType transferType ) { - if (data.length != 93) { + if (data.length != 94) { revert BalancerV2Executor__InvalidDataLength(); } @@ -79,5 +94,6 @@ contract BalancerV2Executor is IExecutor { poolId = bytes32(data[40:72]); receiver = address(bytes20(data[72:92])); needsApproval = uint8(data[92]) > 0; + transferType = TransferType(uint8(data[93])); } } diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index 9a8c1e6..38173e9 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -64,7 +64,15 @@ contract CurveExecutor is IExecutor, TokenTransfer { TransferType transferType ) = _decodeData(data); - _transfer(tokenIn, msg.sender, pool, amountIn, transferType); + _transfer( + tokenIn, + msg.sender, + // Receiver can never be the pool, since the pool expects funds in the router contract + // Thus, this call will only ever be used to transfer funds from the user into the router. + address(this), + amountIn, + transferType + ); if (tokenApprovalNeeded && tokenIn != nativeToken) { // slither-disable-next-line unused-return diff --git a/foundry/src/executors/UniswapV4Executor.sol b/foundry/src/executors/UniswapV4Executor.sol index adcea98..aafbd7d 100644 --- a/foundry/src/executors/UniswapV4Executor.sol +++ b/foundry/src/executors/UniswapV4Executor.sol @@ -53,7 +53,9 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer { _transfer( tokenIn, msg.sender, - address(this), // irrelevant attribute + // Receiver can never be the pool, since the pool expects funds in the router contract + // Thus, this call will only ever be used to transfer funds from the user into the router. + address(this), amountIn, transferType ); diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index d5b9651..83b665f 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -106,7 +106,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper { pancakev3Executor = new UniswapV3Executor( factoryPancakeV3, initCodePancakeV3, PERMIT2_ADDRESS ); - balancerv2Executor = new BalancerV2Executor(); + balancerv2Executor = new BalancerV2Executor(PERMIT2_ADDRESS); ekuboExecutor = new EkuboExecutor(ekuboCore); curveExecutor = new CurveExecutor(ETH_ADDR_FOR_CURVE, PERMIT2_ADDRESS); diff --git a/foundry/test/executors/BalancerV2Executor.t.sol b/foundry/test/executors/BalancerV2Executor.t.sol index 9187187..78b435b 100644 --- a/foundry/test/executors/BalancerV2Executor.t.sol +++ b/foundry/test/executors/BalancerV2Executor.t.sol @@ -6,6 +6,8 @@ import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; contract BalancerV2ExecutorExposed is BalancerV2Executor { + constructor(address _permit2) BalancerV2Executor(_permit2) {} + function decodeParams(bytes calldata data) external pure @@ -14,18 +16,15 @@ contract BalancerV2ExecutorExposed is BalancerV2Executor { IERC20 tokenOut, bytes32 poolId, address receiver, - bool needsApproval + bool needsApproval, + TransferType transferType ) { return _decodeData(data); } } -contract BalancerV2ExecutorTest is - BalancerV2ExecutorExposed, - Test, - Constants -{ +contract BalancerV2ExecutorTest is Test, Constants { using SafeERC20 for IERC20; BalancerV2ExecutorExposed balancerV2Exposed; @@ -37,12 +36,17 @@ contract BalancerV2ExecutorTest is function setUp() public { uint256 forkBlock = 17323404; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); - balancerV2Exposed = new BalancerV2ExecutorExposed(); + balancerV2Exposed = new BalancerV2ExecutorExposed(PERMIT2_ADDRESS); } function testDecodeParams() public view { bytes memory params = abi.encodePacked( - WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, address(2), true + WETH_ADDR, + BAL_ADDR, + WETH_BAL_POOL_ID, + address(2), + true, + TokenTransfer.TransferType.NONE ); ( @@ -50,7 +54,8 @@ contract BalancerV2ExecutorTest is IERC20 tokenOut, bytes32 poolId, address receiver, - bool needsApproval + bool needsApproval, + TokenTransfer.TransferType transferType ) = balancerV2Exposed.decodeParams(params); assertEq(address(tokenIn), WETH_ADDR); @@ -58,6 +63,7 @@ contract BalancerV2ExecutorTest is assertEq(poolId, WETH_BAL_POOL_ID); assertEq(receiver, address(2)); assertEq(needsApproval, true); + assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE)); } function testDecodeParamsInvalidDataLength() public { @@ -70,8 +76,14 @@ contract BalancerV2ExecutorTest is function testSwap() public { uint256 amountIn = 10 ** 18; - bytes memory protocolData = - abi.encodePacked(WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, BOB, true); + bytes memory protocolData = abi.encodePacked( + WETH_ADDR, + BAL_ADDR, + WETH_BAL_POOL_ID, + BOB, + true, + TokenTransfer.TransferType.NONE + ); deal(WETH_ADDR, address(balancerV2Exposed), amountIn); uint256 balanceBefore = BAL.balanceOf(BOB); @@ -86,14 +98,15 @@ contract BalancerV2ExecutorTest is function testDecodeIntegration() public view { // Generated by the SwapEncoder - test_encode_balancer_v2 bytes memory protocolData = - hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754423978a60c9317c58a424e3d5c6ee304399dbdb9c8ef030ab642b10820db8f560002000000000000000000141d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e01"; + hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754423978a60c9317c58a424e3d5c6ee304399dbdb9c8ef030ab642b10820db8f560002000000000000000000141d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0105"; ( IERC20 tokenIn, IERC20 tokenOut, bytes32 poolId, address receiver, - bool needsApproval + bool needsApproval, + TokenTransfer.TransferType transferType ) = balancerV2Exposed.decodeParams(protocolData); assertEq(address(tokenIn), WETH_ADDR); @@ -101,12 +114,13 @@ contract BalancerV2ExecutorTest is assertEq(poolId, WETH_BAL_POOL_ID); assertEq(receiver, BOB); assertEq(needsApproval, true); + assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE)); } function testSwapIntegration() public { // Generated by the SwapEncoder - test_encode_balancer_v2 bytes memory protocolData = - hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754423978a60c9317c58a424e3d5c6ee304399dbdb9c8ef030ab642b10820db8f560002000000000000000000141d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e01"; + hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754423978a60c9317c58a424e3d5c6ee304399dbdb9c8ef030ab642b10820db8f560002000000000000000000141d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0105"; uint256 amountIn = 10 ** 18; deal(WETH_ADDR, address(balancerV2Exposed), amountIn); diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 2ea8954..1602ae3 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -281,6 +281,7 @@ impl SwapEncoder for BalancerV2SwapEncoder { component_id, bytes_to_address(&encoding_context.receiver)?, approval_needed, + (encoding_context.transfer_type as u8).to_be_bytes(), ); Ok(args.abi_encode_packed()) } @@ -703,7 +704,7 @@ mod tests { router_address: Some(Bytes::zero(20)), group_token_in: token_in.clone(), group_token_out: token_out.clone(), - transfer_type: TransferType::Transfer, + transfer_type: TransferType::None, }; let encoder = BalancerV2SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -731,7 +732,9 @@ mod tests { // receiver "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", // approval needed - "01" + "01", + // transfer type + "05" )) ); } From 30dc3f7682b0cf41f82efa03e75c380101cd0db5 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Tue, 15 Apr 2025 12:44:03 -0400 Subject: [PATCH 13/13] chore: renamings for clarity - Also added a check for length in the Curve executor. --- foundry/src/TychoRouter.sol | 3 -- foundry/src/executors/CurveExecutor.sol | 3 ++ foundry/src/executors/TokenTransfer.sol | 16 +++++------ foundry/test/TychoRouterSequentialSwap.t.sol | 18 ++++++------ foundry/test/TychoRouterSingleSwap.t.sol | 14 +++++----- foundry/test/TychoRouterSplitSwap.t.sol | 28 +++++++++---------- .../test/executors/UniswapV2Executor.t.sol | 15 +++++----- .../test/executors/UniswapV3Executor.t.sol | 9 +++--- .../test/executors/UniswapV4Executor.t.sol | 2 +- .../transfer_optimizations.rs | 18 ++++++------ .../evm/swap_encoder/swap_encoders.rs | 14 +++++----- src/encoding/evm/tycho_encoders.rs | 2 +- src/encoding/models.rs | 20 ++++++------- 13 files changed, 82 insertions(+), 80 deletions(-) diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 32b54a5..1b693fd 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -58,9 +58,6 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; error TychoRouter__AddressZero(); error TychoRouter__EmptySwaps(); error TychoRouter__NegativeSlippage(uint256 amount, uint256 minAmount); -error TychoRouter__AmountInDiffersFromConsumed( - uint256 amountIn, uint256 amountConsumed -); error TychoRouter__MessageValueMismatch(uint256 value, uint256 amount); error TychoRouter__InvalidDataLength(); error TychoRouter__UndefinedMinAmountOut(); diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index 38173e9..933077e 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -6,6 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "./TokenTransfer.sol"; error CurveExecutor__AddressZero(); +error CurveExecutor__InvalidDataLength(); interface CryptoPool { // slither-disable-next-line naming-convention @@ -53,6 +54,8 @@ contract CurveExecutor is IExecutor, TokenTransfer { payable returns (uint256) { + if (data.length != 65) revert CurveExecutor__InvalidDataLength(); + ( address tokenIn, address tokenOut, diff --git a/foundry/src/executors/TokenTransfer.sol b/foundry/src/executors/TokenTransfer.sol index 886e1f9..b5f8629 100644 --- a/foundry/src/executors/TokenTransfer.sol +++ b/foundry/src/executors/TokenTransfer.sol @@ -14,15 +14,15 @@ contract TokenTransfer { enum TransferType { // Assume funds are in the TychoRouter - transfer into the pool - TRANSFER, + TRANSFER_TO_PROTOCOL, // Assume funds are in msg.sender's wallet - transferFrom into the pool - TRANSFER_FROM, + TRANSFER_FROM_TO_PROTOCOL, // Assume funds are in msg.sender's wallet - permit2TransferFrom into the pool - TRANSFER_PERMIT2, + TRANSFER_PERMIT2_TO_PROTOCOL, // Assume funds are in msg.sender's wallet - but the pool requires it to be // in the router contract when calling swap - transferFrom into the router // contract - TRANSFER_TO_ROUTER, + TRANSFER_FROM_TO_ROUTER, // Assume funds are in msg.sender's wallet - but the pool requires it to be // in the router contract when calling swap - transferFrom into the router // contract using permit2 @@ -45,19 +45,19 @@ contract TokenTransfer { uint256 amount, TransferType transferType ) internal { - if (transferType == TransferType.TRANSFER) { + if (transferType == TransferType.TRANSFER_TO_PROTOCOL) { if (tokenIn == address(0)) { payable(receiver).transfer(amount); } else { IERC20(tokenIn).safeTransfer(receiver, amount); } - } else if (transferType == TransferType.TRANSFER_FROM) { + } else if (transferType == TransferType.TRANSFER_FROM_TO_PROTOCOL) { // slither-disable-next-line arbitrary-send-erc20 IERC20(tokenIn).safeTransferFrom(sender, receiver, amount); - } else if (transferType == TransferType.TRANSFER_PERMIT2) { + } else if (transferType == TransferType.TRANSFER_PERMIT2_TO_PROTOCOL) { // Permit2.permit is already called from the TychoRouter permit2.transferFrom(sender, receiver, uint160(amount), tokenIn); - } else if (transferType == TransferType.TRANSFER_TO_ROUTER) { + } else if (transferType == TransferType.TRANSFER_FROM_TO_ROUTER) { // slither-disable-next-line arbitrary-send-erc20 IERC20(tokenIn).safeTransferFrom(sender, address(this), amount); } else if (transferType == TransferType.TRANSFER_PERMIT2_TO_ROUTER) { diff --git a/foundry/test/TychoRouterSequentialSwap.t.sol b/foundry/test/TychoRouterSequentialSwap.t.sol index 031f66c..9efd39b 100644 --- a/foundry/test/TychoRouterSequentialSwap.t.sol +++ b/foundry/test/TychoRouterSequentialSwap.t.sol @@ -18,8 +18,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { // (univ2) (univ2) TokenTransfer.TransferType transferType = permit2 - ? TokenTransfer.TransferType.TRANSFER_PERMIT2 - : TokenTransfer.TransferType.TRANSFER_FROM; + ? TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL + : TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL; bytes[] memory swaps = new bytes[](2); // WETH -> DAI @@ -38,7 +38,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { DAI_USDC_POOL, tychoRouterAddr, true, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ) ); return swaps; @@ -222,7 +222,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, tychoRouterAddr, false, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ) ); @@ -234,7 +234,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { DAI_USDC_POOL, tychoRouterAddr, true, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ) ); @@ -282,7 +282,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { DAI_USDC_POOL, tychoRouterAddr, false, - TokenTransfer.TransferType.TRANSFER_PERMIT2 + TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL ) ); @@ -294,7 +294,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, tychoRouterAddr, true, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ) ); @@ -331,7 +331,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3, true, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ); bytes memory usdcWethV3Pool2OneZeroData = encodeUniswapV3Swap( @@ -340,7 +340,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3_2, false, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ); bytes[] memory swaps = new bytes[](2); diff --git a/foundry/test/TychoRouterSingleSwap.t.sol b/foundry/test/TychoRouterSingleSwap.t.sol index 2c85586..8a8a4d3 100644 --- a/foundry/test/TychoRouterSingleSwap.t.sol +++ b/foundry/test/TychoRouterSingleSwap.t.sol @@ -26,7 +26,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, tychoRouterAddr, false, - TokenTransfer.TransferType.TRANSFER_PERMIT2 + TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL ); bytes memory swap = @@ -67,7 +67,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, tychoRouterAddr, false, - TokenTransfer.TransferType.TRANSFER_FROM + TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL ); bytes memory swap = @@ -108,7 +108,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, tychoRouterAddr, false, - TokenTransfer.TransferType.TRANSFER_FROM + TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL ); bytes memory swap = @@ -134,7 +134,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, tychoRouterAddr, false, - TokenTransfer.TransferType.TRANSFER_FROM + TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL ); bytes memory swap = @@ -169,7 +169,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, tychoRouterAddr, false, - TokenTransfer.TransferType.TRANSFER_FROM + TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL ); bytes memory swap = @@ -218,7 +218,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, tychoRouterAddr, false, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ); bytes memory swap = @@ -261,7 +261,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, tychoRouterAddr, true, - TokenTransfer.TransferType.TRANSFER_PERMIT2 + TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL ); bytes memory swap = diff --git a/foundry/test/TychoRouterSplitSwap.t.sol b/foundry/test/TychoRouterSplitSwap.t.sol index a182ecd..00f8406 100644 --- a/foundry/test/TychoRouterSplitSwap.t.sol +++ b/foundry/test/TychoRouterSplitSwap.t.sol @@ -21,8 +21,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { bytes[] memory swaps = new bytes[](4); TokenTransfer.TransferType inTransferType = permit2 - ? TokenTransfer.TransferType.TRANSFER_PERMIT2 - : TokenTransfer.TransferType.TRANSFER_FROM; + ? TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL + : TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL; // WETH -> WBTC (60%) swaps[0] = encodeSplitSwap( @@ -49,7 +49,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { USDC_WBTC_POOL, tychoRouterAddr, true, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ) ); // WETH -> DAI @@ -74,7 +74,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { DAI_USDC_POOL, tychoRouterAddr, true, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ) ); @@ -270,7 +270,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, tychoRouterAddr, false, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ); bytes memory swap = encodeSplitSwap( @@ -319,7 +319,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, tychoRouterAddr, true, - TokenTransfer.TransferType.TRANSFER_PERMIT2 + TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL ); bytes memory swap = encodeSplitSwap( @@ -370,7 +370,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tychoRouterAddr, DAI_WETH_USV3, zeroForOne, - TokenTransfer.TransferType.TRANSFER_PERMIT2 + TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL ); bytes memory swap = encodeSplitSwap( uint8(0), uint8(1), uint24(0), address(usv3Executor), protocolData @@ -511,7 +511,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3, true, - TokenTransfer.TransferType.TRANSFER_FROM + TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL ); bytes memory usdcWethV3Pool2ZeroOneData = encodeUniswapV3Swap( @@ -520,7 +520,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3_2, true, - TokenTransfer.TransferType.TRANSFER_FROM + TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL ); bytes memory wethUsdcV2OneZeroData = encodeUniswapV2Swap( @@ -528,7 +528,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { USDC_WETH_USV2, tychoRouterAddr, false, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ); bytes[] memory swaps = new bytes[](3); @@ -578,7 +578,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { USDC_WETH_USV2, tychoRouterAddr, true, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ); bytes memory usdcWethV3Pool1OneZeroData = encodeUniswapV3Swap( @@ -587,7 +587,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3, false, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ); bytes memory usdcWethV3Pool2OneZeroData = encodeUniswapV3Swap( @@ -596,7 +596,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3_2, false, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ); bytes[] memory swaps = new bytes[](3); @@ -639,7 +639,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { USDC_MAG7_POOL, tychoRouterAddr, true, - TokenTransfer.TransferType.TRANSFER_FROM + TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL ); bytes memory swap = encodeSplitSwap( diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index a001a1e..64be1d2 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -84,7 +84,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { address(2), address(3), false, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ); ( @@ -100,7 +100,8 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { assertEq(receiver, address(3)); assertEq(zeroForOne, false); assertEq( - uint8(TokenTransfer.TransferType.TRANSFER), uint8(transferType) + uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), + uint8(transferType) ); } @@ -157,7 +158,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { WETH_DAI_POOL, BOB, zeroForOne, - uint8(TokenTransfer.TransferType.TRANSFER) + uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL) ); deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); @@ -176,7 +177,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { WETH_DAI_POOL, BOB, zeroForOne, - uint8(TokenTransfer.TransferType.TRANSFER_FROM) + uint8(TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL) ); deal(WETH_ADDR, address(this), amountIn); @@ -197,7 +198,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { WETH_DAI_POOL, ALICE, zeroForOne, - uint8(TokenTransfer.TransferType.TRANSFER_PERMIT2) + uint8(TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL) ); deal(WETH_ADDR, ALICE, amountIn); @@ -283,7 +284,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { fakePool, BOB, zeroForOne, - uint8(TokenTransfer.TransferType.TRANSFER) + uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL) ); deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); @@ -303,7 +304,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { USDC_MAG7_POOL, BOB, zeroForOne, - uint8(TokenTransfer.TransferType.TRANSFER) + uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL) ); deal(BASE_USDC, address(uniswapV2Exposed), amountIn); diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index 5b134b8..cd77697 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -71,7 +71,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address(2), address(3), false, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ); ( @@ -91,7 +91,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { assertEq(target, address(3)); assertEq(zeroForOne, false); assertEq( - uint8(transferType), uint8(TokenTransfer.TransferType.TRANSFER) + uint8(transferType), + uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL) ); } @@ -126,7 +127,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { WETH_ADDR, DAI_ADDR, poolFee, - TokenTransfer.TransferType.TRANSFER, + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL, address(uniswapV3Exposed) ); uint256 dataOffset = 3; // some offset @@ -160,7 +161,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address(this), fakePool, zeroForOne, - TokenTransfer.TransferType.TRANSFER + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL ); vm.expectRevert(UniswapV3Executor__InvalidTarget.selector); diff --git a/foundry/test/executors/UniswapV4Executor.t.sol b/foundry/test/executors/UniswapV4Executor.t.sol index 4c925f2..62f61c9 100644 --- a/foundry/test/executors/UniswapV4Executor.t.sol +++ b/foundry/test/executors/UniswapV4Executor.t.sol @@ -52,7 +52,7 @@ contract UniswapV4ExecutorTest is Test, Constants { uint24 pool2Fee = 1000; int24 tickSpacing2 = -10; TokenTransfer.TransferType transferType = - TokenTransfer.TransferType.TRANSFER_FROM; + TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL; UniswapV4Executor.UniswapV4Pool[] memory pools = new UniswapV4Executor.UniswapV4Pool[](2); diff --git a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs index 77c23d4..5188986 100644 --- a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs +++ b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs @@ -33,21 +33,21 @@ pub trait TransferOptimization { } else if is_first_swap && send_funds_to_pool { if permit2 { // Transfer from swapper to pool using permit2. - TransferType::Permit2Transfer + TransferType::TransferPermit2ToProtocol } else { // Transfer from swapper to pool. - TransferType::TransferFrom + TransferType::TransferFromToProtocol } } else if is_first_swap && funds_expected_in_router { if permit2 { // Transfer from swapper to router using permit2. - TransferType::Permit2TransferToRouter + TransferType::TransferPermit2ToRouter } else { // Transfer from swapper to router. - TransferType::TransferToRouter + TransferType::TransferFromToRouter } } else { - TransferType::Transfer + TransferType::TransferToProtocol } } } @@ -93,7 +93,7 @@ mod tests { let strategy = MockStrategy {}; let transfer_method = strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), true, false); - assert_eq!(transfer_method, TransferType::Permit2Transfer); + assert_eq!(transfer_method, TransferType::TransferPermit2ToProtocol); } #[test] @@ -111,7 +111,7 @@ mod tests { let strategy = MockStrategy {}; let transfer_method = strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false); - assert_eq!(transfer_method, TransferType::TransferFrom); + assert_eq!(transfer_method, TransferType::TransferFromToProtocol); } #[test] @@ -149,7 +149,7 @@ mod tests { let strategy = MockStrategy {}; let transfer_method = strategy.get_transfer_type(swap.clone(), eth(), eth(), weth(), false, true); - assert_eq!(transfer_method, TransferType::TransferFrom); + assert_eq!(transfer_method, TransferType::TransferFromToProtocol); } #[test] @@ -168,6 +168,6 @@ mod tests { let strategy = MockStrategy {}; let transfer_method = strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false); - assert_eq!(transfer_method, TransferType::Transfer); + assert_eq!(transfer_method, TransferType::TransferToProtocol); } } diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 1602ae3..0ec212e 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -595,7 +595,7 @@ mod tests { router_address: Some(Bytes::zero(20)), group_token_in: token_in.clone(), group_token_out: token_out.clone(), - transfer_type: TransferType::Transfer, + transfer_type: TransferType::TransferToProtocol, }; let encoder = UniswapV2SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -649,7 +649,7 @@ mod tests { router_address: Some(Bytes::zero(20)), group_token_in: token_in.clone(), group_token_out: token_out.clone(), - transfer_type: TransferType::Transfer, + transfer_type: TransferType::TransferToProtocol, }; let encoder = UniswapV3SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -773,7 +773,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), - transfer_type: TransferType::Transfer, + transfer_type: TransferType::TransferToProtocol, }; let encoder = UniswapV4SwapEncoder::new( String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"), @@ -842,7 +842,7 @@ mod tests { group_token_in: group_token_in.clone(), // Token out is the same as the group token out group_token_out: token_out.clone(), - transfer_type: TransferType::Transfer, + transfer_type: TransferType::TransferToProtocol, }; let encoder = UniswapV4SwapEncoder::new( @@ -885,7 +885,7 @@ mod tests { router_address: Some(router_address.clone()), group_token_in: usde_address.clone(), group_token_out: wbtc_address.clone(), - transfer_type: TransferType::Transfer, + transfer_type: TransferType::TransferToProtocol, }; // Setup - First sequence: USDE -> USDT @@ -1017,7 +1017,7 @@ mod tests { group_token_out: token_out.clone(), exact_out: false, router_address: Some(Bytes::default()), - transfer_type: TransferType::Transfer, + transfer_type: TransferType::TransferToProtocol, }; let encoder = @@ -1060,7 +1060,7 @@ mod tests { group_token_out: group_token_out.clone(), exact_out: false, router_address: Some(Bytes::default()), - transfer_type: TransferType::Transfer, + transfer_type: TransferType::TransferToProtocol, }; let first_swap = Swap { diff --git a/src/encoding/evm/tycho_encoders.rs b/src/encoding/evm/tycho_encoders.rs index 40beb97..1ecf41f 100644 --- a/src/encoding/evm/tycho_encoders.rs +++ b/src/encoding/evm/tycho_encoders.rs @@ -260,7 +260,7 @@ impl TychoExecutorEncoder { router_address: None, group_token_in: grouped_swap.input_token.clone(), group_token_out: grouped_swap.output_token.clone(), - transfer_type: TransferType::Transfer, + transfer_type: TransferType::TransferToProtocol, }; let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?; grouped_protocol_data.extend(protocol_data); diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 9d5fc47..052df36 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -100,20 +100,20 @@ pub struct Transaction { /// /// # Fields /// -/// * `Transfer`: Transfer the token from the router into the pool. -/// * `TransferFrom`: Transfer the token from the swapper to the pool. -/// * `Permit2Transfer`: Transfer the token from the sender to the pool using Permit2. -/// * `TransferToRouter`: Transfer the token from the swapper to the router. -/// * `Permit2TransferToRouter`: Transfer the token from the swapper to the router using Permit2. +/// * `TransferToProtocol`: Transfer the token from the router into the protocol. +/// * `TransferFromToProtocol`: Transfer the token from the sender to the protocol. +/// * `TransferPermit2ToProtocol`: Transfer the token from the sender to the protocol using Permit2. +/// * `TransferFromToRouter`: Transfer the token from the sender to the router. +/// * `TransferPermit2ToRouter`: Transfer the token from the sender to the router using Permit2. /// * `None`: No transfer is needed. Tokens are already in the pool. #[repr(u8)] #[derive(Clone, Debug, PartialEq)] pub enum TransferType { - Transfer = 0, - TransferFrom = 1, - Permit2Transfer = 2, - TransferToRouter = 3, - Permit2TransferToRouter = 4, + TransferToProtocol = 0, + TransferFromToProtocol = 1, + TransferPermit2ToProtocol = 2, + TransferFromToRouter = 3, + TransferPermit2ToRouter = 4, None = 5, }