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