1use std::{collections::HashSet, str::FromStr};
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 let executor_address = Bytes::from_str(swap_encoder.executor_address())
356 .map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?;
357
358 Ok(EncodedSolution {
359 swaps: initial_protocol_data,
360 interacting_with: executor_address,
361 permit: None,
362 function_signature: "".to_string(),
363 n_tokens: 0,
364 })
365 }
366}
367
368impl TychoEncoder for TychoExecutorEncoder {
369 fn encode_solutions(
370 &self,
371 solutions: Vec<Solution>,
372 ) -> Result<Vec<EncodedSolution>, EncodingError> {
373 let solution = solutions
374 .first()
375 .ok_or(EncodingError::FatalError("No solutions found".to_string()))?;
376 self.validate_solution(solution)?;
377
378 let encoded_solution = self.encode_executor_calldata(solution)?;
379
380 Ok(vec![encoded_solution])
381 }
382
383 fn encode_full_calldata(
384 &self,
385 _solutions: Vec<Solution>,
386 ) -> Result<Vec<Transaction>, EncodingError> {
387 Err(EncodingError::NotImplementedError(
388 "Full calldata encoding is not supported for TychoExecutorEncoder".to_string(),
389 ))
390 }
391
392 fn validate_solution(&self, solution: &Solution) -> Result<(), EncodingError> {
397 if solution.exact_out {
398 return Err(EncodingError::FatalError(
399 "Currently only exact input solutions are supported".to_string(),
400 ));
401 }
402 Ok(())
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use std::{collections::HashMap, fs, str::FromStr};
409
410 use num_bigint::{BigInt, BigUint};
411 use tycho_common::models::{protocol::ProtocolComponent, Chain};
412
413 use super::*;
414 use crate::encoding::models::{Swap, SwapBuilder};
415
416 fn dai() -> Bytes {
417 Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap()
418 }
419
420 fn eth() -> Bytes {
421 Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap()
422 }
423
424 fn weth() -> Bytes {
425 Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap()
426 }
427
428 fn usdc() -> Bytes {
429 Bytes::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap()
430 }
431
432 fn wbtc() -> Bytes {
433 Bytes::from_str("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599").unwrap()
434 }
435
436 fn pepe() -> Bytes {
437 Bytes::from_str("0x6982508145454Ce325dDbE47a25d4ec3d2311933").unwrap()
438 }
439
440 fn swap_usdc_eth_univ4() -> Swap {
444 let pool_fee_usdc_eth = Bytes::from(BigInt::from(3000).to_signed_bytes_be());
445 let tick_spacing_usdc_eth = Bytes::from(BigInt::from(60).to_signed_bytes_be());
446 let mut static_attributes_usdc_eth: HashMap<String, Bytes> = HashMap::new();
447 static_attributes_usdc_eth.insert("key_lp_fee".into(), pool_fee_usdc_eth);
448 static_attributes_usdc_eth.insert("tick_spacing".into(), tick_spacing_usdc_eth);
449 SwapBuilder::new(
450 ProtocolComponent {
451 id: "0xdce6394339af00981949f5f3baf27e3610c76326a700af57e4b3e3ae4977f78d"
452 .to_string(),
453 protocol_system: "uniswap_v4".to_string(),
454 static_attributes: static_attributes_usdc_eth,
455 ..Default::default()
456 },
457 usdc().clone(),
458 eth().clone(),
459 )
460 .build()
461 }
462
463 fn swap_eth_pepe_univ4() -> Swap {
464 let pool_fee_eth_pepe = Bytes::from(BigInt::from(25000).to_signed_bytes_be());
465 let tick_spacing_eth_pepe = Bytes::from(BigInt::from(500).to_signed_bytes_be());
466 let mut static_attributes_eth_pepe: HashMap<String, Bytes> = HashMap::new();
467 static_attributes_eth_pepe.insert("key_lp_fee".into(), pool_fee_eth_pepe);
468 static_attributes_eth_pepe.insert("tick_spacing".into(), tick_spacing_eth_pepe);
469 SwapBuilder::new(
470 ProtocolComponent {
471 id: "0xecd73ecbf77219f21f129c8836d5d686bbc27d264742ddad620500e3e548e2c9"
472 .to_string(),
473 protocol_system: "uniswap_v4".to_string(),
474 static_attributes: static_attributes_eth_pepe,
475 ..Default::default()
476 },
477 eth().clone(),
478 pepe().clone(),
479 )
480 .build()
481 }
482
483 fn router_address() -> Bytes {
484 Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap()
485 }
486
487 fn eth_chain() -> Chain {
488 Chain::Ethereum
489 }
490
491 fn get_swap_encoder_registry() -> SwapEncoderRegistry {
492 let executors_addresses =
493 fs::read_to_string("config/test_executor_addresses.json").unwrap();
494 SwapEncoderRegistry::new(Some(executors_addresses), eth_chain()).unwrap()
495 }
496
497 fn get_tycho_router_encoder(user_transfer_type: UserTransferType) -> TychoRouterEncoder {
498 TychoRouterEncoder::new(
499 eth_chain(),
500 get_swap_encoder_registry(),
501 router_address(),
502 user_transfer_type,
503 None,
504 false,
505 )
506 .unwrap()
507 }
508
509 mod router_encoder {
510 use super::*;
511
512 #[test]
513 #[allow(deprecated)]
514 fn test_encode_router_calldata_single_swap() {
515 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
516 let eth_amount_in = BigUint::from(1000u32);
517 let swap = SwapBuilder::new(
518 ProtocolComponent {
519 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
520 protocol_system: "uniswap_v2".to_string(),
521 ..Default::default()
522 },
523 weth().clone(),
524 dai().clone(),
525 )
526 .build();
527
528 let solution = Solution {
529 exact_out: false,
530 given_amount: eth_amount_in.clone(),
531 given_token: eth(),
532 checked_token: dai(),
533 swaps: vec![swap],
534 receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
535 native_action: Some(NativeAction::Wrap),
536 ..Default::default()
537 };
538
539 let transactions = encoder.encode_full_calldata(vec![solution]);
540 assert!(transactions.is_ok());
541 let transactions = transactions.unwrap();
542 assert_eq!(transactions.len(), 1);
543 assert_eq!(transactions[0].value, eth_amount_in);
544 assert_eq!(
545 transactions[0].to,
546 Bytes::from_str("0x3ede3eca2a72b3aecc820e955b36f38437d01395").unwrap()
547 );
548 assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "5c4b639c");
550 }
551
552 #[test]
553 #[allow(deprecated)]
554 fn test_encode_router_calldata_single_swap_group() {
555 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
556 let solution = Solution {
557 exact_out: false,
558 given_token: usdc(),
559 given_amount: BigUint::from_str("1000_000000").unwrap(),
560 checked_token: pepe(),
561 checked_amount: BigUint::from_str("105_152_000000000000000000").unwrap(),
562 sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
563 receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
564 swaps: vec![swap_usdc_eth_univ4(), swap_eth_pepe_univ4()],
565 ..Default::default()
566 };
567
568 let transactions = encoder.encode_full_calldata(vec![solution]);
569 assert!(transactions.is_ok());
570 let transactions = transactions.unwrap();
571 assert_eq!(transactions.len(), 1);
572 assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "5c4b639c");
574 }
575
576 #[test]
577 #[allow(deprecated)]
578 fn test_encode_router_calldata_sequential_swap() {
579 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
580 let eth_amount_in = BigUint::from(1000u32);
581 let swap_weth_dai = SwapBuilder::new(
582 ProtocolComponent {
583 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
584 protocol_system: "uniswap_v2".to_string(),
585 ..Default::default()
586 },
587 weth().clone(),
588 dai().clone(),
589 )
590 .build();
591
592 let swap_dai_usdc = SwapBuilder::new(
593 ProtocolComponent {
594 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
595 protocol_system: "uniswap_v2".to_string(),
596 ..Default::default()
597 },
598 dai().clone(),
599 usdc().clone(),
600 )
601 .build();
602
603 let solution = Solution {
604 exact_out: false,
605 given_amount: eth_amount_in.clone(),
606 given_token: eth(),
607 checked_token: usdc(),
608 swaps: vec![swap_weth_dai, swap_dai_usdc],
609 receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
610 native_action: Some(NativeAction::Wrap),
611 checked_amount: BigUint::from(1000u32),
612 ..Default::default()
613 };
614
615 let transactions = encoder.encode_full_calldata(vec![solution]);
616 assert!(transactions.is_ok());
617 let transactions = transactions.unwrap();
618 assert_eq!(transactions.len(), 1);
619 assert_eq!(transactions[0].value, eth_amount_in);
620 assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "e21dd0d3");
622 }
623
624 #[test]
625 fn test_encode_router_calldata_split_swap_group() {
626 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
627 let mut swap_usdc_eth = swap_usdc_eth_univ4();
628 swap_usdc_eth.split = 0.5; let solution = Solution {
630 exact_out: false,
631 given_token: usdc(),
632 given_amount: BigUint::from_str("1000_000000").unwrap(),
633 checked_token: eth(),
634 checked_amount: BigUint::from_str("105_152_000000000000000000").unwrap(),
635 sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
636 receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
637 swaps: vec![swap_usdc_eth, swap_usdc_eth_univ4()],
638 ..Default::default()
639 };
640
641 let encoded_solution_res = encoder.encode_solution(&solution);
642 assert!(encoded_solution_res.is_ok());
643
644 let encoded_solution = encoded_solution_res.unwrap();
645 assert!(encoded_solution
646 .function_signature
647 .contains("splitSwap"));
648 }
649
650 #[test]
651 fn test_validate_fails_for_exact_out() {
652 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
653 let solution = Solution {
654 exact_out: true, ..Default::default()
656 };
657 let result = encoder.validate_solution(&solution);
658
659 assert!(result.is_err());
660 assert_eq!(
661 result.err().unwrap(),
662 EncodingError::FatalError(
663 "Currently only exact input solutions are supported".to_string()
664 )
665 );
666 }
667
668 #[test]
669 fn test_validate_passes_for_wrap() {
670 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
671 let swap = SwapBuilder::new(
672 ProtocolComponent {
673 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
674 protocol_system: "uniswap_v2".to_string(),
675 ..Default::default()
676 },
677 weth().clone(),
678 dai().clone(),
679 )
680 .build();
681
682 let solution = Solution {
683 exact_out: false,
684 given_token: eth(),
685 checked_token: dai(),
686 swaps: vec![swap],
687 native_action: Some(NativeAction::Wrap),
688 ..Default::default()
689 };
690
691 let result = encoder.validate_solution(&solution);
692
693 assert!(result.is_ok());
694 }
695
696 #[test]
697 fn test_validate_fails_for_wrap_wrong_input() {
698 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
699 let swap = SwapBuilder::new(
700 ProtocolComponent {
701 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
702 protocol_system: "uniswap_v2".to_string(),
703 ..Default::default()
704 },
705 weth().clone(),
706 dai().clone(),
707 )
708 .build();
709
710 let solution = Solution {
711 exact_out: false,
712 given_token: weth(),
713 swaps: vec![swap],
714 native_action: Some(NativeAction::Wrap),
715 ..Default::default()
716 };
717
718 let result = encoder.validate_solution(&solution);
719
720 assert!(result.is_err());
721 assert_eq!(
722 result.err().unwrap(),
723 EncodingError::FatalError(
724 "Native token must be the input token in order to wrap".to_string()
725 )
726 );
727 }
728
729 #[test]
730 fn test_validate_fails_for_wrap_wrong_first_swap() {
731 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
732 let swap = SwapBuilder::new(
733 ProtocolComponent {
734 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
735 protocol_system: "uniswap_v2".to_string(),
736 ..Default::default()
737 },
738 eth().clone(),
739 dai().clone(),
740 )
741 .build();
742
743 let solution = Solution {
744 exact_out: false,
745 given_token: eth(),
746 swaps: vec![swap],
747 native_action: Some(NativeAction::Wrap),
748 ..Default::default()
749 };
750
751 let result = encoder.validate_solution(&solution);
752
753 assert!(result.is_err());
754 assert_eq!(
755 result.err().unwrap(),
756 EncodingError::FatalError(
757 "Wrapped token must be the first swap's input in order to wrap".to_string()
758 )
759 );
760 }
761
762 #[test]
763 fn test_validate_fails_no_swaps() {
764 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
765 let solution = Solution {
766 exact_out: false,
767 given_token: eth(),
768 swaps: vec![],
769 native_action: Some(NativeAction::Wrap),
770 ..Default::default()
771 };
772
773 let result = encoder.validate_solution(&solution);
774
775 assert!(result.is_err());
776 assert_eq!(
777 result.err().unwrap(),
778 EncodingError::FatalError("No swaps found in solution".to_string())
779 );
780 }
781
782 #[test]
783 fn test_validate_passes_for_unwrap() {
784 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
785 let swap = SwapBuilder::new(
786 ProtocolComponent {
787 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
788 protocol_system: "uniswap_v2".to_string(),
789 ..Default::default()
790 },
791 dai().clone(),
792 weth().clone(),
793 )
794 .build();
795
796 let solution = Solution {
797 exact_out: false,
798 checked_token: eth(),
799 swaps: vec![swap],
800 native_action: Some(NativeAction::Unwrap),
801 ..Default::default()
802 };
803
804 let result = encoder.validate_solution(&solution);
805
806 assert!(result.is_ok());
807 }
808
809 #[test]
810 fn test_validate_fails_for_unwrap_wrong_output() {
811 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
812 let swap = SwapBuilder::new(
813 ProtocolComponent {
814 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
815 protocol_system: "uniswap_v2".to_string(),
816 ..Default::default()
817 },
818 dai().clone(),
819 weth().clone(),
820 )
821 .build();
822
823 let solution = Solution {
824 exact_out: false,
825 given_token: dai(),
826 checked_token: weth(),
827 swaps: vec![swap],
828 native_action: Some(NativeAction::Unwrap),
829 ..Default::default()
830 };
831
832 let result = encoder.validate_solution(&solution);
833
834 assert!(result.is_err());
835 assert_eq!(
836 result.err().unwrap(),
837 EncodingError::FatalError(
838 "Native token must be the output token in order to unwrap".to_string()
839 )
840 );
841 }
842
843 #[test]
844 fn test_validate_fails_for_unwrap_wrong_last_swap() {
845 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
846 let swap = SwapBuilder::new(
847 ProtocolComponent {
848 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
849 protocol_system: "uniswap_v2".to_string(),
850 ..Default::default()
851 },
852 dai().clone(),
853 eth().clone(),
854 )
855 .build();
856
857 let solution = Solution {
858 exact_out: false,
859 checked_token: eth(),
860 swaps: vec![swap],
861 native_action: Some(NativeAction::Unwrap),
862 ..Default::default()
863 };
864
865 let result = encoder.validate_solution(&solution);
866
867 assert!(result.is_err());
868 assert_eq!(
869 result.err().unwrap(),
870 EncodingError::FatalError(
871 "Wrapped token must be the last swap's output in order to unwrap".to_string()
872 )
873 );
874 }
875
876 #[test]
877 fn test_validate_cyclical_swap() {
878 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
884 let swaps = vec![
885 SwapBuilder::new(
886 ProtocolComponent {
887 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
888 protocol_system: "uniswap_v2".to_string(),
889 ..Default::default()
890 },
891 dai().clone(),
892 weth().clone(),
893 )
894 .build(),
895 SwapBuilder::new(
896 ProtocolComponent {
897 id: "0x0000000000000000000000000000000000000000".to_string(),
898 protocol_system: "uniswap_v2".to_string(),
899 ..Default::default()
900 },
901 dai().clone(),
902 weth().clone(),
903 )
904 .build(),
905 SwapBuilder::new(
906 ProtocolComponent {
907 id: "0x0000000000000000000000000000000000000000".to_string(),
908 protocol_system: "uniswap_v2".to_string(),
909 ..Default::default()
910 },
911 weth().clone(),
912 dai().clone(),
913 )
914 .build(),
915 ];
916
917 let solution = Solution {
918 exact_out: false,
919 given_token: dai(),
920 checked_token: dai(),
921 swaps,
922 ..Default::default()
923 };
924
925 let result = encoder.validate_solution(&solution);
926
927 assert!(result.is_ok());
928 }
929
930 #[test]
931 fn test_validate_cyclical_swap_fail() {
932 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
936 let swaps = vec![
937 SwapBuilder::new(
938 ProtocolComponent {
939 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
940 protocol_system: "uniswap_v2".to_string(),
941 ..Default::default()
942 },
943 dai().clone(),
944 weth().clone(),
945 )
946 .build(),
947 SwapBuilder::new(
948 ProtocolComponent {
949 id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(),
950 protocol_system: "uniswap_v2".to_string(),
951 ..Default::default()
952 },
953 weth().clone(),
954 usdc().clone(),
955 )
956 .build(),
957 SwapBuilder::new(
958 ProtocolComponent {
959 id: "0x0000000000000000000000000000000000000000".to_string(),
960 protocol_system: "uniswap_v2".to_string(),
961 ..Default::default()
962 },
963 usdc().clone(),
964 dai().clone(),
965 )
966 .build(),
967 SwapBuilder::new(
968 ProtocolComponent {
969 id: "0x0000000000000000000000000000000000000000".to_string(),
970 protocol_system: "uniswap_v2".to_string(),
971 ..Default::default()
972 },
973 dai().clone(),
974 wbtc().clone(),
975 )
976 .build(),
977 ];
978
979 let solution = Solution {
980 exact_out: false,
981 given_token: dai(),
982 checked_token: wbtc(),
983 swaps,
984 ..Default::default()
985 };
986
987 let result = encoder.validate_solution(&solution);
988
989 assert!(result.is_err());
990 assert_eq!(
991 result.err().unwrap(),
992 EncodingError::FatalError(
993 "Cyclical swaps are only allowed if they are the first and last token of a solution".to_string()
994 )
995 );
996 }
997 #[test]
998 fn test_validate_cyclical_swap_split_output() {
999 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
1005 let swaps = vec![
1006 SwapBuilder::new(
1007 ProtocolComponent {
1008 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
1009 protocol_system: "uniswap_v2".to_string(),
1010 ..Default::default()
1011 },
1012 weth(),
1013 dai(),
1014 )
1015 .build(),
1016 SwapBuilder::new(
1017 ProtocolComponent {
1018 id: "0x0000000000000000000000000000000000000000".to_string(),
1019 protocol_system: "uniswap_v2".to_string(),
1020 ..Default::default()
1021 },
1022 dai(),
1023 weth(),
1024 )
1025 .split(0.5)
1026 .build(),
1027 SwapBuilder::new(
1028 ProtocolComponent {
1029 id: "0x0000000000000000000000000000000000000000".to_string(),
1030 protocol_system: "uniswap_v2".to_string(),
1031 ..Default::default()
1032 },
1033 dai(),
1034 weth(),
1035 )
1036 .build(),
1037 ];
1038
1039 let solution = Solution {
1040 exact_out: false,
1041 given_token: weth(),
1042 checked_token: weth(),
1043 swaps,
1044 ..Default::default()
1045 };
1046
1047 let result = encoder.validate_solution(&solution);
1048
1049 assert!(result.is_ok());
1050 }
1051
1052 #[test]
1053 fn test_validate_cyclical_swap_native_action_fail() {
1054 let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
1058 let swaps = vec![
1059 SwapBuilder::new(
1060 ProtocolComponent {
1061 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
1062 protocol_system: "uniswap_v2".to_string(),
1063 ..Default::default()
1064 },
1065 weth(),
1066 dai(),
1067 )
1068 .build(),
1069 SwapBuilder::new(
1070 ProtocolComponent {
1071 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
1072 protocol_system: "uniswap_v2".to_string(),
1073 ..Default::default()
1074 },
1075 dai(),
1076 weth(),
1077 )
1078 .build(),
1079 ];
1080
1081 let solution = Solution {
1082 exact_out: false,
1083 given_token: eth(),
1084 checked_token: weth(),
1085 swaps,
1086 native_action: Some(NativeAction::Wrap),
1087 ..Default::default()
1088 };
1089
1090 let result = encoder.validate_solution(&solution);
1091
1092 assert!(result.is_err());
1093 assert_eq!(
1094 result.err().unwrap(),
1095 EncodingError::FatalError(
1096 "Wrapping/Unwrapping is not available in cyclical swaps"
1097 .to_string()
1098 .to_string()
1099 )
1100 );
1101 }
1102 }
1103
1104 mod executor_encoder {
1105 use std::str::FromStr;
1106
1107 use alloy::hex::encode;
1108 use num_bigint::BigUint;
1109 use tycho_common::{models::protocol::ProtocolComponent, Bytes};
1110
1111 use super::*;
1112 use crate::encoding::models::Solution;
1113
1114 #[test]
1115 fn test_executor_encoder_encode() {
1116 let swap_encoder_registry = get_swap_encoder_registry();
1117 let encoder = TychoExecutorEncoder::new(swap_encoder_registry).unwrap();
1118
1119 let token_in = weth();
1120 let token_out = dai();
1121
1122 let swap = SwapBuilder::new(
1123 ProtocolComponent {
1124 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
1125 protocol_system: "uniswap_v2".to_string(),
1126 ..Default::default()
1127 },
1128 token_in.clone(),
1129 token_out.clone(),
1130 )
1131 .build();
1132
1133 let solution = Solution {
1134 exact_out: false,
1135 given_token: token_in,
1136 given_amount: BigUint::from(1000000000000000000u64),
1137 checked_token: token_out,
1138 checked_amount: BigUint::from(1000000000000000000u64),
1139 sender: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
1140 receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(),
1142 swaps: vec![swap],
1143 native_action: None,
1144 };
1145
1146 let encoded_solutions = encoder
1147 .encode_solutions(vec![solution])
1148 .unwrap();
1149 let encoded = encoded_solutions
1150 .first()
1151 .expect("Expected at least one encoded solution");
1152 let hex_protocol_data = encode(&encoded.swaps);
1153 assert_eq!(
1154 encoded.interacting_with,
1155 Bytes::from_str("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").unwrap()
1156 );
1157 assert_eq!(
1158 hex_protocol_data,
1159 String::from(concat!(
1160 "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
1162 "a478c2975ab1ea89e8196811f51a7b7ade33eb11",
1164 "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e",
1166 "00",
1168 "01",
1170 ))
1171 );
1172 }
1173
1174 #[test]
1175 fn test_executor_encoder_too_many_swaps() {
1176 let swap_encoder_registry = get_swap_encoder_registry();
1177 let encoder = TychoExecutorEncoder::new(swap_encoder_registry).unwrap();
1178
1179 let token_in = weth();
1180 let token_out = dai();
1181
1182 let swap = SwapBuilder::new(
1183 ProtocolComponent {
1184 id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
1185 protocol_system: "uniswap_v2".to_string(),
1186 ..Default::default()
1187 },
1188 token_in.clone(),
1189 token_out.clone(),
1190 )
1191 .build();
1192
1193 let solution = Solution {
1194 exact_out: false,
1195 given_token: token_in,
1196 given_amount: BigUint::from(1000000000000000000u64),
1197 checked_token: token_out,
1198 checked_amount: BigUint::from(1000000000000000000u64),
1199 sender: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
1200 receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(),
1201 swaps: vec![swap.clone(), swap],
1202 native_action: None,
1203 };
1204
1205 let result = encoder.encode_solutions(vec![solution]);
1206 assert!(result.is_err());
1207 }
1208
1209 #[test]
1210 fn test_executor_encoder_grouped_swaps() {
1211 let swap_encoder_registry = get_swap_encoder_registry();
1212 let encoder = TychoExecutorEncoder::new(swap_encoder_registry).unwrap();
1213
1214 let usdc = usdc();
1215 let pepe = pepe();
1216
1217 let solution = Solution {
1218 exact_out: false,
1219 given_token: usdc,
1220 given_amount: BigUint::from_str("1000_000000").unwrap(),
1221 checked_token: pepe,
1222 checked_amount: BigUint::from(1000000000000000000u64),
1223 sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
1224 receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
1225 swaps: vec![swap_usdc_eth_univ4(), swap_eth_pepe_univ4()],
1226 ..Default::default()
1227 };
1228
1229 let encoded_solutions = encoder
1230 .encode_solutions(vec![solution])
1231 .unwrap();
1232 let encoded_solution = encoded_solutions
1233 .first()
1234 .expect("Expected at least one encoded solution");
1235 let hex_protocol_data = encode(&encoded_solution.swaps);
1236 assert_eq!(
1237 encoded_solution.interacting_with,
1238 Bytes::from_str("0xf62849f9a0b5bf2913b396098f7c7019b51a820a").unwrap()
1239 );
1240 assert_eq!(
1241 hex_protocol_data,
1242 String::from(concat!(
1243 "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
1245 "6982508145454ce325ddbe47a25d4ec3d2311933",
1247 "00",
1249 "01",
1251 "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
1253 "0000000000000000000000000000000000000000",
1255 "0000000000000000000000000000000000000000",
1257 "000bb8",
1259 "00003c",
1261 "001a",
1263 "6982508145454ce325ddbe47a25d4ec3d2311933",
1265 "0061a8",
1267 "0001f4"
1269 ))
1270 );
1271 }
1272 }
1273}