tycho_execution/encoding/evm/strategy_encoder/
strategy_encoders.rs

1use std::{collections::HashSet, str::FromStr};
2
3use alloy::primitives::{aliases::U24, U8};
4use tycho_common::{models::Chain, Bytes};
5
6use crate::encoding::{
7    errors::EncodingError,
8    evm::{
9        group_swaps::group_swaps,
10        strategy_encoder::{
11            strategy_validators::{SequentialSwapValidator, SplitSwapValidator, SwapValidator},
12            transfer_optimizations::TransferOptimization,
13        },
14        swap_encoder::swap_encoder_registry::SwapEncoderRegistry,
15        utils::{get_token_position, percentage_to_uint24, ple_encode},
16    },
17    models::{EncodedSolution, EncodingContext, NativeAction, Solution, UserTransferType},
18    strategy_encoder::StrategyEncoder,
19    swap_encoder::SwapEncoder,
20};
21
22/// Represents the encoder for a swap strategy which supports single swaps.
23///
24/// # Fields
25/// * `swap_encoder_registry`: SwapEncoderRegistry, containing all possible swap encoders
26/// * `function_signature`: String, the signature for the swap function in the router contract
27/// * `router_address`: Address of the router to be used to execute swaps
28/// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers
29/// * `historical_trade`: Whether the swap is to be done in the current block or in an historical
30///   one. This is relevant for checking token approvals in some protocols (like Balancer v2).
31#[derive(Clone)]
32pub struct SingleSwapStrategyEncoder {
33    swap_encoder_registry: SwapEncoderRegistry,
34    function_signature: String,
35    router_address: Bytes,
36    transfer_optimization: TransferOptimization,
37    historical_trade: bool,
38}
39
40impl SingleSwapStrategyEncoder {
41    pub fn new(
42        chain: Chain,
43        swap_encoder_registry: SwapEncoderRegistry,
44        user_transfer_type: UserTransferType,
45        router_address: Bytes,
46        historical_trade: bool,
47    ) -> Result<Self, EncodingError> {
48        let function_signature = if user_transfer_type == UserTransferType::TransferFromPermit2 {
49            "singleSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)"
50        } else {
51            "singleSwap(uint256,address,address,uint256,bool,bool,address,bool,bytes)"
52        }.to_string();
53
54        Ok(Self {
55            function_signature,
56            swap_encoder_registry,
57            router_address: router_address.clone(),
58            transfer_optimization: TransferOptimization::new(
59                chain.native_token().address,
60                chain.wrapped_native_token().address,
61                user_transfer_type,
62                router_address,
63            ),
64            historical_trade,
65        })
66    }
67
68    /// Encodes information necessary for performing a single hop against a given executor for
69    /// a protocol.
70    fn encode_swap_header(&self, executor_address: Bytes, protocol_data: Vec<u8>) -> Vec<u8> {
71        let mut encoded = Vec::new();
72        encoded.extend(executor_address.to_vec());
73        encoded.extend(protocol_data);
74        encoded
75    }
76}
77
78impl StrategyEncoder for SingleSwapStrategyEncoder {
79    fn encode_strategy(&self, solution: &Solution) -> Result<EncodedSolution, EncodingError> {
80        let grouped_swaps = group_swaps(&solution.swaps);
81        let number_of_groups = grouped_swaps.len();
82        if number_of_groups != 1 {
83            return Err(EncodingError::InvalidInput(format!(
84                "Single strategy only supports exactly one swap for non-groupable protocols. Found {number_of_groups}",
85            )))
86        }
87
88        let grouped_swap = grouped_swaps
89            .first()
90            .ok_or_else(|| EncodingError::FatalError("Swap grouping failed".to_string()))?;
91
92        if grouped_swap.split != 0f64 {
93            return Err(EncodingError::InvalidInput(
94                "Splits not supported for single swaps.".to_string(),
95            ))
96        }
97
98        let (mut unwrap, mut wrap) = (false, false);
99        if let Some(action) = &solution.native_action {
100            match *action {
101                NativeAction::Wrap => wrap = true,
102                NativeAction::Unwrap => unwrap = true,
103            }
104        }
105        let protocol = &grouped_swap.protocol_system;
106        let swap_encoder = self
107            .get_swap_encoder(protocol)
108            .ok_or_else(|| {
109                EncodingError::InvalidInput(format!(
110                    "Swap encoder not found for protocol: {protocol}"
111                ))
112            })?;
113
114        let swap_receiver =
115            if !unwrap { solution.receiver.clone() } else { self.router_address.clone() };
116
117        let transfer = self
118            .transfer_optimization
119            .get_transfers(grouped_swap, &solution.given_token, wrap, false);
120        let encoding_context = EncodingContext {
121            receiver: swap_receiver,
122            exact_out: solution.exact_out,
123            router_address: Some(self.router_address.clone()),
124            group_token_in: grouped_swap.token_in.clone(),
125            group_token_out: grouped_swap.token_out.clone(),
126            transfer_type: transfer,
127            historical_trade: self.historical_trade,
128        };
129
130        let mut grouped_protocol_data: Vec<Vec<u8>> = vec![];
131        let mut initial_protocol_data: Vec<u8> = vec![];
132        for swap in grouped_swap.swaps.iter() {
133            let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
134            if encoding_context.group_token_in == swap.token_in {
135                initial_protocol_data = protocol_data;
136            } else {
137                grouped_protocol_data.push(protocol_data);
138            }
139        }
140
141        if !grouped_protocol_data.is_empty() {
142            initial_protocol_data.extend(ple_encode(grouped_protocol_data));
143        }
144
145        let swap_data = self.encode_swap_header(
146            Bytes::from_str(swap_encoder.executor_address())
147                .map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?,
148            initial_protocol_data,
149        );
150        Ok(EncodedSolution {
151            function_signature: self.function_signature.clone(),
152            interacting_with: self.router_address.clone(),
153            swaps: swap_data,
154            permit: None,
155            n_tokens: 0,
156        })
157    }
158
159    fn get_swap_encoder(&self, protocol_system: &str) -> Option<&Box<dyn SwapEncoder>> {
160        self.swap_encoder_registry
161            .get_encoder(protocol_system)
162    }
163
164    fn clone_box(&self) -> Box<dyn StrategyEncoder> {
165        Box::new(self.clone())
166    }
167}
168
169/// Represents the encoder for a swap strategy which supports sequential swaps.
170///
171/// # Fields
172/// * `swap_encoder_registry`: SwapEncoderRegistry, containing all possible swap encoders
173/// * `function_signature`: String, the signature for the swap function in the router contract
174/// * `native_address`: Address of the chain's native token
175/// * `wrapped_address`: Address of the chain's wrapped token
176/// * `router_address`: Address of the router to be used to execute swaps
177/// * `sequential_swap_validator`: SequentialSwapValidator, responsible for checking validity of
178///   sequential swap solutions
179/// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers
180/// * `historical_trade`: Whether the swap is to be done in the current block or in an historical
181///   one. This is relevant for checking token approvals in some protocols (like Balancer v2).
182#[derive(Clone)]
183pub struct SequentialSwapStrategyEncoder {
184    swap_encoder_registry: SwapEncoderRegistry,
185    function_signature: String,
186    router_address: Bytes,
187    native_address: Bytes,
188    wrapped_address: Bytes,
189    sequential_swap_validator: SequentialSwapValidator,
190    transfer_optimization: TransferOptimization,
191    historical_trade: bool,
192}
193
194impl SequentialSwapStrategyEncoder {
195    pub fn new(
196        chain: Chain,
197        swap_encoder_registry: SwapEncoderRegistry,
198        user_transfer_type: UserTransferType,
199        router_address: Bytes,
200        historical_trade: bool,
201    ) -> Result<Self, EncodingError> {
202        let function_signature = if user_transfer_type == UserTransferType::TransferFromPermit2 {
203            "sequentialSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)"
204        } else {
205            "sequentialSwap(uint256,address,address,uint256,bool,bool,address,bool,bytes)"
206
207        }.to_string();
208        let native_token_address = chain.native_token().address;
209        let wrapped_token_address = chain.wrapped_native_token().address;
210        Ok(Self {
211            function_signature,
212            swap_encoder_registry,
213            router_address: router_address.clone(),
214            native_address: native_token_address.clone(),
215            wrapped_address: wrapped_token_address.clone(),
216            sequential_swap_validator: SequentialSwapValidator,
217            transfer_optimization: TransferOptimization::new(
218                native_token_address,
219                wrapped_token_address,
220                user_transfer_type,
221                router_address,
222            ),
223            historical_trade,
224        })
225    }
226
227    /// Encodes information necessary for performing a single hop against a given executor for
228    /// a protocol.
229    fn encode_swap_header(&self, executor_address: Bytes, protocol_data: Vec<u8>) -> Vec<u8> {
230        let mut encoded = Vec::new();
231        encoded.extend(executor_address.to_vec());
232        encoded.extend(protocol_data);
233        encoded
234    }
235}
236
237impl StrategyEncoder for SequentialSwapStrategyEncoder {
238    fn encode_strategy(&self, solution: &Solution) -> Result<EncodedSolution, EncodingError> {
239        self.sequential_swap_validator
240            .validate_swap_path(
241                &solution.swaps,
242                &solution.given_token,
243                &solution.checked_token,
244                &solution.native_action,
245                &self.native_address,
246                &self.wrapped_address,
247            )?;
248
249        let grouped_swaps = group_swaps(&solution.swaps);
250
251        let (mut wrap, mut unwrap) = (false, false);
252        if let Some(action) = &solution.native_action {
253            match *action {
254                NativeAction::Wrap => wrap = true,
255                NativeAction::Unwrap => unwrap = true,
256            }
257        }
258
259        let mut swaps = vec![];
260        let mut next_in_between_swap_optimization_allowed = true;
261        for (i, grouped_swap) in grouped_swaps.iter().enumerate() {
262            let protocol = &grouped_swap.protocol_system;
263            let swap_encoder = self
264                .get_swap_encoder(protocol)
265                .ok_or_else(|| {
266                    EncodingError::InvalidInput(format!(
267                        "Swap encoder not found for protocol: {protocol}",
268                    ))
269                })?;
270
271            let in_between_swap_optimization_allowed = next_in_between_swap_optimization_allowed;
272            let next_swap = grouped_swaps.get(i + 1);
273            let (swap_receiver, next_swap_optimization) = self
274                .transfer_optimization
275                .get_receiver(&solution.receiver, next_swap, unwrap)?;
276            next_in_between_swap_optimization_allowed = next_swap_optimization;
277
278            let transfer = self
279                .transfer_optimization
280                .get_transfers(
281                    grouped_swap,
282                    &solution.given_token,
283                    wrap,
284                    in_between_swap_optimization_allowed,
285                );
286            let encoding_context = EncodingContext {
287                receiver: swap_receiver,
288                exact_out: solution.exact_out,
289                router_address: Some(self.router_address.clone()),
290                group_token_in: grouped_swap.token_in.clone(),
291                group_token_out: grouped_swap.token_out.clone(),
292                transfer_type: transfer,
293                historical_trade: self.historical_trade,
294            };
295
296            let mut grouped_protocol_data: Vec<Vec<u8>> = vec![];
297            let mut initial_protocol_data: Vec<u8> = vec![];
298            for swap in grouped_swap.swaps.iter() {
299                let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
300                if encoding_context.group_token_in == swap.token_in {
301                    initial_protocol_data = protocol_data;
302                } else {
303                    grouped_protocol_data.push(protocol_data);
304                }
305            }
306
307            if !grouped_protocol_data.is_empty() {
308                initial_protocol_data.extend(ple_encode(grouped_protocol_data));
309            }
310
311            let swap_data = self.encode_swap_header(
312                Bytes::from_str(swap_encoder.executor_address()).map_err(|_| {
313                    EncodingError::FatalError("Invalid executor address".to_string())
314                })?,
315                initial_protocol_data,
316            );
317            swaps.push(swap_data);
318        }
319
320        let encoded_swaps = ple_encode(swaps);
321        Ok(EncodedSolution {
322            interacting_with: self.router_address.clone(),
323            function_signature: self.function_signature.clone(),
324            swaps: encoded_swaps,
325            permit: None,
326            n_tokens: 0,
327        })
328    }
329
330    fn get_swap_encoder(&self, protocol_system: &str) -> Option<&Box<dyn SwapEncoder>> {
331        self.swap_encoder_registry
332            .get_encoder(protocol_system)
333    }
334
335    fn clone_box(&self) -> Box<dyn StrategyEncoder> {
336        Box::new(self.clone())
337    }
338}
339
340/// Represents the encoder for a swap strategy which supports split swaps.
341///
342/// # Fields
343/// * `swap_encoder_registry`: SwapEncoderRegistry, containing all possible swap encoders
344/// * `function_signature`: String, the signature for the swap function in the router contract
345/// * `native_address`: Address of the chain's native token
346/// * `wrapped_address`: Address of the chain's wrapped token
347/// * `split_swap_validator`: SplitSwapValidator, responsible for checking validity of split swap
348///   solutions
349/// * `router_address`: Address of the router to be used to execute swaps
350/// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers
351/// * `historical_trade`: Whether the swap is to be done in the current block or in an historical
352///   one. This is relevant for checking token approvals in some protocols (like Balancer v2).
353#[derive(Clone)]
354pub struct SplitSwapStrategyEncoder {
355    swap_encoder_registry: SwapEncoderRegistry,
356    function_signature: String,
357    native_address: Bytes,
358    wrapped_address: Bytes,
359    split_swap_validator: SplitSwapValidator,
360    router_address: Bytes,
361    transfer_optimization: TransferOptimization,
362    historical_trade: bool,
363}
364
365impl SplitSwapStrategyEncoder {
366    pub fn new(
367        chain: Chain,
368        swap_encoder_registry: SwapEncoderRegistry,
369        user_transfer_type: UserTransferType,
370        router_address: Bytes,
371        historical_trade: bool,
372    ) -> Result<Self, EncodingError> {
373        let function_signature = if user_transfer_type == UserTransferType::TransferFromPermit2 {
374           "splitSwapPermit2(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)"
375        } else {
376                "splitSwap(uint256,address,address,uint256,bool,bool,uint256,address,bool,bytes)"
377        }.to_string();
378        let native_token_address = chain.native_token().address;
379        let wrapped_token_address = chain.wrapped_native_token().address;
380        Ok(Self {
381            function_signature,
382            swap_encoder_registry,
383            native_address: native_token_address.clone(),
384            wrapped_address: wrapped_token_address.clone(),
385            split_swap_validator: SplitSwapValidator,
386            router_address: router_address.clone(),
387            transfer_optimization: TransferOptimization::new(
388                native_token_address,
389                wrapped_token_address,
390                user_transfer_type,
391                router_address,
392            ),
393            historical_trade,
394        })
395    }
396
397    /// Encodes information necessary for performing a single hop against a given executor for
398    /// a protocol as part of a split swap solution.
399    fn encode_swap_header(
400        &self,
401        token_in: U8,
402        token_out: U8,
403        split: U24,
404        executor_address: Bytes,
405        protocol_data: Vec<u8>,
406    ) -> Vec<u8> {
407        let mut encoded = Vec::new();
408        encoded.push(token_in.to_be_bytes_vec()[0]);
409        encoded.push(token_out.to_be_bytes_vec()[0]);
410        encoded.extend_from_slice(&split.to_be_bytes_vec());
411        encoded.extend(executor_address.to_vec());
412        encoded.extend(protocol_data);
413        encoded
414    }
415}
416
417impl StrategyEncoder for SplitSwapStrategyEncoder {
418    fn encode_strategy(&self, solution: &Solution) -> Result<EncodedSolution, EncodingError> {
419        self.split_swap_validator
420            .validate_split_percentages(&solution.swaps)?;
421        self.split_swap_validator
422            .validate_swap_path(
423                &solution.swaps,
424                &solution.given_token,
425                &solution.checked_token,
426                &solution.native_action,
427                &self.native_address,
428                &self.wrapped_address,
429            )?;
430
431        // The tokens array is composed of the given token, the checked token and all the
432        // intermediary tokens in between. The contract expects the tokens to be in this order.
433        let solution_tokens: HashSet<&Bytes> = vec![&solution.given_token, &solution.checked_token]
434            .into_iter()
435            .collect();
436
437        let grouped_swaps = group_swaps(&solution.swaps);
438
439        let intermediary_tokens: HashSet<&Bytes> = grouped_swaps
440            .iter()
441            .flat_map(|grouped_swap| vec![&grouped_swap.token_in, &grouped_swap.token_out])
442            .collect();
443        let mut intermediary_tokens: Vec<&Bytes> = intermediary_tokens
444            .difference(&solution_tokens)
445            .cloned()
446            .collect();
447        // this is only to make the test deterministic (same index for the same token for different
448        // runs)
449        intermediary_tokens.sort();
450
451        let (mut unwrap, mut wrap) = (false, false);
452        if let Some(action) = &solution.native_action {
453            match *action {
454                NativeAction::Wrap => wrap = true,
455                NativeAction::Unwrap => unwrap = true,
456            }
457        }
458
459        let mut tokens = Vec::with_capacity(2 + intermediary_tokens.len());
460        if wrap {
461            tokens.push(&self.wrapped_address);
462        } else {
463            tokens.push(&solution.given_token);
464        }
465        tokens.extend(intermediary_tokens);
466
467        if unwrap {
468            tokens.push(&self.wrapped_address);
469        } else {
470            tokens.push(&solution.checked_token);
471        }
472
473        let mut swaps = vec![];
474        for grouped_swap in grouped_swaps.iter() {
475            let protocol = &grouped_swap.protocol_system;
476            let swap_encoder = self
477                .get_swap_encoder(protocol)
478                .ok_or_else(|| {
479                    EncodingError::InvalidInput(format!(
480                        "Swap encoder not found for protocol: {protocol}",
481                    ))
482                })?;
483
484            let swap_receiver = if !unwrap && grouped_swap.token_out == solution.checked_token {
485                solution.receiver.clone()
486            } else {
487                self.router_address.clone()
488            };
489            let transfer = self
490                .transfer_optimization
491                .get_transfers(grouped_swap, &solution.given_token, wrap, false);
492            let encoding_context = EncodingContext {
493                receiver: swap_receiver,
494                exact_out: solution.exact_out,
495                router_address: Some(self.router_address.clone()),
496                group_token_in: grouped_swap.token_in.clone(),
497                group_token_out: grouped_swap.token_out.clone(),
498                transfer_type: transfer,
499                historical_trade: self.historical_trade,
500            };
501
502            let mut grouped_protocol_data: Vec<Vec<u8>> = vec![];
503            let mut initial_protocol_data: Vec<u8> = vec![];
504            for swap in grouped_swap.swaps.iter() {
505                let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
506                if encoding_context.group_token_in == swap.token_in {
507                    initial_protocol_data = protocol_data;
508                } else {
509                    grouped_protocol_data.push(protocol_data);
510                }
511            }
512
513            if !grouped_protocol_data.is_empty() {
514                initial_protocol_data.extend(ple_encode(grouped_protocol_data));
515            }
516
517            let swap_data = self.encode_swap_header(
518                get_token_position(&tokens, &grouped_swap.token_in)?,
519                get_token_position(&tokens, &grouped_swap.token_out)?,
520                percentage_to_uint24(grouped_swap.split),
521                Bytes::from_str(swap_encoder.executor_address()).map_err(|_| {
522                    EncodingError::FatalError("Invalid executor address".to_string())
523                })?,
524                initial_protocol_data,
525            );
526            swaps.push(swap_data);
527        }
528
529        let encoded_swaps = ple_encode(swaps);
530        let tokens_len = if solution.given_token == solution.checked_token {
531            tokens.len() - 1
532        } else {
533            tokens.len()
534        };
535        Ok(EncodedSolution {
536            interacting_with: self.router_address.clone(),
537            function_signature: self.function_signature.clone(),
538            swaps: encoded_swaps,
539            permit: None,
540            n_tokens: tokens_len,
541        })
542    }
543
544    fn get_swap_encoder(&self, protocol_system: &str) -> Option<&Box<dyn SwapEncoder>> {
545        self.swap_encoder_registry
546            .get_encoder(protocol_system)
547    }
548
549    fn clone_box(&self) -> Box<dyn StrategyEncoder> {
550        Box::new(self.clone())
551    }
552}
553
554#[cfg(test)]
555mod tests {
556    use std::{collections::HashMap, fs, str::FromStr};
557
558    use alloy::{hex::encode, primitives::hex};
559    use num_bigint::{BigInt, BigUint};
560    use tycho_common::{
561        models::{protocol::ProtocolComponent, Chain},
562        Bytes,
563    };
564
565    use super::*;
566
567    fn eth_chain() -> Chain {
568        Chain::Ethereum
569    }
570
571    fn weth() -> Bytes {
572        Bytes::from(hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").to_vec())
573    }
574
575    fn get_swap_encoder_registry() -> SwapEncoderRegistry {
576        let executors_addresses =
577            fs::read_to_string("config/test_executor_addresses.json").unwrap();
578        let eth_chain = eth_chain();
579        SwapEncoderRegistry::new(Some(executors_addresses), eth_chain).unwrap()
580    }
581
582    fn router_address() -> Bytes {
583        Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap()
584    }
585
586    mod single {
587        use super::*;
588        use crate::encoding::models::SwapBuilder;
589        #[test]
590        fn test_single_swap_strategy_encoder() {
591            // Performs a single swap from WETH to DAI on a USV2 pool, with no grouping
592            // optimizations.
593            let checked_amount = BigUint::from_str("2018817438608734439720").unwrap();
594            let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
595            let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
596
597            let swap = SwapBuilder::new(
598                ProtocolComponent {
599                    id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
600                    protocol_system: "uniswap_v2".to_string(),
601                    ..Default::default()
602                },
603                weth.clone(),
604                dai.clone(),
605            )
606            .build();
607            let swap_encoder_registry = get_swap_encoder_registry();
608            let encoder = SingleSwapStrategyEncoder::new(
609                eth_chain(),
610                swap_encoder_registry,
611                UserTransferType::TransferFromPermit2,
612                router_address(),
613                false,
614            )
615            .unwrap();
616            let solution = Solution {
617                exact_out: false,
618                given_token: weth,
619                given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
620                checked_token: dai,
621                checked_amount: checked_amount.clone(),
622                sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
623                receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
624                swaps: vec![swap],
625                ..Default::default()
626            };
627
628            let encoded_solution = encoder
629                .encode_strategy(&solution)
630                .unwrap();
631
632            let expected_swap = String::from(concat!(
633                // Swap data
634                "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
635                "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
636                "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id
637                "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
638                "00",                                       // zero2one
639                "00",                                       // transfer type TransferFrom
640            ));
641            let hex_calldata = encode(&encoded_solution.swaps);
642
643            assert_eq!(hex_calldata, expected_swap);
644            assert_eq!(encoded_solution.function_signature, "singleSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string());
645            assert_eq!(encoded_solution.interacting_with, router_address());
646        }
647
648        #[test]
649        fn test_single_swap_strategy_encoder_no_transfer_in() {
650            // Performs a single swap from WETH to DAI on a USV2 pool assuming that the tokens are
651            // already in the router
652
653            let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
654            let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
655
656            let checked_amount = BigUint::from_str("1_640_000000000000000000").unwrap();
657
658            let swap = SwapBuilder::new(
659                ProtocolComponent {
660                    id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
661                    protocol_system: "uniswap_v2".to_string(),
662                    ..Default::default()
663                },
664                weth.clone(),
665                dai.clone(),
666            )
667            .build();
668            let swap_encoder_registry = get_swap_encoder_registry();
669            let encoder = SingleSwapStrategyEncoder::new(
670                eth_chain(),
671                swap_encoder_registry,
672                UserTransferType::None,
673                router_address(),
674                false,
675            )
676            .unwrap();
677            let solution = Solution {
678                exact_out: false,
679                given_token: weth,
680                given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
681                checked_token: dai,
682                checked_amount,
683                sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
684                receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
685                swaps: vec![swap],
686                ..Default::default()
687            };
688
689            let encoded_solution = encoder
690                .encode_strategy(&solution)
691                .unwrap();
692
693            let expected_input = [
694                // Swap data
695                "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
696                "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
697                "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id
698                "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
699                "00",                                       // zero2one
700                "01",                                       // transfer type Transfer
701            ]
702            .join("");
703
704            let hex_calldata = encode(&encoded_solution.swaps);
705
706            assert_eq!(hex_calldata, expected_input);
707            assert_eq!(
708                encoded_solution.function_signature,
709                "singleSwap(uint256,address,address,uint256,bool,bool,address,bool,bytes)"
710                    .to_string()
711            );
712            assert_eq!(encoded_solution.interacting_with, router_address());
713        }
714    }
715
716    mod sequential {
717        use super::*;
718        use crate::encoding::models::SwapBuilder;
719
720        #[test]
721        fn test_sequential_swap_strategy_encoder_no_permit2() {
722            // Performs a sequential swap from WETH to USDC though WBTC using USV2 pools
723            //
724            //   WETH ───(USV2)──> WBTC ───(USV2)──> USDC
725
726            let weth = weth();
727            let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
728            let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
729
730            let swap_weth_wbtc = SwapBuilder::new(
731                ProtocolComponent {
732                    id: "0xBb2b8038a1640196FbE3e38816F3e67Cba72D940".to_string(),
733                    protocol_system: "uniswap_v2".to_string(),
734                    ..Default::default()
735                },
736                weth.clone(),
737                wbtc.clone(),
738            )
739            .build();
740            let swap_wbtc_usdc = SwapBuilder::new(
741                ProtocolComponent {
742                    id: "0x004375Dff511095CC5A197A54140a24eFEF3A416".to_string(),
743                    protocol_system: "uniswap_v2".to_string(),
744                    ..Default::default()
745                },
746                wbtc.clone(),
747                usdc.clone(),
748            )
749            .build();
750            let swap_encoder_registry = get_swap_encoder_registry();
751            let encoder = SequentialSwapStrategyEncoder::new(
752                eth_chain(),
753                swap_encoder_registry,
754                UserTransferType::TransferFrom,
755                router_address(),
756                false,
757            )
758            .unwrap();
759            let solution = Solution {
760                exact_out: false,
761                given_token: weth,
762                given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
763                checked_token: usdc,
764                checked_amount: BigUint::from_str("26173932").unwrap(),
765                sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
766                receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
767                swaps: vec![swap_weth_wbtc, swap_wbtc_usdc],
768                ..Default::default()
769            };
770
771            let encoded_solution = encoder
772                .encode_strategy(&solution)
773                .unwrap();
774
775            let hex_calldata = encode(&encoded_solution.swaps);
776
777            let expected = String::from(concat!(
778                // swap 1
779                "0052",                                     // swap length
780                "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
781                "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
782                "bb2b8038a1640196fbe3e38816f3e67cba72d940", // component id
783                "004375dff511095cc5a197a54140a24efef3a416", // receiver (next pool)
784                "00",                                       // zero to one
785                "00",                                       // transfer type TransferFrom
786                // swap 2
787                "0052",                                     // swap length
788                "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
789                "2260fac5e5542a773aa44fbcfedf7c193bc2c599", // token in
790                "004375dff511095cc5a197a54140a24efef3a416", // component id
791                "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver (final user)
792                "01",                                       // zero to one
793                "02",                                       // transfer type None
794            ));
795
796            assert_eq!(hex_calldata, expected);
797            assert_eq!(
798                encoded_solution.function_signature,
799                "sequentialSwap(uint256,address,address,uint256,bool,bool,address,bool,bytes)"
800                    .to_string()
801            );
802            assert_eq!(encoded_solution.interacting_with, router_address());
803        }
804    }
805
806    mod split {
807        use super::*;
808        use crate::encoding::models::SwapBuilder;
809
810        #[test]
811        fn test_split_input_cyclic_swap() {
812            // This test has start and end tokens that are the same
813            // The flow is:
814            //            ┌─ (USV3, 60% split) ──> WETH ─┐
815            //            │                              │
816            // USDC ──────┤                              ├──(USV2)──> USDC
817            //            │                              │
818            //            └─ (USV3, 40% split) ──> WETH ─┘
819
820            let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
821            let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
822
823            // USDC -> WETH (Pool 1) - 60% of input
824            let swap_usdc_weth_pool1 = SwapBuilder::new(
825                ProtocolComponent {
826                    id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3
827                                                                                   * Pool 1 */
828                    protocol_system: "uniswap_v3".to_string(),
829                    static_attributes: {
830                        let mut attrs = HashMap::new();
831                        attrs.insert(
832                            "fee".to_string(),
833                            Bytes::from(BigInt::from(500).to_signed_bytes_be()),
834                        );
835                        attrs
836                    },
837                    ..Default::default()
838                },
839                usdc.clone(),
840                weth.clone(),
841            )
842            .split(0.6f64)
843            .build();
844
845            // USDC -> WETH (Pool 2) - 40% of input (remaining)
846            let swap_usdc_weth_pool2 = SwapBuilder::new(
847                ProtocolComponent {
848                    id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3
849                                                                                   * Pool 2 */
850                    protocol_system: "uniswap_v3".to_string(),
851                    static_attributes: {
852                        let mut attrs = HashMap::new();
853                        attrs.insert(
854                            "fee".to_string(),
855                            Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
856                        );
857                        attrs
858                    },
859                    ..Default::default()
860                },
861                usdc.clone(),
862                weth.clone(),
863            )
864            .build();
865
866            // WETH -> USDC (Pool 2)
867            let swap_weth_usdc_pool2 = SwapBuilder::new(
868                ProtocolComponent {
869                    id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(), /* USDC-WETH USV2
870                                                                                   * Pool 2 */
871                    protocol_system: "uniswap_v2".to_string(),
872                    static_attributes: {
873                        let mut attrs = HashMap::new();
874                        attrs.insert(
875                            "fee".to_string(),
876                            Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
877                        );
878                        attrs
879                    },
880                    ..Default::default()
881                },
882                weth.clone(),
883                usdc.clone(),
884            )
885            .build();
886            let swap_encoder_registry = get_swap_encoder_registry();
887            let encoder = SplitSwapStrategyEncoder::new(
888                eth_chain(),
889                swap_encoder_registry,
890                UserTransferType::TransferFromPermit2,
891                Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
892                false,
893            )
894            .unwrap();
895
896            let solution = Solution {
897                exact_out: false,
898                given_token: usdc.clone(),
899                given_amount: BigUint::from_str("100000000").unwrap(), // 100 USDC (6 decimals)
900                checked_token: usdc.clone(),
901                checked_amount: BigUint::from_str("99574171").unwrap(), /* Expected output
902                                                                         * from
903                                                                         * test */
904                sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
905                receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
906                swaps: vec![swap_usdc_weth_pool1, swap_usdc_weth_pool2, swap_weth_usdc_pool2],
907                ..Default::default()
908            };
909
910            let encoded_solution = encoder
911                .encode_strategy(&solution)
912                .unwrap();
913
914            let hex_calldata = hex::encode(&encoded_solution.swaps);
915
916            let expected_swaps = [
917                "006e",                                     // ple encoded swaps
918                "00",                                       // token in index
919                "01",                                       // token out index
920                "999999",                                   // split
921                "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address
922                "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in
923                "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token out
924                "0001f4",                                   // pool fee
925                "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver
926                "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id
927                "01",                                       // zero2one
928                "00",                                       // transfer type TransferFrom
929                "006e",                                     // ple encoded swaps
930                "00",                                       // token in index
931                "01",                                       // token out index
932                "000000",                                   // split
933                "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address
934                "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in
935                "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token out
936                "000bb8",                                   // pool fee
937                "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver
938                "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id
939                "01",                                       // zero2one
940                "00",                                       // transfer type TransferFrom
941                "0057",                                     // ple encoded swaps
942                "01",                                       // token in index
943                "00",                                       // token out index
944                "000000",                                   // split
945                "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address,
946                "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
947                "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id,
948                "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
949                "00",                                       // zero2one
950                "01",                                       // transfer type Transfer
951            ]
952            .join("");
953            assert_eq!(hex_calldata, expected_swaps);
954            assert_eq!(
955                encoded_solution.function_signature,
956                "splitSwapPermit2(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)"
957                    .to_string()
958            );
959            assert_eq!(encoded_solution.interacting_with, router_address());
960        }
961
962        #[test]
963        fn test_split_output_cyclic_swap() {
964            // This test has start and end tokens that are the same
965            // The flow is:
966            //                        ┌─── (USV3, 60% split) ───┐
967            //                        │                         │
968            // USDC ──(USV2) ── WETH──|                         ├─> USDC
969            //                        │                         │
970            //                        └─── (USV3, 40% split) ───┘
971
972            let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
973            let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
974
975            let swap_usdc_weth_v2 = SwapBuilder::new(
976                ProtocolComponent {
977                    id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(), // USDC-WETH USV2
978                    protocol_system: "uniswap_v2".to_string(),
979                    static_attributes: {
980                        let mut attrs = HashMap::new();
981                        attrs.insert(
982                            "fee".to_string(),
983                            Bytes::from(BigInt::from(500).to_signed_bytes_be()),
984                        );
985                        attrs
986                    },
987                    ..Default::default()
988                },
989                usdc.clone(),
990                weth.clone(),
991            )
992            .build();
993
994            let swap_weth_usdc_v3_pool1 = SwapBuilder::new(
995                ProtocolComponent {
996                    id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3
997                                                                                   * Pool 1 */
998                    protocol_system: "uniswap_v3".to_string(),
999                    static_attributes: {
1000                        let mut attrs = HashMap::new();
1001                        attrs.insert(
1002                            "fee".to_string(),
1003                            Bytes::from(BigInt::from(500).to_signed_bytes_be()),
1004                        );
1005                        attrs
1006                    },
1007                    ..Default::default()
1008                },
1009                weth.clone(),
1010                usdc.clone(),
1011            )
1012            .split(0.6f64)
1013            .build();
1014
1015            let swap_weth_usdc_v3_pool2 = SwapBuilder::new(
1016                ProtocolComponent {
1017                    id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3
1018                                                                                   * Pool 1 */
1019                    protocol_system: "uniswap_v3".to_string(),
1020                    static_attributes: {
1021                        let mut attrs = HashMap::new();
1022                        attrs.insert(
1023                            "fee".to_string(),
1024                            Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
1025                        );
1026                        attrs
1027                    },
1028                    ..Default::default()
1029                },
1030                weth.clone(),
1031                usdc.clone(),
1032            )
1033            .build();
1034
1035            let swap_encoder_registry = get_swap_encoder_registry();
1036            let encoder = SplitSwapStrategyEncoder::new(
1037                eth_chain(),
1038                swap_encoder_registry,
1039                UserTransferType::TransferFrom,
1040                Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
1041                false,
1042            )
1043            .unwrap();
1044
1045            let solution = Solution {
1046                exact_out: false,
1047                given_token: usdc.clone(),
1048                given_amount: BigUint::from_str("100000000").unwrap(), // 100 USDC (6 decimals)
1049                checked_token: usdc.clone(),
1050                checked_amount: BigUint::from_str("99025908").unwrap(), /* Expected output
1051                                                                         * from
1052                                                                         * test */
1053                sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
1054                receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
1055                swaps: vec![swap_usdc_weth_v2, swap_weth_usdc_v3_pool1, swap_weth_usdc_v3_pool2],
1056                ..Default::default()
1057            };
1058
1059            let encoded_solution = encoder
1060                .encode_strategy(&solution)
1061                .unwrap();
1062
1063            let hex_calldata = hex::encode(&encoded_solution.swaps);
1064
1065            let expected_swaps = [
1066                "0057",                                     // ple encoded swaps
1067                "00",                                       // token in index
1068                "01",                                       // token out index
1069                "000000",                                   // split
1070                "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
1071                "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in
1072                "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id
1073                "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver
1074                "01",                                       // zero2one
1075                "00",                                       // transfer type TransferFrom
1076                "006e",                                     // ple encoded swaps
1077                "01",                                       // token in index
1078                "00",                                       // token out index
1079                "999999",                                   // split
1080                "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address
1081                "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
1082                "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token out
1083                "0001f4",                                   // pool fee
1084                "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
1085                "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id
1086                "00",                                       // zero2one
1087                "01",                                       // transfer type Transfer
1088                "006e",                                     // ple encoded swaps
1089                "01",                                       // token in index
1090                "00",                                       // token out index
1091                "000000",                                   // split
1092                "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address
1093                "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
1094                "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token out
1095                "000bb8",                                   // pool fee
1096                "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
1097                "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id
1098                "00",                                       // zero2one
1099                "01",                                       // transfer type Transfer
1100            ]
1101            .join("");
1102
1103            assert_eq!(hex_calldata, expected_swaps);
1104            assert_eq!(
1105                encoded_solution.function_signature,
1106                "splitSwap(uint256,address,address,uint256,bool,bool,uint256,address,bool,bytes)"
1107                    .to_string()
1108            );
1109            assert_eq!(encoded_solution.interacting_with, router_address());
1110        }
1111    }
1112}