tycho_execution/encoding/evm/strategy_encoder/
strategy_encoders.rs

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