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