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