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::{GROUPABLE_PROTOCOLS, IN_TRANSFER_REQUIRED_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.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.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.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 IN_TRANSFER_REQUIRED_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, SwapBuilder};
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 SwapBuilder::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 .build()
458 }
459
460 fn swap_eth_pepe_univ4() -> Swap {
461 let pool_fee_eth_pepe = Bytes::from(BigInt::from(25000).to_signed_bytes_be());
462 let tick_spacing_eth_pepe = Bytes::from(BigInt::from(500).to_signed_bytes_be());
463 let mut static_attributes_eth_pepe: HashMap<String, Bytes> = HashMap::new();
464 static_attributes_eth_pepe.insert("key_lp_fee".into(), pool_fee_eth_pepe);
465 static_attributes_eth_pepe.insert("tick_spacing".into(), tick_spacing_eth_pepe);
466 SwapBuilder::new(
467 ProtocolComponent {
468 id: "0xecd73ecbf77219f21f129c8836d5d686bbc27d264742ddad620500e3e548e2c9"
469 .to_string(),
470 protocol_system: "uniswap_v4".to_string(),
471 static_attributes: static_attributes_eth_pepe,
472 ..Default::default()
473 },
474 eth().clone(),
475 pepe().clone(),
476 )
477 .build()
478 }
479
480 fn router_address() -> Bytes {
481 Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap()
482 }
483
484 fn eth_chain() -> Chain {
485 Chain::Ethereum
486 }
487
488 fn get_swap_encoder_registry() -> SwapEncoderRegistry {
489 let executors_addresses =
490 fs::read_to_string("config/test_executor_addresses.json").unwrap();
491 SwapEncoderRegistry::new(Some(executors_addresses), eth_chain()).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 = SwapBuilder::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 .build();
524
525 let solution = Solution {
526 exact_out: false,
527 given_amount: eth_amount_in.clone(),
528 given_token: eth(),
529 checked_token: dai(),
530 swaps: vec![swap],
531 receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
532 native_action: Some(NativeAction::Wrap),
533 ..Default::default()
534 };
535
536 let transactions = encoder.encode_full_calldata(vec![solution]);
537 assert!(transactions.is_ok());
538 let transactions = transactions.unwrap();
539 assert_eq!(transactions.len(), 1);
540 assert_eq!(transactions[0].value, eth_amount_in);
541 assert_eq!(
542 transactions[0].to,
543 Bytes::from_str("0x3ede3eca2a72b3aecc820e955b36f38437d01395").unwrap()
544 );
545 assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "5c4b639c");
547 }
548
549 #[test]
550 #[allow(deprecated)]
551 fn test_encode_router_calldata_single_swap_group() {
552 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
553 let solution = Solution {
554 exact_out: false,
555 given_token: usdc(),
556 given_amount: BigUint::from_str("1000_000000").unwrap(),
557 checked_token: pepe(),
558 checked_amount: BigUint::from_str("105_152_000000000000000000").unwrap(),
559 sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
560 receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
561 swaps: vec![swap_usdc_eth_univ4(), swap_eth_pepe_univ4()],
562 ..Default::default()
563 };
564
565 let transactions = encoder.encode_full_calldata(vec![solution]);
566 assert!(transactions.is_ok());
567 let transactions = transactions.unwrap();
568 assert_eq!(transactions.len(), 1);
569 assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "5c4b639c");
571 }
572
573 #[test]
574 #[allow(deprecated)]
575 fn test_encode_router_calldata_sequential_swap() {
576 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
577 let eth_amount_in = BigUint::from(1000u32);
578 let swap_weth_dai = 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
589 let swap_dai_usdc = SwapBuilder::new(
590 ProtocolComponent {
591 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
592 protocol_system: "uniswap_v2".to_string(),
593 ..Default::default()
594 },
595 dai().clone(),
596 usdc().clone(),
597 )
598 .build();
599
600 let solution = Solution {
601 exact_out: false,
602 given_amount: eth_amount_in.clone(),
603 given_token: eth(),
604 checked_token: usdc(),
605 swaps: vec![swap_weth_dai, swap_dai_usdc],
606 receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
607 native_action: Some(NativeAction::Wrap),
608 checked_amount: BigUint::from(1000u32),
609 ..Default::default()
610 };
611
612 let transactions = encoder.encode_full_calldata(vec![solution]);
613 assert!(transactions.is_ok());
614 let transactions = transactions.unwrap();
615 assert_eq!(transactions.len(), 1);
616 assert_eq!(transactions[0].value, eth_amount_in);
617 assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "e21dd0d3");
619 }
620
621 #[test]
622 fn test_encode_router_calldata_split_swap_group() {
623 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
624 let mut swap_usdc_eth = swap_usdc_eth_univ4();
625 swap_usdc_eth.split = 0.5; let solution = Solution {
627 exact_out: false,
628 given_token: usdc(),
629 given_amount: BigUint::from_str("1000_000000").unwrap(),
630 checked_token: eth(),
631 checked_amount: BigUint::from_str("105_152_000000000000000000").unwrap(),
632 sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
633 receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
634 swaps: vec![swap_usdc_eth, swap_usdc_eth_univ4()],
635 ..Default::default()
636 };
637
638 let encoded_solution_res = encoder.encode_solution(&solution);
639 assert!(encoded_solution_res.is_ok());
640
641 let encoded_solution = encoded_solution_res.unwrap();
642 assert!(encoded_solution
643 .function_signature
644 .contains("splitSwap"));
645 }
646
647 #[test]
648 fn test_validate_fails_for_exact_out() {
649 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
650 let solution = Solution {
651 exact_out: true, ..Default::default()
653 };
654 let result = encoder.validate_solution(&solution);
655
656 assert!(result.is_err());
657 assert_eq!(
658 result.err().unwrap(),
659 EncodingError::FatalError(
660 "Currently only exact input solutions are supported".to_string()
661 )
662 );
663 }
664
665 #[test]
666 fn test_validate_passes_for_wrap() {
667 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
668 let swap = SwapBuilder::new(
669 ProtocolComponent {
670 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
671 protocol_system: "uniswap_v2".to_string(),
672 ..Default::default()
673 },
674 weth().clone(),
675 dai().clone(),
676 )
677 .build();
678
679 let solution = Solution {
680 exact_out: false,
681 given_token: eth(),
682 checked_token: dai(),
683 swaps: vec![swap],
684 native_action: Some(NativeAction::Wrap),
685 ..Default::default()
686 };
687
688 let result = encoder.validate_solution(&solution);
689
690 assert!(result.is_ok());
691 }
692
693 #[test]
694 fn test_validate_fails_for_wrap_wrong_input() {
695 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
696 let swap = SwapBuilder::new(
697 ProtocolComponent {
698 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
699 protocol_system: "uniswap_v2".to_string(),
700 ..Default::default()
701 },
702 weth().clone(),
703 dai().clone(),
704 )
705 .build();
706
707 let solution = Solution {
708 exact_out: false,
709 given_token: weth(),
710 swaps: vec![swap],
711 native_action: Some(NativeAction::Wrap),
712 ..Default::default()
713 };
714
715 let result = encoder.validate_solution(&solution);
716
717 assert!(result.is_err());
718 assert_eq!(
719 result.err().unwrap(),
720 EncodingError::FatalError(
721 "Native token must be the input token in order to wrap".to_string()
722 )
723 );
724 }
725
726 #[test]
727 fn test_validate_fails_for_wrap_wrong_first_swap() {
728 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
729 let swap = SwapBuilder::new(
730 ProtocolComponent {
731 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
732 protocol_system: "uniswap_v2".to_string(),
733 ..Default::default()
734 },
735 eth().clone(),
736 dai().clone(),
737 )
738 .build();
739
740 let solution = Solution {
741 exact_out: false,
742 given_token: eth(),
743 swaps: vec![swap],
744 native_action: Some(NativeAction::Wrap),
745 ..Default::default()
746 };
747
748 let result = encoder.validate_solution(&solution);
749
750 assert!(result.is_err());
751 assert_eq!(
752 result.err().unwrap(),
753 EncodingError::FatalError(
754 "Wrapped token must be the first swap's input in order to wrap".to_string()
755 )
756 );
757 }
758
759 #[test]
760 fn test_validate_fails_no_swaps() {
761 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
762 let solution = Solution {
763 exact_out: false,
764 given_token: eth(),
765 swaps: vec![],
766 native_action: Some(NativeAction::Wrap),
767 ..Default::default()
768 };
769
770 let result = encoder.validate_solution(&solution);
771
772 assert!(result.is_err());
773 assert_eq!(
774 result.err().unwrap(),
775 EncodingError::FatalError("No swaps found in solution".to_string())
776 );
777 }
778
779 #[test]
780 fn test_validate_passes_for_unwrap() {
781 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
782 let swap = SwapBuilder::new(
783 ProtocolComponent {
784 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
785 protocol_system: "uniswap_v2".to_string(),
786 ..Default::default()
787 },
788 dai().clone(),
789 weth().clone(),
790 )
791 .build();
792
793 let solution = Solution {
794 exact_out: false,
795 checked_token: eth(),
796 swaps: vec![swap],
797 native_action: Some(NativeAction::Unwrap),
798 ..Default::default()
799 };
800
801 let result = encoder.validate_solution(&solution);
802
803 assert!(result.is_ok());
804 }
805
806 #[test]
807 fn test_validate_fails_for_unwrap_wrong_output() {
808 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
809 let swap = SwapBuilder::new(
810 ProtocolComponent {
811 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
812 protocol_system: "uniswap_v2".to_string(),
813 ..Default::default()
814 },
815 dai().clone(),
816 weth().clone(),
817 )
818 .build();
819
820 let solution = Solution {
821 exact_out: false,
822 given_token: dai(),
823 checked_token: weth(),
824 swaps: vec![swap],
825 native_action: Some(NativeAction::Unwrap),
826 ..Default::default()
827 };
828
829 let result = encoder.validate_solution(&solution);
830
831 assert!(result.is_err());
832 assert_eq!(
833 result.err().unwrap(),
834 EncodingError::FatalError(
835 "Native token must be the output token in order to unwrap".to_string()
836 )
837 );
838 }
839
840 #[test]
841 fn test_validate_fails_for_unwrap_wrong_last_swap() {
842 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
843 let swap = SwapBuilder::new(
844 ProtocolComponent {
845 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
846 protocol_system: "uniswap_v2".to_string(),
847 ..Default::default()
848 },
849 dai().clone(),
850 eth().clone(),
851 )
852 .build();
853
854 let solution = Solution {
855 exact_out: false,
856 checked_token: eth(),
857 swaps: vec![swap],
858 native_action: Some(NativeAction::Unwrap),
859 ..Default::default()
860 };
861
862 let result = encoder.validate_solution(&solution);
863
864 assert!(result.is_err());
865 assert_eq!(
866 result.err().unwrap(),
867 EncodingError::FatalError(
868 "Wrapped token must be the last swap's output in order to unwrap".to_string()
869 )
870 );
871 }
872
873 #[test]
874 fn test_validate_cyclical_swap() {
875 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
881 let swaps = vec![
882 SwapBuilder::new(
883 ProtocolComponent {
884 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
885 protocol_system: "uniswap_v2".to_string(),
886 ..Default::default()
887 },
888 dai().clone(),
889 weth().clone(),
890 )
891 .build(),
892 SwapBuilder::new(
893 ProtocolComponent {
894 id: "0x0000000000000000000000000000000000000000".to_string(),
895 protocol_system: "uniswap_v2".to_string(),
896 ..Default::default()
897 },
898 dai().clone(),
899 weth().clone(),
900 )
901 .build(),
902 SwapBuilder::new(
903 ProtocolComponent {
904 id: "0x0000000000000000000000000000000000000000".to_string(),
905 protocol_system: "uniswap_v2".to_string(),
906 ..Default::default()
907 },
908 weth().clone(),
909 dai().clone(),
910 )
911 .build(),
912 ];
913
914 let solution = Solution {
915 exact_out: false,
916 given_token: dai(),
917 checked_token: dai(),
918 swaps,
919 ..Default::default()
920 };
921
922 let result = encoder.validate_solution(&solution);
923
924 assert!(result.is_ok());
925 }
926
927 #[test]
928 fn test_validate_cyclical_swap_fail() {
929 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
933 let swaps = vec![
934 SwapBuilder::new(
935 ProtocolComponent {
936 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
937 protocol_system: "uniswap_v2".to_string(),
938 ..Default::default()
939 },
940 dai().clone(),
941 weth().clone(),
942 )
943 .build(),
944 SwapBuilder::new(
945 ProtocolComponent {
946 id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(),
947 protocol_system: "uniswap_v2".to_string(),
948 ..Default::default()
949 },
950 weth().clone(),
951 usdc().clone(),
952 )
953 .build(),
954 SwapBuilder::new(
955 ProtocolComponent {
956 id: "0x0000000000000000000000000000000000000000".to_string(),
957 protocol_system: "uniswap_v2".to_string(),
958 ..Default::default()
959 },
960 usdc().clone(),
961 dai().clone(),
962 )
963 .build(),
964 SwapBuilder::new(
965 ProtocolComponent {
966 id: "0x0000000000000000000000000000000000000000".to_string(),
967 protocol_system: "uniswap_v2".to_string(),
968 ..Default::default()
969 },
970 dai().clone(),
971 wbtc().clone(),
972 )
973 .build(),
974 ];
975
976 let solution = Solution {
977 exact_out: false,
978 given_token: dai(),
979 checked_token: wbtc(),
980 swaps,
981 ..Default::default()
982 };
983
984 let result = encoder.validate_solution(&solution);
985
986 assert!(result.is_err());
987 assert_eq!(
988 result.err().unwrap(),
989 EncodingError::FatalError(
990 "Cyclical swaps are only allowed if they are the first and last token of a solution".to_string()
991 )
992 );
993 }
994 #[test]
995 fn test_validate_cyclical_swap_split_output() {
996 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
1002 let swaps = vec![
1003 SwapBuilder::new(
1004 ProtocolComponent {
1005 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
1006 protocol_system: "uniswap_v2".to_string(),
1007 ..Default::default()
1008 },
1009 weth(),
1010 dai(),
1011 )
1012 .build(),
1013 SwapBuilder::new(
1014 ProtocolComponent {
1015 id: "0x0000000000000000000000000000000000000000".to_string(),
1016 protocol_system: "uniswap_v2".to_string(),
1017 ..Default::default()
1018 },
1019 dai(),
1020 weth(),
1021 )
1022 .split(0.5)
1023 .build(),
1024 SwapBuilder::new(
1025 ProtocolComponent {
1026 id: "0x0000000000000000000000000000000000000000".to_string(),
1027 protocol_system: "uniswap_v2".to_string(),
1028 ..Default::default()
1029 },
1030 dai(),
1031 weth(),
1032 )
1033 .build(),
1034 ];
1035
1036 let solution = Solution {
1037 exact_out: false,
1038 given_token: weth(),
1039 checked_token: weth(),
1040 swaps,
1041 ..Default::default()
1042 };
1043
1044 let result = encoder.validate_solution(&solution);
1045
1046 assert!(result.is_ok());
1047 }
1048
1049 #[test]
1050 fn test_validate_cyclical_swap_native_action_fail() {
1051 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
1055 let swaps = vec![
1056 SwapBuilder::new(
1057 ProtocolComponent {
1058 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
1059 protocol_system: "uniswap_v2".to_string(),
1060 ..Default::default()
1061 },
1062 weth(),
1063 dai(),
1064 )
1065 .build(),
1066 SwapBuilder::new(
1067 ProtocolComponent {
1068 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
1069 protocol_system: "uniswap_v2".to_string(),
1070 ..Default::default()
1071 },
1072 dai(),
1073 weth(),
1074 )
1075 .build(),
1076 ];
1077
1078 let solution = Solution {
1079 exact_out: false,
1080 given_token: eth(),
1081 checked_token: weth(),
1082 swaps,
1083 native_action: Some(NativeAction::Wrap),
1084 ..Default::default()
1085 };
1086
1087 let result = encoder.validate_solution(&solution);
1088
1089 assert!(result.is_err());
1090 assert_eq!(
1091 result.err().unwrap(),
1092 EncodingError::FatalError(
1093 "Wrapping/Unwrapping is not available in cyclical swaps"
1094 .to_string()
1095 .to_string()
1096 )
1097 );
1098 }
1099 }
1100
1101 mod executor_encoder {
1102 use std::str::FromStr;
1103
1104 use alloy::hex::encode;
1105 use num_bigint::BigUint;
1106 use tycho_common::{models::protocol::ProtocolComponent, Bytes};
1107
1108 use super::*;
1109 use crate::encoding::models::Solution;
1110
1111 #[test]
1112 fn test_executor_encoder_encode() {
1113 let swap_encoder_registry = get_swap_encoder_registry();
1114 let encoder = TychoExecutorEncoder::new(swap_encoder_registry).unwrap();
1115
1116 let token_in = weth();
1117 let token_out = dai();
1118
1119 let swap = SwapBuilder::new(
1120 ProtocolComponent {
1121 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
1122 protocol_system: "uniswap_v2".to_string(),
1123 ..Default::default()
1124 },
1125 token_in.clone(),
1126 token_out.clone(),
1127 )
1128 .build();
1129
1130 let solution = Solution {
1131 exact_out: false,
1132 given_token: token_in,
1133 given_amount: BigUint::from(1000000000000000000u64),
1134 checked_token: token_out,
1135 checked_amount: BigUint::from(1000000000000000000u64),
1136 sender: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
1137 receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(),
1139 swaps: vec![swap],
1140 native_action: None,
1141 };
1142
1143 let encoded_solutions = encoder
1144 .encode_solutions(vec![solution])
1145 .unwrap();
1146 let encoded = encoded_solutions
1147 .first()
1148 .expect("Expected at least one encoded solution");
1149 let hex_protocol_data = encode(&encoded.swaps);
1150 assert_eq!(
1151 encoded.interacting_with,
1152 Bytes::from_str("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").unwrap()
1153 );
1154 assert_eq!(
1155 hex_protocol_data,
1156 String::from(concat!(
1157 "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
1159 "a478c2975ab1ea89e8196811f51a7b7ade33eb11",
1161 "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e",
1163 "00",
1165 "01",
1167 ))
1168 );
1169 }
1170
1171 #[test]
1172 fn test_executor_encoder_too_many_swaps() {
1173 let swap_encoder_registry = get_swap_encoder_registry();
1174 let encoder = TychoExecutorEncoder::new(swap_encoder_registry).unwrap();
1175
1176 let token_in = weth();
1177 let token_out = dai();
1178
1179 let swap = SwapBuilder::new(
1180 ProtocolComponent {
1181 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
1182 protocol_system: "uniswap_v2".to_string(),
1183 ..Default::default()
1184 },
1185 token_in.clone(),
1186 token_out.clone(),
1187 )
1188 .build();
1189
1190 let solution = Solution {
1191 exact_out: false,
1192 given_token: token_in,
1193 given_amount: BigUint::from(1000000000000000000u64),
1194 checked_token: token_out,
1195 checked_amount: BigUint::from(1000000000000000000u64),
1196 sender: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
1197 receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(),
1198 swaps: vec![swap.clone(), swap],
1199 native_action: None,
1200 };
1201
1202 let result = encoder.encode_solutions(vec![solution]);
1203 assert!(result.is_err());
1204 }
1205
1206 #[test]
1207 fn test_executor_encoder_grouped_swaps() {
1208 let swap_encoder_registry = get_swap_encoder_registry();
1209 let encoder = TychoExecutorEncoder::new(swap_encoder_registry).unwrap();
1210
1211 let usdc = usdc();
1212 let pepe = pepe();
1213
1214 let solution = Solution {
1215 exact_out: false,
1216 given_token: usdc,
1217 given_amount: BigUint::from_str("1000_000000").unwrap(),
1218 checked_token: pepe,
1219 checked_amount: BigUint::from(1000000000000000000u64),
1220 sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
1221 receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
1222 swaps: vec![swap_usdc_eth_univ4(), swap_eth_pepe_univ4()],
1223 ..Default::default()
1224 };
1225
1226 let encoded_solutions = encoder
1227 .encode_solutions(vec![solution])
1228 .unwrap();
1229 let encoded_solution = encoded_solutions
1230 .first()
1231 .expect("Expected at least one encoded solution");
1232 let hex_protocol_data = encode(&encoded_solution.swaps);
1233 assert_eq!(
1234 encoded_solution.interacting_with,
1235 Bytes::from_str("0xf62849f9a0b5bf2913b396098f7c7019b51a820a").unwrap()
1236 );
1237 assert_eq!(
1238 hex_protocol_data,
1239 String::from(concat!(
1240 "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
1242 "6982508145454ce325ddbe47a25d4ec3d2311933",
1244 "00",
1246 "01",
1248 "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
1250 "0000000000000000000000000000000000000000",
1252 "0000000000000000000000000000000000000000",
1254 "000bb8",
1256 "00003c",
1258 "001a",
1260 "6982508145454ce325ddbe47a25d4ec3d2311933",
1262 "0061a8",
1264 "0001f4"
1266 ))
1267 );
1268 }
1269 }
1270}