1use std::collections::HashSet;
2
3use alloy::signers::local::PrivateKeySigner;
4use tycho_common::{models::Chain, Bytes};
5
6use crate::encoding::{
7 errors::EncodingError,
8 evm::{
9 approvals::permit2::Permit2,
10 constants::{FUNDS_IN_ROUTER_PROTOCOLS, GROUPABLE_PROTOCOLS},
11 encoding_utils::encode_tycho_router_call,
12 group_swaps::group_swaps,
13 strategy_encoder::strategy_encoders::{
14 SequentialSwapStrategyEncoder, SingleSwapStrategyEncoder, SplitSwapStrategyEncoder,
15 },
16 swap_encoder::swap_encoder_registry::SwapEncoderRegistry,
17 utils::ple_encode,
18 },
19 models::{
20 EncodedSolution, EncodingContext, NativeAction, Solution, Transaction, TransferType,
21 UserTransferType,
22 },
23 strategy_encoder::StrategyEncoder,
24 tycho_encoder::TychoEncoder,
25};
26
27#[derive(Clone)]
39pub struct TychoRouterEncoder {
40 chain: Chain,
41 single_swap_strategy: SingleSwapStrategyEncoder,
42 sequential_swap_strategy: SequentialSwapStrategyEncoder,
43 split_swap_strategy: SplitSwapStrategyEncoder,
44 router_address: Bytes,
45 user_transfer_type: UserTransferType,
46 permit2: Option<Permit2>,
47 signer: Option<PrivateKeySigner>,
48}
49
50impl TychoRouterEncoder {
51 pub fn new(
52 chain: Chain,
53 swap_encoder_registry: SwapEncoderRegistry,
54 router_address: Bytes,
55 user_transfer_type: UserTransferType,
56 signer: Option<PrivateKeySigner>,
57 historical_trade: bool,
58 ) -> Result<Self, EncodingError> {
59 let permit2 = if user_transfer_type == UserTransferType::TransferFromPermit2 {
60 Some(Permit2::new()?)
61 } else {
62 None
63 };
64 Ok(TychoRouterEncoder {
65 single_swap_strategy: SingleSwapStrategyEncoder::new(
66 chain,
67 swap_encoder_registry.clone(),
68 user_transfer_type.clone(),
69 router_address.clone(),
70 historical_trade,
71 )?,
72 sequential_swap_strategy: SequentialSwapStrategyEncoder::new(
73 chain,
74 swap_encoder_registry.clone(),
75 user_transfer_type.clone(),
76 router_address.clone(),
77 historical_trade,
78 )?,
79 split_swap_strategy: SplitSwapStrategyEncoder::new(
80 chain,
81 swap_encoder_registry,
82 user_transfer_type.clone(),
83 router_address.clone(),
84 historical_trade,
85 )?,
86 router_address,
87 permit2,
88 signer,
89 chain,
90 user_transfer_type,
91 })
92 }
93
94 fn encode_solution(&self, solution: &Solution) -> Result<EncodedSolution, EncodingError> {
95 self.validate_solution(solution)?;
96 let protocols: HashSet<String> = solution
97 .swaps
98 .iter()
99 .map(|swap| swap.component().protocol_system.clone())
100 .collect();
101
102 let mut encoded_solution = if (solution.swaps.len() == 1) ||
103 ((protocols.len() == 1 &&
104 protocols
105 .iter()
106 .any(|p| GROUPABLE_PROTOCOLS.contains(&p.as_str()))) &&
107 solution
108 .swaps
109 .iter()
110 .all(|swap| swap.get_split() == 0.0))
111 {
112 self.single_swap_strategy
113 .encode_strategy(solution)?
114 } else if solution
115 .swaps
116 .iter()
117 .all(|swap| swap.get_split() == 0.0)
118 {
119 self.sequential_swap_strategy
120 .encode_strategy(solution)?
121 } else {
122 self.split_swap_strategy
123 .encode_strategy(solution)?
124 };
125
126 if let Some(permit2) = &self.permit2 {
127 let permit = permit2.get_permit(
128 &self.router_address,
129 &solution.sender,
130 &solution.given_token,
131 &solution.given_amount,
132 )?;
133 encoded_solution.permit = Some(permit);
134 }
135 Ok(encoded_solution)
136 }
137}
138
139impl TychoEncoder for TychoRouterEncoder {
140 fn encode_solutions(
141 &self,
142 solutions: Vec<Solution>,
143 ) -> Result<Vec<EncodedSolution>, EncodingError> {
144 let mut result: Vec<EncodedSolution> = Vec::new();
145 for solution in solutions.iter() {
146 let encoded_solution = self.encode_solution(solution)?;
147 result.push(encoded_solution);
148 }
149 Ok(result)
150 }
151
152 fn encode_full_calldata(
153 &self,
154 solutions: Vec<Solution>,
155 ) -> Result<Vec<Transaction>, EncodingError> {
156 let mut transactions: Vec<Transaction> = Vec::new();
157 for solution in solutions.iter() {
158 let encoded_solution = self.encode_solution(solution)?;
159
160 let transaction = encode_tycho_router_call(
161 self.chain.id(),
162 encoded_solution,
163 solution,
164 &self.user_transfer_type,
165 &self.chain.native_token().address,
166 self.signer.clone(),
167 )?;
168
169 transactions.push(transaction);
170 }
171 Ok(transactions)
172 }
173
174 fn validate_solution(&self, solution: &Solution) -> Result<(), EncodingError> {
186 if solution.exact_out {
187 return Err(EncodingError::FatalError(
188 "Currently only exact input solutions are supported".to_string(),
189 ));
190 }
191 if solution.swaps.is_empty() {
192 return Err(EncodingError::FatalError("No swaps found in solution".to_string()));
193 }
194 let native_address = self.chain.native_token().address;
195 let wrapped_address = self
196 .chain
197 .wrapped_native_token()
198 .address;
199 if let Some(native_action) = &solution.native_action {
200 if native_action == &NativeAction::Wrap {
201 if solution.given_token != native_address {
202 return Err(EncodingError::FatalError(
203 "Native token must be the input token in order to wrap".to_string(),
204 ));
205 }
206 if let Some(first_swap) = solution.swaps.first() {
207 if *first_swap.token_in() != wrapped_address {
208 return Err(EncodingError::FatalError(
209 "Wrapped token must be the first swap's input in order to wrap"
210 .to_string(),
211 ));
212 }
213 }
214 } else if native_action == &NativeAction::Unwrap {
215 if solution.checked_token != native_address {
216 return Err(EncodingError::FatalError(
217 "Native token must be the output token in order to unwrap".to_string(),
218 ));
219 }
220 if let Some(last_swap) = solution.swaps.last() {
221 if *last_swap.token_out() != wrapped_address {
222 return Err(EncodingError::FatalError(
223 "Wrapped token must be the last swap's output in order to unwrap"
224 .to_string(),
225 ));
226 }
227 }
228 }
229 }
230
231 let mut solution_tokens = vec![];
232 let mut split_tokens_already_considered = HashSet::new();
233 for (i, swap) in solution.swaps.iter().enumerate() {
234 if swap.get_split() != 0.0 {
236 if !split_tokens_already_considered.contains(swap.token_in()) {
237 solution_tokens.push(swap.token_in());
238 split_tokens_already_considered.insert(swap.token_in());
239 }
240 } else {
241 if !split_tokens_already_considered.contains(swap.token_in()) {
243 solution_tokens.push(swap.token_in());
244 }
245 }
246 if i == solution.swaps.len() - 1 {
247 solution_tokens.push(swap.token_out());
248 }
249 }
250
251 if solution_tokens.len() !=
252 solution_tokens
253 .iter()
254 .cloned()
255 .collect::<HashSet<&Bytes>>()
256 .len()
257 {
258 if let Some(last_swap) = solution.swaps.last() {
259 if *solution.swaps[0].token_in() != *last_swap.token_out() {
260 return Err(EncodingError::FatalError(
261 "Cyclical swaps are only allowed if they are the first and last token of a solution".to_string(),
262 ));
263 } else {
264 if let Some(_native_action) = &solution.native_action {
267 return Err(EncodingError::FatalError(
268 "Wrapping/Unwrapping is not available in cyclical swaps".to_string(),
269 ));
270 }
271 }
272 }
273 }
274 Ok(())
275 }
276}
277
278#[derive(Clone)]
286pub struct TychoExecutorEncoder {
287 swap_encoder_registry: SwapEncoderRegistry,
288}
289
290impl TychoExecutorEncoder {
291 pub fn new(swap_encoder_registry: SwapEncoderRegistry) -> Result<Self, EncodingError> {
292 Ok(TychoExecutorEncoder { swap_encoder_registry })
293 }
294
295 fn encode_executor_calldata(
296 &self,
297 solution: &Solution,
298 ) -> Result<EncodedSolution, EncodingError> {
299 let grouped_swaps = group_swaps(&solution.swaps);
300 let number_of_groups = grouped_swaps.len();
301 if number_of_groups > 1 {
302 return Err(EncodingError::InvalidInput(format!(
303 "Tycho executor encoder only supports one swap. Found {number_of_groups}"
304 )))
305 }
306
307 let grouped_swap = grouped_swaps
308 .first()
309 .ok_or_else(|| EncodingError::FatalError("Swap grouping failed".to_string()))?;
310
311 let swap_encoder = self
312 .swap_encoder_registry
313 .get_encoder(&grouped_swap.protocol_system)
314 .ok_or_else(|| {
315 EncodingError::InvalidInput(format!(
316 "Swap encoder not found for protocol: {}",
317 grouped_swap.protocol_system
318 ))
319 })?;
320
321 let transfer = if !FUNDS_IN_ROUTER_PROTOCOLS.contains(
322 &grouped_swap.swaps[0]
323 .component()
324 .protocol_system
325 .as_str(),
326 ) {
327 TransferType::Transfer
328 } else {
329 TransferType::None
330 };
331 let encoding_context = EncodingContext {
332 receiver: solution.receiver.clone(),
333 exact_out: solution.exact_out,
334 router_address: None,
335 group_token_in: grouped_swap.token_in.clone(),
336 group_token_out: grouped_swap.token_out.clone(),
337 transfer_type: transfer,
338 historical_trade: false,
339 };
340 let mut grouped_protocol_data: Vec<Vec<u8>> = vec![];
341 let mut initial_protocol_data: Vec<u8> = vec![];
342 for swap in grouped_swap.swaps.iter() {
343 let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
344 if encoding_context.group_token_in == *swap.token_in() {
345 initial_protocol_data = protocol_data;
346 } else {
347 grouped_protocol_data.push(protocol_data);
348 }
349 }
350
351 if !grouped_protocol_data.is_empty() {
352 initial_protocol_data.extend(ple_encode(grouped_protocol_data));
353 }
354
355 Ok(EncodedSolution {
356 swaps: initial_protocol_data,
357 interacting_with: swap_encoder.executor_address().clone(),
358 permit: None,
359 function_signature: "".to_string(),
360 n_tokens: 0,
361 })
362 }
363}
364
365impl TychoEncoder for TychoExecutorEncoder {
366 fn encode_solutions(
367 &self,
368 solutions: Vec<Solution>,
369 ) -> Result<Vec<EncodedSolution>, EncodingError> {
370 let solution = solutions
371 .first()
372 .ok_or(EncodingError::FatalError("No solutions found".to_string()))?;
373 self.validate_solution(solution)?;
374
375 let encoded_solution = self.encode_executor_calldata(solution)?;
376
377 Ok(vec![encoded_solution])
378 }
379
380 fn encode_full_calldata(
381 &self,
382 _solutions: Vec<Solution>,
383 ) -> Result<Vec<Transaction>, EncodingError> {
384 Err(EncodingError::NotImplementedError(
385 "Full calldata encoding is not supported for TychoExecutorEncoder".to_string(),
386 ))
387 }
388
389 fn validate_solution(&self, solution: &Solution) -> Result<(), EncodingError> {
394 if solution.exact_out {
395 return Err(EncodingError::FatalError(
396 "Currently only exact input solutions are supported".to_string(),
397 ));
398 }
399 Ok(())
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use std::{collections::HashMap, fs, str::FromStr};
406
407 use num_bigint::{BigInt, BigUint};
408 use tycho_common::models::{protocol::ProtocolComponent, Chain};
409
410 use super::*;
411 use crate::encoding::models::Swap;
412
413 fn dai() -> Bytes {
414 Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap()
415 }
416
417 fn eth() -> Bytes {
418 Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap()
419 }
420
421 fn weth() -> Bytes {
422 Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap()
423 }
424
425 fn usdc() -> Bytes {
426 Bytes::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap()
427 }
428
429 fn wbtc() -> Bytes {
430 Bytes::from_str("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599").unwrap()
431 }
432
433 fn pepe() -> Bytes {
434 Bytes::from_str("0x6982508145454Ce325dDbE47a25d4ec3d2311933").unwrap()
435 }
436
437 fn swap_usdc_eth_univ4() -> Swap {
441 let pool_fee_usdc_eth = Bytes::from(BigInt::from(3000).to_signed_bytes_be());
442 let tick_spacing_usdc_eth = Bytes::from(BigInt::from(60).to_signed_bytes_be());
443 let mut static_attributes_usdc_eth: HashMap<String, Bytes> = HashMap::new();
444 static_attributes_usdc_eth.insert("key_lp_fee".into(), pool_fee_usdc_eth);
445 static_attributes_usdc_eth.insert("tick_spacing".into(), tick_spacing_usdc_eth);
446 Swap::new(
447 ProtocolComponent {
448 id: "0xdce6394339af00981949f5f3baf27e3610c76326a700af57e4b3e3ae4977f78d"
449 .to_string(),
450 protocol_system: "uniswap_v4".to_string(),
451 static_attributes: static_attributes_usdc_eth,
452 ..Default::default()
453 },
454 usdc().clone(),
455 eth().clone(),
456 )
457 }
458
459 fn swap_eth_pepe_univ4() -> Swap {
460 let pool_fee_eth_pepe = Bytes::from(BigInt::from(25000).to_signed_bytes_be());
461 let tick_spacing_eth_pepe = Bytes::from(BigInt::from(500).to_signed_bytes_be());
462 let mut static_attributes_eth_pepe: HashMap<String, Bytes> = HashMap::new();
463 static_attributes_eth_pepe.insert("key_lp_fee".into(), pool_fee_eth_pepe);
464 static_attributes_eth_pepe.insert("tick_spacing".into(), tick_spacing_eth_pepe);
465 Swap::new(
466 ProtocolComponent {
467 id: "0xecd73ecbf77219f21f129c8836d5d686bbc27d264742ddad620500e3e548e2c9"
468 .to_string(),
469 protocol_system: "uniswap_v4".to_string(),
470 static_attributes: static_attributes_eth_pepe,
471 ..Default::default()
472 },
473 eth().clone(),
474 pepe().clone(),
475 )
476 }
477
478 fn router_address() -> Bytes {
479 Bytes::from_str("0x6bc529DC7B81A031828dDCE2BC419d01FF268C66").unwrap()
480 }
481
482 fn eth_chain() -> Chain {
483 Chain::Ethereum
484 }
485
486 fn get_swap_encoder_registry() -> SwapEncoderRegistry {
487 let executors_addresses =
488 fs::read_to_string("config/test_executor_addresses.json").unwrap();
489 SwapEncoderRegistry::new(eth_chain())
490 .add_default_encoders(Some(executors_addresses))
491 .unwrap()
492 }
493
494 fn get_tycho_router_encoder(user_transfer_type: UserTransferType) -> TychoRouterEncoder {
495 TychoRouterEncoder::new(
496 eth_chain(),
497 get_swap_encoder_registry(),
498 router_address(),
499 user_transfer_type,
500 None,
501 false,
502 )
503 .unwrap()
504 }
505
506 mod router_encoder {
507 use super::*;
508
509 #[test]
510 #[allow(deprecated)]
511 fn test_encode_router_calldata_single_swap() {
512 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
513 let eth_amount_in = BigUint::from(1000u32);
514 let swap = Swap::new(
515 ProtocolComponent {
516 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
517 protocol_system: "uniswap_v2".to_string(),
518 ..Default::default()
519 },
520 weth().clone(),
521 dai().clone(),
522 );
523
524 let solution = Solution {
525 exact_out: false,
526 given_amount: eth_amount_in.clone(),
527 given_token: eth(),
528 checked_token: dai(),
529 swaps: vec![swap],
530 receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
531 native_action: Some(NativeAction::Wrap),
532 ..Default::default()
533 };
534
535 let transactions = encoder.encode_full_calldata(vec![solution]);
536 assert!(transactions.is_ok());
537 let transactions = transactions.unwrap();
538 assert_eq!(transactions.len(), 1);
539 assert_eq!(transactions[0].value, eth_amount_in);
540 assert_eq!(
541 transactions[0].to,
542 Bytes::from_str("0x6bc529dc7b81a031828ddce2bc419d01ff268c66").unwrap()
543 );
544 assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "5c4b639c");
546 }
547
548 #[test]
549 #[allow(deprecated)]
550 fn test_encode_router_calldata_single_swap_group() {
551 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
552 let solution = Solution {
553 exact_out: false,
554 given_token: usdc(),
555 given_amount: BigUint::from_str("1000_000000").unwrap(),
556 checked_token: pepe(),
557 checked_amount: BigUint::from_str("105_152_000000000000000000").unwrap(),
558 sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
559 receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
560 swaps: vec![swap_usdc_eth_univ4(), swap_eth_pepe_univ4()],
561 ..Default::default()
562 };
563
564 let transactions = encoder.encode_full_calldata(vec![solution]);
565 assert!(transactions.is_ok());
566 let transactions = transactions.unwrap();
567 assert_eq!(transactions.len(), 1);
568 assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "5c4b639c");
570 }
571
572 #[test]
573 #[allow(deprecated)]
574 fn test_encode_router_calldata_sequential_swap() {
575 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
576 let eth_amount_in = BigUint::from(1000u32);
577 let swap_weth_dai = Swap::new(
578 ProtocolComponent {
579 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
580 protocol_system: "uniswap_v2".to_string(),
581 ..Default::default()
582 },
583 weth().clone(),
584 dai().clone(),
585 );
586
587 let swap_dai_usdc = Swap::new(
588 ProtocolComponent {
589 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
590 protocol_system: "uniswap_v2".to_string(),
591 ..Default::default()
592 },
593 dai().clone(),
594 usdc().clone(),
595 );
596
597 let solution = Solution {
598 exact_out: false,
599 given_amount: eth_amount_in.clone(),
600 given_token: eth(),
601 checked_token: usdc(),
602 swaps: vec![swap_weth_dai, swap_dai_usdc],
603 receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
604 native_action: Some(NativeAction::Wrap),
605 checked_amount: BigUint::from(1000u32),
606 ..Default::default()
607 };
608
609 let transactions = encoder.encode_full_calldata(vec![solution]);
610 assert!(transactions.is_ok());
611 let transactions = transactions.unwrap();
612 assert_eq!(transactions.len(), 1);
613 assert_eq!(transactions[0].value, eth_amount_in);
614 assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "e21dd0d3");
616 }
617
618 #[test]
619 fn test_encode_router_calldata_split_swap_group() {
620 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
621 let mut swap_usdc_eth = swap_usdc_eth_univ4();
622 swap_usdc_eth = swap_usdc_eth.split(0.5); let solution = Solution {
624 exact_out: false,
625 given_token: usdc(),
626 given_amount: BigUint::from_str("1000_000000").unwrap(),
627 checked_token: eth(),
628 checked_amount: BigUint::from_str("105_152_000000000000000000").unwrap(),
629 sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
630 receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
631 swaps: vec![swap_usdc_eth, swap_usdc_eth_univ4()],
632 ..Default::default()
633 };
634
635 let encoded_solution_res = encoder.encode_solution(&solution);
636 assert!(encoded_solution_res.is_ok());
637
638 let encoded_solution = encoded_solution_res.unwrap();
639 assert!(encoded_solution
640 .function_signature
641 .contains("splitSwap"));
642 }
643
644 #[test]
645 fn test_validate_fails_for_exact_out() {
646 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
647 let solution = Solution {
648 exact_out: true, ..Default::default()
650 };
651 let result = encoder.validate_solution(&solution);
652
653 assert!(result.is_err());
654 assert_eq!(
655 result.err().unwrap(),
656 EncodingError::FatalError(
657 "Currently only exact input solutions are supported".to_string()
658 )
659 );
660 }
661
662 #[test]
663 fn test_validate_passes_for_wrap() {
664 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
665 let swap = Swap::new(
666 ProtocolComponent {
667 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
668 protocol_system: "uniswap_v2".to_string(),
669 ..Default::default()
670 },
671 weth().clone(),
672 dai().clone(),
673 );
674
675 let solution = Solution {
676 exact_out: false,
677 given_token: eth(),
678 checked_token: dai(),
679 swaps: vec![swap],
680 native_action: Some(NativeAction::Wrap),
681 ..Default::default()
682 };
683
684 let result = encoder.validate_solution(&solution);
685
686 assert!(result.is_ok());
687 }
688
689 #[test]
690 fn test_validate_fails_for_wrap_wrong_input() {
691 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
692 let swap = Swap::new(
693 ProtocolComponent {
694 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
695 protocol_system: "uniswap_v2".to_string(),
696 ..Default::default()
697 },
698 weth().clone(),
699 dai().clone(),
700 );
701
702 let solution = Solution {
703 exact_out: false,
704 given_token: weth(),
705 swaps: vec![swap],
706 native_action: Some(NativeAction::Wrap),
707 ..Default::default()
708 };
709
710 let result = encoder.validate_solution(&solution);
711
712 assert!(result.is_err());
713 assert_eq!(
714 result.err().unwrap(),
715 EncodingError::FatalError(
716 "Native token must be the input token in order to wrap".to_string()
717 )
718 );
719 }
720
721 #[test]
722 fn test_validate_fails_for_wrap_wrong_first_swap() {
723 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
724 let swap = Swap::new(
725 ProtocolComponent {
726 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
727 protocol_system: "uniswap_v2".to_string(),
728 ..Default::default()
729 },
730 eth().clone(),
731 dai().clone(),
732 );
733
734 let solution = Solution {
735 exact_out: false,
736 given_token: eth(),
737 swaps: vec![swap],
738 native_action: Some(NativeAction::Wrap),
739 ..Default::default()
740 };
741
742 let result = encoder.validate_solution(&solution);
743
744 assert!(result.is_err());
745 assert_eq!(
746 result.err().unwrap(),
747 EncodingError::FatalError(
748 "Wrapped token must be the first swap's input in order to wrap".to_string()
749 )
750 );
751 }
752
753 #[test]
754 fn test_validate_fails_no_swaps() {
755 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
756 let solution = Solution {
757 exact_out: false,
758 given_token: eth(),
759 swaps: vec![],
760 native_action: Some(NativeAction::Wrap),
761 ..Default::default()
762 };
763
764 let result = encoder.validate_solution(&solution);
765
766 assert!(result.is_err());
767 assert_eq!(
768 result.err().unwrap(),
769 EncodingError::FatalError("No swaps found in solution".to_string())
770 );
771 }
772
773 #[test]
774 fn test_validate_passes_for_unwrap() {
775 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
776 let swap = Swap::new(
777 ProtocolComponent {
778 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
779 protocol_system: "uniswap_v2".to_string(),
780 ..Default::default()
781 },
782 dai().clone(),
783 weth().clone(),
784 );
785
786 let solution = Solution {
787 exact_out: false,
788 checked_token: eth(),
789 swaps: vec![swap],
790 native_action: Some(NativeAction::Unwrap),
791 ..Default::default()
792 };
793
794 let result = encoder.validate_solution(&solution);
795
796 assert!(result.is_ok());
797 }
798
799 #[test]
800 fn test_validate_fails_for_unwrap_wrong_output() {
801 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
802 let swap = Swap::new(
803 ProtocolComponent {
804 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
805 protocol_system: "uniswap_v2".to_string(),
806 ..Default::default()
807 },
808 dai().clone(),
809 weth().clone(),
810 );
811
812 let solution = Solution {
813 exact_out: false,
814 given_token: dai(),
815 checked_token: weth(),
816 swaps: vec![swap],
817 native_action: Some(NativeAction::Unwrap),
818 ..Default::default()
819 };
820
821 let result = encoder.validate_solution(&solution);
822
823 assert!(result.is_err());
824 assert_eq!(
825 result.err().unwrap(),
826 EncodingError::FatalError(
827 "Native token must be the output token in order to unwrap".to_string()
828 )
829 );
830 }
831
832 #[test]
833 fn test_validate_fails_for_unwrap_wrong_last_swap() {
834 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
835 let swap = Swap::new(
836 ProtocolComponent {
837 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
838 protocol_system: "uniswap_v2".to_string(),
839 ..Default::default()
840 },
841 dai().clone(),
842 eth().clone(),
843 );
844
845 let solution = Solution {
846 exact_out: false,
847 checked_token: eth(),
848 swaps: vec![swap],
849 native_action: Some(NativeAction::Unwrap),
850 ..Default::default()
851 };
852
853 let result = encoder.validate_solution(&solution);
854
855 assert!(result.is_err());
856 assert_eq!(
857 result.err().unwrap(),
858 EncodingError::FatalError(
859 "Wrapped token must be the last swap's output in order to unwrap".to_string()
860 )
861 );
862 }
863
864 #[test]
865 fn test_validate_cyclical_swap() {
866 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
872 let swaps = vec![
873 Swap::new(
874 ProtocolComponent {
875 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
876 protocol_system: "uniswap_v2".to_string(),
877 ..Default::default()
878 },
879 dai().clone(),
880 weth().clone(),
881 ),
882 Swap::new(
883 ProtocolComponent {
884 id: "0x0000000000000000000000000000000000000000".to_string(),
885 protocol_system: "uniswap_v2".to_string(),
886 ..Default::default()
887 },
888 dai().clone(),
889 weth().clone(),
890 ),
891 Swap::new(
892 ProtocolComponent {
893 id: "0x0000000000000000000000000000000000000000".to_string(),
894 protocol_system: "uniswap_v2".to_string(),
895 ..Default::default()
896 },
897 weth().clone(),
898 dai().clone(),
899 ),
900 ];
901
902 let solution = Solution {
903 exact_out: false,
904 given_token: dai(),
905 checked_token: dai(),
906 swaps,
907 ..Default::default()
908 };
909
910 let result = encoder.validate_solution(&solution);
911
912 assert!(result.is_ok());
913 }
914
915 #[test]
916 fn test_validate_cyclical_swap_fail() {
917 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
921 let swaps = vec![
922 Swap::new(
923 ProtocolComponent {
924 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
925 protocol_system: "uniswap_v2".to_string(),
926 ..Default::default()
927 },
928 dai().clone(),
929 weth().clone(),
930 ),
931 Swap::new(
932 ProtocolComponent {
933 id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(),
934 protocol_system: "uniswap_v2".to_string(),
935 ..Default::default()
936 },
937 weth().clone(),
938 usdc().clone(),
939 ),
940 Swap::new(
941 ProtocolComponent {
942 id: "0x0000000000000000000000000000000000000000".to_string(),
943 protocol_system: "uniswap_v2".to_string(),
944 ..Default::default()
945 },
946 usdc().clone(),
947 dai().clone(),
948 ),
949 Swap::new(
950 ProtocolComponent {
951 id: "0x0000000000000000000000000000000000000000".to_string(),
952 protocol_system: "uniswap_v2".to_string(),
953 ..Default::default()
954 },
955 dai().clone(),
956 wbtc().clone(),
957 ),
958 ];
959
960 let solution = Solution {
961 exact_out: false,
962 given_token: dai(),
963 checked_token: wbtc(),
964 swaps,
965 ..Default::default()
966 };
967
968 let result = encoder.validate_solution(&solution);
969
970 assert!(result.is_err());
971 assert_eq!(
972 result.err().unwrap(),
973 EncodingError::FatalError(
974 "Cyclical swaps are only allowed if they are the first and last token of a solution".to_string()
975 )
976 );
977 }
978 #[test]
979 fn test_validate_cyclical_swap_split_output() {
980 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
986 let swaps = vec![
987 Swap::new(
988 ProtocolComponent {
989 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
990 protocol_system: "uniswap_v2".to_string(),
991 ..Default::default()
992 },
993 weth(),
994 dai(),
995 ),
996 Swap::new(
997 ProtocolComponent {
998 id: "0x0000000000000000000000000000000000000000".to_string(),
999 protocol_system: "uniswap_v2".to_string(),
1000 ..Default::default()
1001 },
1002 dai(),
1003 weth(),
1004 )
1005 .split(0.5),
1006 Swap::new(
1007 ProtocolComponent {
1008 id: "0x0000000000000000000000000000000000000000".to_string(),
1009 protocol_system: "uniswap_v2".to_string(),
1010 ..Default::default()
1011 },
1012 dai(),
1013 weth(),
1014 ),
1015 ];
1016
1017 let solution = Solution {
1018 exact_out: false,
1019 given_token: weth(),
1020 checked_token: weth(),
1021 swaps,
1022 ..Default::default()
1023 };
1024
1025 let result = encoder.validate_solution(&solution);
1026
1027 assert!(result.is_ok());
1028 }
1029
1030 #[test]
1031 fn test_validate_cyclical_swap_native_action_fail() {
1032 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
1036 let swaps = vec![
1037 Swap::new(
1038 ProtocolComponent {
1039 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
1040 protocol_system: "uniswap_v2".to_string(),
1041 ..Default::default()
1042 },
1043 weth(),
1044 dai(),
1045 ),
1046 Swap::new(
1047 ProtocolComponent {
1048 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
1049 protocol_system: "uniswap_v2".to_string(),
1050 ..Default::default()
1051 },
1052 dai(),
1053 weth(),
1054 ),
1055 ];
1056
1057 let solution = Solution {
1058 exact_out: false,
1059 given_token: eth(),
1060 checked_token: weth(),
1061 swaps,
1062 native_action: Some(NativeAction::Wrap),
1063 ..Default::default()
1064 };
1065
1066 let result = encoder.validate_solution(&solution);
1067
1068 assert!(result.is_err());
1069 assert_eq!(
1070 result.err().unwrap(),
1071 EncodingError::FatalError(
1072 "Wrapping/Unwrapping is not available in cyclical swaps"
1073 .to_string()
1074 .to_string()
1075 )
1076 );
1077 }
1078 }
1079
1080 mod executor_encoder {
1081 use std::str::FromStr;
1082
1083 use alloy::hex::encode;
1084 use num_bigint::BigUint;
1085 use tycho_common::{models::protocol::ProtocolComponent, Bytes};
1086
1087 use super::*;
1088 use crate::encoding::models::Solution;
1089
1090 #[test]
1091 fn test_executor_encoder_encode() {
1092 let swap_encoder_registry = get_swap_encoder_registry();
1093 let encoder = TychoExecutorEncoder::new(swap_encoder_registry).unwrap();
1094
1095 let token_in = weth();
1096 let token_out = dai();
1097
1098 let swap = Swap::new(
1099 ProtocolComponent {
1100 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
1101 protocol_system: "uniswap_v2".to_string(),
1102 ..Default::default()
1103 },
1104 token_in.clone(),
1105 token_out.clone(),
1106 );
1107
1108 let solution = Solution {
1109 exact_out: false,
1110 given_token: token_in,
1111 given_amount: BigUint::from(1000000000000000000u64),
1112 checked_token: token_out,
1113 checked_amount: BigUint::from(1000000000000000000u64),
1114 sender: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
1115 receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(),
1117 swaps: vec![swap],
1118 native_action: None,
1119 };
1120
1121 let encoded_solutions = encoder
1122 .encode_solutions(vec![solution])
1123 .unwrap();
1124 let encoded = encoded_solutions
1125 .first()
1126 .expect("Expected at least one encoded solution");
1127 let hex_protocol_data = encode(&encoded.swaps);
1128 assert_eq!(
1129 encoded.interacting_with,
1130 Bytes::from_str("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").unwrap()
1131 );
1132 assert_eq!(
1133 hex_protocol_data,
1134 String::from(concat!(
1135 "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
1137 "a478c2975ab1ea89e8196811f51a7b7ade33eb11",
1139 "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e",
1141 "00",
1143 "01",
1145 ))
1146 );
1147 }
1148
1149 #[test]
1150 fn test_executor_encoder_too_many_swaps() {
1151 let swap_encoder_registry = get_swap_encoder_registry();
1152 let encoder = TychoExecutorEncoder::new(swap_encoder_registry).unwrap();
1153
1154 let token_in = weth();
1155 let token_out = dai();
1156
1157 let swap = Swap::new(
1158 ProtocolComponent {
1159 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
1160 protocol_system: "uniswap_v2".to_string(),
1161 ..Default::default()
1162 },
1163 token_in.clone(),
1164 token_out.clone(),
1165 );
1166
1167 let solution = Solution {
1168 exact_out: false,
1169 given_token: token_in,
1170 given_amount: BigUint::from(1000000000000000000u64),
1171 checked_token: token_out,
1172 checked_amount: BigUint::from(1000000000000000000u64),
1173 sender: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
1174 receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(),
1175 swaps: vec![swap.clone(), swap],
1176 native_action: None,
1177 };
1178
1179 let result = encoder.encode_solutions(vec![solution]);
1180 assert!(result.is_err());
1181 }
1182
1183 #[test]
1184 fn test_executor_encoder_grouped_swaps() {
1185 let swap_encoder_registry = get_swap_encoder_registry();
1186 let encoder = TychoExecutorEncoder::new(swap_encoder_registry).unwrap();
1187
1188 let usdc = usdc();
1189 let pepe = pepe();
1190
1191 let solution = Solution {
1192 exact_out: false,
1193 given_token: usdc,
1194 given_amount: BigUint::from_str("1000_000000").unwrap(),
1195 checked_token: pepe,
1196 checked_amount: BigUint::from(1000000000000000000u64),
1197 sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
1198 receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
1199 swaps: vec![swap_usdc_eth_univ4(), swap_eth_pepe_univ4()],
1200 ..Default::default()
1201 };
1202
1203 let encoded_solutions = encoder
1204 .encode_solutions(vec![solution])
1205 .unwrap();
1206 let encoded_solution = encoded_solutions
1207 .first()
1208 .expect("Expected at least one encoded solution");
1209 let hex_protocol_data = encode(&encoded_solution.swaps);
1210 assert_eq!(
1211 encoded_solution.interacting_with,
1212 Bytes::from_str("0xf62849f9a0b5bf2913b396098f7c7019b51a820a").unwrap()
1213 );
1214 assert_eq!(
1215 hex_protocol_data,
1216 String::from(concat!(
1217 "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
1219 "6982508145454ce325ddbe47a25d4ec3d2311933",
1221 "00",
1223 "01",
1225 "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
1227 "0000000000000000000000000000000000000000",
1229 "000bb8",
1231 "00003c",
1233 "0000000000000000000000000000000000000000",
1235 "0000",
1237 "0030",
1239 "6982508145454ce325ddbe47a25d4ec3d2311933",
1241 "0061a8",
1243 "0001f4",
1245 "0000000000000000000000000000000000000000",
1247 "0000",
1249 ))
1250 );
1251 }
1252 }
1253}