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