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