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