diff --git a/src/encoding/evm/encoder_builders.rs b/src/encoding/evm/encoder_builders.rs index b6b6497..90f641b 100644 --- a/src/encoding/evm/encoder_builders.rs +++ b/src/encoding/evm/encoder_builders.rs @@ -23,6 +23,7 @@ pub struct TychoRouterEncoderBuilder { executors_file_path: Option, router_address: Option, swapper_pk: Option, + historical_trade: bool, } impl Default for TychoRouterEncoderBuilder { @@ -39,6 +40,7 @@ impl TychoRouterEncoderBuilder { router_address: None, swapper_pk: None, user_transfer_type: None, + historical_trade: false, } } pub fn chain(mut self, chain: Chain) -> Self { @@ -65,6 +67,15 @@ impl TychoRouterEncoderBuilder { self } + /// Sets the `historical_trade` manually to true. + /// If set to true, it means that the encoded trade will be used in an historical block (as a + /// test) and not in the current one. This is relevant for checking token approvals in some + /// protocols (like Balancer v2). + pub fn historical_trade(mut self) -> Self { + self.historical_trade = true; + self + } + /// Sets the `swapper_pk` for the encoder. This is used to sign permit2 objects. This is only /// needed if you intend to get the full calldata for the transfer. We do not recommend /// using this option, you should sign and create the function calldata entirely on your @@ -115,6 +126,7 @@ impl TychoRouterEncoderBuilder { tycho_router_address, user_transfer_type, signer, + self.historical_trade, )?)) } else { Err(EncodingError::FatalError( diff --git a/src/encoding/evm/encoding_utils.rs b/src/encoding/evm/encoding_utils.rs index 1e31f2c..5b636c2 100644 --- a/src/encoding/evm/encoding_utils.rs +++ b/src/encoding/evm/encoding_utils.rs @@ -58,11 +58,12 @@ use crate::encoding::{ /// funds. /// /// # Parameters +/// - `chain_id`: Chain ID /// - `encoded_solution`: The solution already encoded by Tycho. /// - `solution`: The high-level solution including tokens, amounts, and receiver info. -/// - `token_in_already_in_router`: Whether the input token is already present in the router. -/// - `router_address`: The address of the Tycho Router contract. +/// - `user_transfer_type`: The desired transfer method. /// - `native_address`: The address used to represent the native token +/// - `signer`: Optional signer for permit2 /// /// # Returns /// A `Result` that either contains the full transaction data (to, diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index fa47be8..e0c1255 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -26,12 +26,15 @@ use crate::encoding::{ /// * `function_signature`: String, the signature for the swap function in the router contract /// * `router_address`: Address of the router to be used to execute swaps /// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers +/// * `historical_trade`: Whether the swap is to be done in the current block or in an historical +/// one. This is relevant for checking token approvals in some protocols (like Balancer v2). #[derive(Clone)] pub struct SingleSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, function_signature: String, router_address: Bytes, transfer_optimization: TransferOptimization, + historical_trade: bool, } impl SingleSwapStrategyEncoder { @@ -40,6 +43,7 @@ impl SingleSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, user_transfer_type: UserTransferType, router_address: Bytes, + historical_trade: bool, ) -> Result { let function_signature = if user_transfer_type == UserTransferType::TransferFromPermit2 { "singleSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)" @@ -57,6 +61,7 @@ impl SingleSwapStrategyEncoder { user_transfer_type, router_address, ), + historical_trade, }) } @@ -119,6 +124,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { group_token_in: grouped_swap.token_in.clone(), group_token_out: grouped_swap.token_out.clone(), transfer_type: transfer, + historical_trade: self.historical_trade, }; let mut grouped_protocol_data: Vec> = vec![]; @@ -171,6 +177,8 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { /// * `sequential_swap_validator`: SequentialSwapValidator, responsible for checking validity of /// sequential swap solutions /// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers +/// * `historical_trade`: Whether the swap is to be done in the current block or in an historical +/// one. This is relevant for checking token approvals in some protocols (like Balancer v2). #[derive(Clone)] pub struct SequentialSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, @@ -180,6 +188,7 @@ pub struct SequentialSwapStrategyEncoder { wrapped_address: Bytes, sequential_swap_validator: SequentialSwapValidator, transfer_optimization: TransferOptimization, + historical_trade: bool, } impl SequentialSwapStrategyEncoder { @@ -188,6 +197,7 @@ impl SequentialSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, user_transfer_type: UserTransferType, router_address: Bytes, + historical_trade: bool, ) -> Result { let function_signature = if user_transfer_type == UserTransferType::TransferFromPermit2 { "sequentialSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)" @@ -210,6 +220,7 @@ impl SequentialSwapStrategyEncoder { user_transfer_type, router_address, ), + historical_trade, }) } @@ -279,6 +290,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { group_token_in: grouped_swap.token_in.clone(), group_token_out: grouped_swap.token_out.clone(), transfer_type: transfer, + historical_trade: self.historical_trade, }; let mut grouped_protocol_data: Vec> = vec![]; @@ -336,6 +348,8 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { /// solutions /// * `router_address`: Address of the router to be used to execute swaps /// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers +/// * `historical_trade`: Whether the swap is to be done in the current block or in an historical +/// one. This is relevant for checking token approvals in some protocols (like Balancer v2). #[derive(Clone)] pub struct SplitSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, @@ -345,6 +359,7 @@ pub struct SplitSwapStrategyEncoder { split_swap_validator: SplitSwapValidator, router_address: Bytes, transfer_optimization: TransferOptimization, + historical_trade: bool, } impl SplitSwapStrategyEncoder { @@ -353,6 +368,7 @@ impl SplitSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, user_transfer_type: UserTransferType, router_address: Bytes, + historical_trade: bool, ) -> Result { let function_signature = if user_transfer_type == UserTransferType::TransferFromPermit2 { "splitSwapPermit2(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)" @@ -374,6 +390,7 @@ impl SplitSwapStrategyEncoder { user_transfer_type, router_address, ), + historical_trade, }) } @@ -479,6 +496,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { group_token_in: grouped_swap.token_in.clone(), group_token_out: grouped_swap.token_out.clone(), transfer_type: transfer, + historical_trade: self.historical_trade, }; let mut grouped_protocol_data: Vec> = vec![]; @@ -591,6 +609,7 @@ mod tests { swap_encoder_registry, UserTransferType::TransferFromPermit2, router_address(), + false, ) .unwrap(); let solution = Solution { @@ -651,6 +670,7 @@ mod tests { swap_encoder_registry, UserTransferType::None, router_address(), + false, ) .unwrap(); let solution = Solution { @@ -732,6 +752,7 @@ mod tests { swap_encoder_registry, UserTransferType::TransferFrom, router_address(), + false, ) .unwrap(); let solution = Solution { @@ -867,6 +888,7 @@ mod tests { swap_encoder_registry, UserTransferType::TransferFromPermit2, Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + false, ) .unwrap(); @@ -1015,6 +1037,7 @@ mod tests { swap_encoder_registry, UserTransferType::TransferFrom, Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + false, ) .unwrap(); diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 134a5d6..eb8375b 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -280,19 +280,20 @@ impl SwapEncoder for BalancerV2SwapEncoder { ) -> Result, EncodingError> { let token_approvals_manager = ProtocolApprovalsManager::new()?; let token = bytes_to_address(&swap.token_in)?; - let approval_needed: bool; + let mut approval_needed: bool = true; if let Some(router_address) = &encoding_context.router_address { - let tycho_router_address = bytes_to_address(router_address)?; - approval_needed = token_approvals_manager.approval_needed( - token, - tycho_router_address, - Address::from_str(&self.vault_address) - .map_err(|_| EncodingError::FatalError("Invalid vault address".to_string()))?, - )?; - } else { - approval_needed = true; - } + if !encoding_context.historical_trade { + let tycho_router_address = bytes_to_address(router_address)?; + approval_needed = token_approvals_manager.approval_needed( + token, + tycho_router_address, + Address::from_str(&self.vault_address).map_err(|_| { + EncodingError::FatalError("Invalid vault address".to_string()) + })?, + )?; + } + }; let component_id = AlloyBytes::from_str(&swap.component.id) .map_err(|_| EncodingError::FatalError("Invalid component ID".to_string()))?; @@ -1026,6 +1027,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = UniswapV2SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -1081,6 +1083,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = UniswapV3SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -1138,6 +1141,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::None, + historical_trade: true, }; let encoder = BalancerV2SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -1207,6 +1211,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = UniswapV4SwapEncoder::new( String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"), @@ -1275,6 +1280,7 @@ mod tests { // Token out is the same as the group token out group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = UniswapV4SwapEncoder::new( @@ -1318,6 +1324,7 @@ mod tests { group_token_in: usde_address.clone(), group_token_out: wbtc_address.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; // Setup - First sequence: USDE -> USDT @@ -1448,6 +1455,7 @@ mod tests { exact_out: false, router_address: Some(Bytes::default()), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = EkuboSwapEncoder::new(String::default(), Chain::Ethereum, None).unwrap(); @@ -1490,6 +1498,7 @@ mod tests { exact_out: false, router_address: Some(Bytes::default()), transfer_type: TransferType::Transfer, + historical_trade: false, }; let first_swap = SwapBuilder::new( @@ -1687,6 +1696,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::None, + historical_trade: false, }; let encoder = CurveSwapEncoder::new( String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"), @@ -1753,6 +1763,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::None, + historical_trade: false, }; let encoder = CurveSwapEncoder::new( String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"), @@ -1820,6 +1831,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::None, + historical_trade: false, }; let encoder = CurveSwapEncoder::new( String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"), @@ -1888,6 +1900,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = BalancerV3SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -1940,6 +1953,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = MaverickV2SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -2033,6 +2047,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = BebopSwapEncoder::new( @@ -2107,6 +2122,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = HashflowSwapEncoder::new( @@ -2200,6 +2216,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), transfer_type: TransferType::Transfer, + historical_trade: false, }; let encoder = HashflowSwapEncoder::new( diff --git a/src/encoding/evm/tycho_encoders.rs b/src/encoding/evm/tycho_encoders.rs index 2710d71..79632db 100644 --- a/src/encoding/evm/tycho_encoders.rs +++ b/src/encoding/evm/tycho_encoders.rs @@ -54,6 +54,7 @@ impl TychoRouterEncoder { router_address: Bytes, user_transfer_type: UserTransferType, signer: Option, + historical_trade: bool, ) -> Result { let permit2 = if user_transfer_type == UserTransferType::TransferFromPermit2 { Some(Permit2::new()?) @@ -66,18 +67,21 @@ impl TychoRouterEncoder { swap_encoder_registry.clone(), user_transfer_type.clone(), router_address.clone(), + historical_trade.clone(), )?, sequential_swap_strategy: SequentialSwapStrategyEncoder::new( chain, swap_encoder_registry.clone(), user_transfer_type.clone(), router_address.clone(), + historical_trade.clone(), )?, split_swap_strategy: SplitSwapStrategyEncoder::new( chain, swap_encoder_registry, user_transfer_type.clone(), router_address.clone(), + historical_trade, )?, router_address, permit2, @@ -331,6 +335,7 @@ impl TychoExecutorEncoder { group_token_in: grouped_swap.token_in.clone(), group_token_out: grouped_swap.token_out.clone(), transfer_type: transfer, + historical_trade: false, }; let mut grouped_protocol_data: Vec> = vec![]; let mut initial_protocol_data: Vec = vec![]; @@ -498,6 +503,7 @@ mod tests { router_address(), user_transfer_type, None, + false, ) .unwrap() } diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 48b224c..b7886ba 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -273,6 +273,8 @@ impl PartialEq for PermitDetails { /// * `group_token_in`: Token to be used as the input for the group swap. /// * `group_token_out`: Token to be used as the output for the group swap. /// * `transfer`: Type of transfer to be performed. See `TransferType` for more details. +/// * `historical_trade`: Whether the swap is to be done in the current block or in an historical +/// one. This is relevant for checking token approvals in some protocols (like Balancer v2). #[derive(Clone, Debug)] pub struct EncodingContext { pub receiver: Bytes, @@ -281,6 +283,7 @@ pub struct EncodingContext { pub group_token_in: Bytes, pub group_token_out: Bytes, pub transfer_type: TransferType, + pub historical_trade: bool, } /// Represents the type of transfer to be performed into the pool.