tycho_execution/encoding/evm/strategy_encoder/
strategy_encoders.rs

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