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 "000bb8",
1254 "00003c",
1256 "0000000000000000000000000000000000000000",
1258 "0000",
1260 "0030",
1262 "6982508145454ce325ddbe47a25d4ec3d2311933",
1264 "0061a8",
1266 "0001f4",
1268 "0000000000000000000000000000000000000000",
1270 "0000",
1272 ))
1273 );
1274 }
1275 }
1276}