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::SwapBuilder;
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 = SwapBuilder::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            .build();
617            let swap_encoder_registry = get_swap_encoder_registry();
618            let encoder = SingleSwapStrategyEncoder::new(
619                eth_chain(),
620                swap_encoder_registry,
621                UserTransferType::TransferFromPermit2,
622                router_address(),
623                false,
624            )
625            .unwrap();
626            let solution = Solution {
627                exact_out: false,
628                given_token: weth,
629                given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
630                checked_token: dai,
631                checked_amount: checked_amount.clone(),
632                sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
633                receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
634                swaps: vec![swap],
635                ..Default::default()
636            };
637
638            let encoded_solution = encoder
639                .encode_strategy(&solution)
640                .unwrap();
641
642            let expected_swap = String::from(concat!(
643                // Swap data
644                "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
645                "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
646                "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id
647                "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
648                "00",                                       // zero2one
649                "00",                                       // transfer type TransferFrom
650            ));
651            let hex_calldata = encode(&encoded_solution.swaps);
652
653            assert_eq!(hex_calldata, expected_swap);
654            assert_eq!(encoded_solution.function_signature, "singleSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string());
655            assert_eq!(encoded_solution.interacting_with, router_address());
656        }
657
658        #[test]
659        fn test_single_swap_strategy_encoder_no_transfer_in() {
660            // Performs a single swap from WETH to DAI on a USV2 pool assuming that the tokens are
661            // already in the router
662
663            let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
664            let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
665
666            let checked_amount = BigUint::from_str("1_640_000000000000000000").unwrap();
667
668            let swap = SwapBuilder::new(
669                ProtocolComponent {
670                    id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
671                    protocol_system: "uniswap_v2".to_string(),
672                    ..Default::default()
673                },
674                weth.clone(),
675                dai.clone(),
676            )
677            .build();
678            let swap_encoder_registry = get_swap_encoder_registry();
679            let encoder = SingleSwapStrategyEncoder::new(
680                eth_chain(),
681                swap_encoder_registry,
682                UserTransferType::None,
683                router_address(),
684                false,
685            )
686            .unwrap();
687            let solution = Solution {
688                exact_out: false,
689                given_token: weth,
690                given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
691                checked_token: dai,
692                checked_amount,
693                sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
694                receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
695                swaps: vec![swap],
696                ..Default::default()
697            };
698
699            let encoded_solution = encoder
700                .encode_strategy(&solution)
701                .unwrap();
702
703            let expected_input = [
704                // Swap data
705                "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
706                "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
707                "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id
708                "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
709                "00",                                       // zero2one
710                "01",                                       // transfer type Transfer
711            ]
712            .join("");
713
714            let hex_calldata = encode(&encoded_solution.swaps);
715
716            assert_eq!(hex_calldata, expected_input);
717            assert_eq!(
718                encoded_solution.function_signature,
719                "singleSwap(uint256,address,address,uint256,bool,bool,address,bool,bytes)"
720                    .to_string()
721            );
722            assert_eq!(encoded_solution.interacting_with, router_address());
723        }
724    }
725
726    mod sequential {
727        use super::*;
728        use crate::encoding::models::SwapBuilder;
729
730        #[test]
731        fn test_sequential_swap_strategy_encoder_no_permit2() {
732            // Performs a sequential swap from WETH to USDC though WBTC using USV2 pools
733            //
734            //   WETH ───(USV2)──> WBTC ───(USV2)──> USDC
735
736            let weth = weth();
737            let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
738            let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
739
740            let swap_weth_wbtc = SwapBuilder::new(
741                ProtocolComponent {
742                    id: "0xBb2b8038a1640196FbE3e38816F3e67Cba72D940".to_string(),
743                    protocol_system: "uniswap_v2".to_string(),
744                    ..Default::default()
745                },
746                weth.clone(),
747                wbtc.clone(),
748            )
749            .build();
750            let swap_wbtc_usdc = SwapBuilder::new(
751                ProtocolComponent {
752                    id: "0x004375Dff511095CC5A197A54140a24eFEF3A416".to_string(),
753                    protocol_system: "uniswap_v2".to_string(),
754                    ..Default::default()
755                },
756                wbtc.clone(),
757                usdc.clone(),
758            )
759            .build();
760            let swap_encoder_registry = get_swap_encoder_registry();
761            let encoder = SequentialSwapStrategyEncoder::new(
762                eth_chain(),
763                swap_encoder_registry,
764                UserTransferType::TransferFrom,
765                router_address(),
766                false,
767            )
768            .unwrap();
769            let solution = Solution {
770                exact_out: false,
771                given_token: weth,
772                given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
773                checked_token: usdc,
774                checked_amount: BigUint::from_str("26173932").unwrap(),
775                sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
776                receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
777                swaps: vec![swap_weth_wbtc, swap_wbtc_usdc],
778                ..Default::default()
779            };
780
781            let encoded_solution = encoder
782                .encode_strategy(&solution)
783                .unwrap();
784
785            let hex_calldata = encode(&encoded_solution.swaps);
786
787            let expected = String::from(concat!(
788                // swap 1
789                "0052",                                     // swap length
790                "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
791                "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
792                "bb2b8038a1640196fbe3e38816f3e67cba72d940", // component id
793                "004375dff511095cc5a197a54140a24efef3a416", // receiver (next pool)
794                "00",                                       // zero to one
795                "00",                                       // transfer type TransferFrom
796                // swap 2
797                "0052",                                     // swap length
798                "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
799                "2260fac5e5542a773aa44fbcfedf7c193bc2c599", // token in
800                "004375dff511095cc5a197a54140a24efef3a416", // component id
801                "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver (final user)
802                "01",                                       // zero to one
803                "02",                                       // transfer type None
804            ));
805
806            assert_eq!(hex_calldata, expected);
807            assert_eq!(
808                encoded_solution.function_signature,
809                "sequentialSwap(uint256,address,address,uint256,bool,bool,address,bool,bytes)"
810                    .to_string()
811            );
812            assert_eq!(encoded_solution.interacting_with, router_address());
813        }
814    }
815
816    mod split {
817        use super::*;
818        use crate::encoding::models::SwapBuilder;
819
820        #[test]
821        fn test_split_input_cyclic_swap() {
822            // This test has start and end tokens that are the same
823            // The flow is:
824            //            ┌─ (USV3, 60% split) ──> WETH ─┐
825            //            │                              │
826            // USDC ──────┤                              ├──(USV2)──> USDC
827            //            │                              │
828            //            └─ (USV3, 40% split) ──> WETH ─┘
829
830            let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
831            let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
832
833            // USDC -> WETH (Pool 1) - 60% of input
834            let swap_usdc_weth_pool1 = SwapBuilder::new(
835                ProtocolComponent {
836                    id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3
837                                                                                   * Pool 1 */
838                    protocol_system: "uniswap_v3".to_string(),
839                    static_attributes: {
840                        let mut attrs = HashMap::new();
841                        attrs.insert(
842                            "fee".to_string(),
843                            Bytes::from(BigInt::from(500).to_signed_bytes_be()),
844                        );
845                        attrs
846                    },
847                    ..Default::default()
848                },
849                usdc.clone(),
850                weth.clone(),
851            )
852            .split(0.6f64)
853            .build();
854
855            // USDC -> WETH (Pool 2) - 40% of input (remaining)
856            let swap_usdc_weth_pool2 = SwapBuilder::new(
857                ProtocolComponent {
858                    id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3
859                                                                                   * Pool 2 */
860                    protocol_system: "uniswap_v3".to_string(),
861                    static_attributes: {
862                        let mut attrs = HashMap::new();
863                        attrs.insert(
864                            "fee".to_string(),
865                            Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
866                        );
867                        attrs
868                    },
869                    ..Default::default()
870                },
871                usdc.clone(),
872                weth.clone(),
873            )
874            .build();
875
876            // WETH -> USDC (Pool 2)
877            let swap_weth_usdc_pool2 = SwapBuilder::new(
878                ProtocolComponent {
879                    id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(), /* USDC-WETH USV2
880                                                                                   * Pool 2 */
881                    protocol_system: "uniswap_v2".to_string(),
882                    static_attributes: {
883                        let mut attrs = HashMap::new();
884                        attrs.insert(
885                            "fee".to_string(),
886                            Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
887                        );
888                        attrs
889                    },
890                    ..Default::default()
891                },
892                weth.clone(),
893                usdc.clone(),
894            )
895            .build();
896            let swap_encoder_registry = get_swap_encoder_registry();
897            let encoder = SplitSwapStrategyEncoder::new(
898                eth_chain(),
899                swap_encoder_registry,
900                UserTransferType::TransferFromPermit2,
901                Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
902                false,
903            )
904            .unwrap();
905
906            let solution = Solution {
907                exact_out: false,
908                given_token: usdc.clone(),
909                given_amount: BigUint::from_str("100000000").unwrap(), // 100 USDC (6 decimals)
910                checked_token: usdc.clone(),
911                checked_amount: BigUint::from_str("99574171").unwrap(), /* Expected output
912                                                                         * from
913                                                                         * test */
914                sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
915                receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
916                swaps: vec![swap_usdc_weth_pool1, swap_usdc_weth_pool2, swap_weth_usdc_pool2],
917                ..Default::default()
918            };
919
920            let encoded_solution = encoder
921                .encode_strategy(&solution)
922                .unwrap();
923
924            let hex_calldata = hex::encode(&encoded_solution.swaps);
925
926            let expected_swaps = [
927                "006e",                                     // ple encoded swaps
928                "00",                                       // token in index
929                "01",                                       // token out index
930                "999999",                                   // split
931                "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address
932                "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in
933                "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token out
934                "0001f4",                                   // pool fee
935                "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver
936                "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id
937                "01",                                       // zero2one
938                "00",                                       // transfer type TransferFrom
939                "006e",                                     // ple encoded swaps
940                "00",                                       // token in index
941                "01",                                       // token out index
942                "000000",                                   // split
943                "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address
944                "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in
945                "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token out
946                "000bb8",                                   // pool fee
947                "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver
948                "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id
949                "01",                                       // zero2one
950                "00",                                       // transfer type TransferFrom
951                "0057",                                     // ple encoded swaps
952                "01",                                       // token in index
953                "00",                                       // token out index
954                "000000",                                   // split
955                "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address,
956                "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
957                "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id,
958                "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
959                "00",                                       // zero2one
960                "01",                                       // transfer type Transfer
961            ]
962            .join("");
963            assert_eq!(hex_calldata, expected_swaps);
964            assert_eq!(
965                encoded_solution.function_signature,
966                "splitSwapPermit2(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)"
967                    .to_string()
968            );
969            assert_eq!(encoded_solution.interacting_with, router_address());
970        }
971
972        #[test]
973        fn test_split_output_cyclic_swap() {
974            // This test has start and end tokens that are the same
975            // The flow is:
976            //                        ┌─── (USV3, 60% split) ───┐
977            //                        │                         │
978            // USDC ──(USV2) ── WETH──|                         ├─> USDC
979            //                        │                         │
980            //                        └─── (USV3, 40% split) ───┘
981
982            let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
983            let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
984
985            let swap_usdc_weth_v2 = SwapBuilder::new(
986                ProtocolComponent {
987                    id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(), // USDC-WETH USV2
988                    protocol_system: "uniswap_v2".to_string(),
989                    static_attributes: {
990                        let mut attrs = HashMap::new();
991                        attrs.insert(
992                            "fee".to_string(),
993                            Bytes::from(BigInt::from(500).to_signed_bytes_be()),
994                        );
995                        attrs
996                    },
997                    ..Default::default()
998                },
999                usdc.clone(),
1000                weth.clone(),
1001            )
1002            .build();
1003
1004            let swap_weth_usdc_v3_pool1 = SwapBuilder::new(
1005                ProtocolComponent {
1006                    id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3
1007                                                                                   * Pool 1 */
1008                    protocol_system: "uniswap_v3".to_string(),
1009                    static_attributes: {
1010                        let mut attrs = HashMap::new();
1011                        attrs.insert(
1012                            "fee".to_string(),
1013                            Bytes::from(BigInt::from(500).to_signed_bytes_be()),
1014                        );
1015                        attrs
1016                    },
1017                    ..Default::default()
1018                },
1019                weth.clone(),
1020                usdc.clone(),
1021            )
1022            .split(0.6f64)
1023            .build();
1024
1025            let swap_weth_usdc_v3_pool2 = SwapBuilder::new(
1026                ProtocolComponent {
1027                    id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3
1028                                                                                   * Pool 1 */
1029                    protocol_system: "uniswap_v3".to_string(),
1030                    static_attributes: {
1031                        let mut attrs = HashMap::new();
1032                        attrs.insert(
1033                            "fee".to_string(),
1034                            Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
1035                        );
1036                        attrs
1037                    },
1038                    ..Default::default()
1039                },
1040                weth.clone(),
1041                usdc.clone(),
1042            )
1043            .build();
1044
1045            let swap_encoder_registry = get_swap_encoder_registry();
1046            let encoder = SplitSwapStrategyEncoder::new(
1047                eth_chain(),
1048                swap_encoder_registry,
1049                UserTransferType::TransferFrom,
1050                Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
1051                false,
1052            )
1053            .unwrap();
1054
1055            let solution = Solution {
1056                exact_out: false,
1057                given_token: usdc.clone(),
1058                given_amount: BigUint::from_str("100000000").unwrap(), // 100 USDC (6 decimals)
1059                checked_token: usdc.clone(),
1060                checked_amount: BigUint::from_str("99025908").unwrap(), /* Expected output
1061                                                                         * from
1062                                                                         * test */
1063                sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
1064                receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
1065                swaps: vec![swap_usdc_weth_v2, swap_weth_usdc_v3_pool1, swap_weth_usdc_v3_pool2],
1066                ..Default::default()
1067            };
1068
1069            let encoded_solution = encoder
1070                .encode_strategy(&solution)
1071                .unwrap();
1072
1073            let hex_calldata = hex::encode(&encoded_solution.swaps);
1074
1075            let expected_swaps = [
1076                "0057",                                     // ple encoded swaps
1077                "00",                                       // token in index
1078                "01",                                       // token out index
1079                "000000",                                   // split
1080                "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
1081                "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in
1082                "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id
1083                "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver
1084                "01",                                       // zero2one
1085                "00",                                       // transfer type TransferFrom
1086                "006e",                                     // ple encoded swaps
1087                "01",                                       // token in index
1088                "00",                                       // token out index
1089                "999999",                                   // split
1090                "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address
1091                "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
1092                "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token out
1093                "0001f4",                                   // pool fee
1094                "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
1095                "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id
1096                "00",                                       // zero2one
1097                "01",                                       // transfer type Transfer
1098                "006e",                                     // ple encoded swaps
1099                "01",                                       // token in index
1100                "00",                                       // token out index
1101                "000000",                                   // split
1102                "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address
1103                "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
1104                "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token out
1105                "000bb8",                                   // pool fee
1106                "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
1107                "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id
1108                "00",                                       // zero2one
1109                "01",                                       // transfer type Transfer
1110            ]
1111            .join("");
1112
1113            assert_eq!(hex_calldata, expected_swaps);
1114            assert_eq!(
1115                encoded_solution.function_signature,
1116                "splitSwap(uint256,address,address,uint256,bool,bool,uint256,address,bool,bytes)"
1117                    .to_string()
1118            );
1119            assert_eq!(encoded_solution.interacting_with, router_address());
1120        }
1121    }
1122}