1use std::collections::{HashMap, HashSet};
12
13use async_trait::async_trait;
14pub use petgraph::graph::EdgeIndex;
15use petgraph::{graph::NodeIndex, stable_graph};
16use tracing::{debug, trace};
17use tycho_simulation::tycho_common::models::Address;
18
19use super::GraphManager;
20use crate::{
21 feed::{
22 events::{EventError, MarketEvent, MarketEventHandler},
23 market_data::MarketDataView,
24 },
25 graph::GraphError,
26 types::ComponentId,
27};
28
29#[derive(Debug, Clone, Default)]
48pub struct EdgeData<D = ()> {
49 pub component_id: ComponentId,
51 pub data: Option<D>,
53}
54
55impl<M> EdgeData<M> {
56 pub fn new(component_id: ComponentId) -> Self {
58 Self { component_id, data: None }
59 }
60
61 pub fn with_data(component_id: ComponentId, data: M) -> Self {
63 Self { component_id, data: Some(data) }
64 }
65}
66
67pub type StableDiGraph<D> = stable_graph::StableDiGraph<Address, EdgeData<D>>;
69
70pub struct PetgraphStableDiGraphManager<D: Clone> {
77 graph: StableDiGraph<D>,
81 edge_map: HashMap<ComponentId, Vec<EdgeIndex>>,
83 node_map: HashMap<Address, NodeIndex>,
85}
86
87impl<D: Clone> PetgraphStableDiGraphManager<D> {
88 pub fn new() -> Self {
90 Self { graph: StableDiGraph::default(), edge_map: HashMap::new(), node_map: HashMap::new() }
91 }
92
93 pub(crate) fn find_node(&self, addr: &Address) -> Result<NodeIndex, GraphError> {
95 self.node_map
96 .get(addr)
97 .copied()
98 .ok_or_else(|| GraphError::TokenNotFound(addr.clone()))
99 }
100
101 fn get_or_create_node(&mut self, addr: &Address) -> NodeIndex {
104 match self.find_node(addr) {
106 Ok(node_idx) => node_idx,
107 Err(_) => {
108 let node_idx = self.graph.add_node(addr.clone());
109 self.node_map
110 .insert(addr.clone(), node_idx);
111 node_idx
112 }
113 }
114 }
115
116 fn add_edge(&mut self, from_idx: NodeIndex, to_idx: NodeIndex, component_id: &ComponentId) {
124 let edge_idx = self
125 .graph
126 .add_edge(from_idx, to_idx, EdgeData::new(component_id.clone()));
127 self.edge_map
128 .entry(component_id.clone())
129 .or_default()
130 .push(edge_idx);
131 }
132
133 fn add_component_edges(&mut self, component_id: &ComponentId, node_indices: &[NodeIndex]) {
136 node_indices
138 .iter()
139 .enumerate()
140 .flat_map(|(i, &from_idx)| {
141 node_indices
142 .iter()
143 .skip(i + 1)
144 .map(move |&to_idx| (from_idx, to_idx))
145 })
146 .for_each(|(from_idx, to_idx)| {
147 self.add_edge(from_idx, to_idx, component_id);
149 self.add_edge(to_idx, from_idx, component_id);
150 });
151 }
152
153 fn add_components(
163 &mut self,
164 components: &HashMap<ComponentId, Vec<Address>>,
165 ) -> Result<(), GraphError> {
166 let mut invalid_components = Vec::new();
167 let mut skipped_duplicates = 0usize;
168
169 let mut sorted_components: Vec<_> = components.iter().collect();
171 sorted_components.sort_by_key(|(id, _)| *id);
172
173 for (comp_id, tokens) in sorted_components {
174 if self.edge_map.contains_key(comp_id) {
175 trace!(component_id = %comp_id, "skipping already-tracked component");
176 skipped_duplicates += 1;
177 continue;
178 }
179
180 if tokens.len() < 2 {
181 invalid_components.push(comp_id.clone());
182 continue;
183 }
184 let mut sorted_tokens: Vec<&Address> = tokens.iter().collect();
185 sorted_tokens.sort();
186 let node_indices: Vec<NodeIndex> = sorted_tokens
187 .iter()
188 .map(|token| self.get_or_create_node(token))
189 .collect();
190 self.add_component_edges(comp_id, &node_indices);
191 }
192
193 if skipped_duplicates > 0 {
194 debug!(skipped_duplicates, "skipped duplicate components during add");
195 }
196
197 if !invalid_components.is_empty() {
199 return Err(GraphError::InvalidComponents(invalid_components));
200 }
201
202 Ok(())
203 }
204
205 fn remove_components(&mut self, components: &[ComponentId]) -> Result<(), GraphError> {
215 let mut missing_components = Vec::new();
216
217 for comp_id in components {
218 if let Some(edge_indices) = self.edge_map.remove(comp_id) {
220 for edge_idx in edge_indices {
221 self.graph.remove_edge(edge_idx);
222 }
223 } else {
224 missing_components.push(comp_id.clone());
226 }
227 }
228
229 if !missing_components.is_empty() {
231 return Err(GraphError::ComponentsNotFound(missing_components));
232 }
233
234 Ok(())
235 }
236
237 #[cfg(test)]
252 pub(crate) fn set_edge_weight(
253 &mut self,
254 component_id: &ComponentId,
255 token_in: &Address,
256 token_out: &Address,
257 data: D,
258 bidirectional: bool,
259 ) -> Result<(), GraphError> {
260 let from_idx = self.find_node(token_in)?;
261 let to_idx = self.find_node(token_out)?;
262
263 let edge_indices = self
265 .edge_map
266 .get(component_id)
267 .ok_or_else(|| GraphError::ComponentsNotFound(vec![component_id.clone()]))?;
268
269 let mut updated = false;
270 for &edge_idx in edge_indices {
271 let (edge_from, edge_to) = match self.graph.edge_endpoints(edge_idx) {
273 Some(endpoints) => endpoints,
274 None => continue,
275 };
276
277 let should_update = if bidirectional {
279 (edge_from == from_idx && edge_to == to_idx) ||
281 (edge_from == to_idx && edge_to == from_idx)
282 } else {
283 edge_from == from_idx && edge_to == to_idx
285 };
286
287 if should_update {
288 let edge_data = self
290 .graph
291 .edge_weight_mut(edge_idx)
292 .ok_or_else(|| GraphError::ComponentsNotFound(vec![component_id.clone()]))?;
293 if edge_data.component_id == *component_id {
295 edge_data.data = Some(data.clone());
296 updated = true;
297 }
298 }
299 }
300
301 if !updated {
302 return Err(GraphError::MissingComponentBetweenTokens(
303 token_in.clone(),
304 token_out.clone(),
305 component_id.clone(),
306 ));
307 }
308
309 Ok(())
310 }
311}
312
313impl<D: Clone + super::EdgeWeightFromSimAndDerived> PetgraphStableDiGraphManager<D> {
314 pub fn update_edge_weights_with_derived(
329 &mut self,
330 market: MarketDataView<'_>,
331 derived: &crate::derived::DerivedData,
332 ) -> usize {
333 let tokens = market.token_registry_ref();
334
335 let updates: Vec<(EdgeIndex, Option<D>)> = self
339 .graph
340 .edge_indices()
341 .filter_map(|edge_idx| {
342 let edge_data = self.graph.edge_weight(edge_idx)?;
343 let component_id = &edge_data.component_id;
344
345 let sim_state = market.get_simulation_state(component_id)?;
346
347 let (source_idx, target_idx) = self.graph.edge_endpoints(edge_idx)?;
348 let source_addr = &self.graph[source_idx];
349 let target_addr = &self.graph[target_idx];
350
351 let token_in = tokens.get(source_addr)?;
352 let token_out = tokens.get(target_addr)?;
353
354 Some((
355 edge_idx,
356 D::from_sim_and_derived(sim_state, component_id, token_in, token_out, derived),
357 ))
358 })
359 .collect();
360
361 let updated = updates
363 .iter()
364 .filter(|(_, w)| w.is_some())
365 .count();
366 for (edge_idx, weight) in updates {
367 if let Some(edge_data) = self.graph.edge_weight_mut(edge_idx) {
368 edge_data.data = weight;
369 }
370 }
371
372 updated
373 }
374}
375
376impl<D: Clone + super::EdgeWeightFromSimAndDerived> super::EdgeWeightUpdaterWithDerived
377 for PetgraphStableDiGraphManager<D>
378{
379 fn update_edge_weights_with_derived(
380 &mut self,
381 market: MarketDataView<'_>,
382 derived: &crate::derived::DerivedData,
383 ) -> usize {
384 self.update_edge_weights_with_derived(market, derived)
385 }
386}
387
388impl<D: Clone> Default for PetgraphStableDiGraphManager<D> {
389 fn default() -> Self {
390 Self::new()
391 }
392}
393
394impl<D: Clone + Send + Sync> GraphManager<StableDiGraph<D>> for PetgraphStableDiGraphManager<D> {
395 fn initialize_graph(&mut self, component_topology: &HashMap<ComponentId, Vec<Address>>) {
396 self.graph = StableDiGraph::default();
398 self.edge_map.clear();
399 self.node_map.clear();
400
401 let mut unique_tokens: Vec<Address> = component_topology
406 .values()
407 .flat_map(|v| v.iter())
408 .cloned()
409 .collect::<HashSet<_>>()
410 .into_iter()
411 .collect();
412 unique_tokens.sort();
413
414 for token in unique_tokens {
415 let node_idx = self.graph.add_node(token.clone());
416 self.node_map.insert(token, node_idx);
417 }
418
419 let mut sorted_components: Vec<_> = component_topology.iter().collect();
421 sorted_components.sort_by_key(|(id, _)| *id);
422
423 for (comp_id, tokens) in sorted_components {
424 let mut sorted_tokens: Vec<&Address> = tokens.iter().collect();
425 sorted_tokens.sort();
426 let node_indices: Vec<NodeIndex> = sorted_tokens
427 .iter()
428 .map(|token| self.node_map[*token])
429 .collect();
430 self.add_component_edges(comp_id, &node_indices);
431 }
432 }
433
434 fn graph(&self) -> &StableDiGraph<D> {
435 &self.graph
436 }
437}
438
439#[async_trait]
440impl<D: Clone + Send> MarketEventHandler for PetgraphStableDiGraphManager<D> {
441 async fn handle_event(&mut self, event: &MarketEvent) -> Result<(), EventError> {
442 match event {
443 MarketEvent::MarketUpdated { added_components, removed_components, .. } => {
444 let mut errors = Vec::new();
446
447 if let Err(e) = self.add_components(added_components) {
449 errors.push(e);
450 }
451
452 if let Err(e) = self.remove_components(removed_components) {
454 errors.push(e);
455 }
456
457 match errors.len() {
459 0 => Ok(()),
460 _ => Err(EventError::GraphErrors(errors)),
461 }
462 }
463 }
464 }
465}
466
467#[cfg(test)]
468mod tests {
469 use std::str::FromStr;
470
471 use super::*;
472
473 fn addr(s: &str) -> Address {
475 Address::from_str(s).expect("Invalid address hex string")
476 }
477
478 #[test]
479 fn test_initialize_graph_empty() {
480 let mut manager = PetgraphStableDiGraphManager::<()>::new();
481 let topology = HashMap::new();
482
483 manager.initialize_graph(&topology);
484
485 let graph = manager.graph();
486 assert_eq!(graph.node_count(), 0);
487 assert_eq!(graph.edge_count(), 0);
488 }
489
490 #[test]
491 fn test_initialize_graph_comprehensive() {
492 let mut manager = PetgraphStableDiGraphManager::<()>::new();
493 let mut topology = HashMap::new();
494 let token_a = addr("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); let token_b = addr("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); let token_c = addr("0x6B175474E89094C44Da98b954EedeAC495271d0F"); let token_d = addr("0xdAC17F958D2ee523a2206206994597C13D831ec7"); topology.insert(
501 "component1".to_string(),
502 vec![token_a.clone(), token_b.clone(), token_c.clone()],
503 );
504 topology.insert("component2".to_string(), vec![token_c.clone(), token_d.clone()]);
506
507 manager.initialize_graph(&topology);
508
509 let graph = manager.graph();
510 assert_eq!(graph.node_count(), 4);
512 assert_eq!(graph.edge_count(), 8);
516
517 let node_a = manager.find_node(&token_a).unwrap();
519 let node_b = manager.find_node(&token_b).unwrap();
520 let node_c = manager.find_node(&token_c).unwrap();
521 let node_d = manager.find_node(&token_d).unwrap();
522
523 assert_eq!(
525 graph
526 .edge_weight(graph.find_edge(node_a, node_b).unwrap())
527 .unwrap()
528 .component_id,
529 "component1".to_string()
530 );
531 assert_eq!(
532 graph
533 .edge_weight(graph.find_edge(node_b, node_a).unwrap())
534 .unwrap()
535 .component_id,
536 "component1".to_string()
537 );
538 assert_eq!(
539 graph
540 .edge_weight(graph.find_edge(node_a, node_c).unwrap())
541 .unwrap()
542 .component_id,
543 "component1".to_string()
544 );
545 assert_eq!(
546 graph
547 .edge_weight(graph.find_edge(node_c, node_a).unwrap())
548 .unwrap()
549 .component_id,
550 "component1".to_string()
551 );
552 assert_eq!(
553 graph
554 .edge_weight(graph.find_edge(node_b, node_c).unwrap())
555 .unwrap()
556 .component_id,
557 "component1".to_string()
558 );
559 assert_eq!(
560 graph
561 .edge_weight(graph.find_edge(node_c, node_b).unwrap())
562 .unwrap()
563 .component_id,
564 "component1".to_string()
565 );
566
567 assert_eq!(
569 graph
570 .edge_weight(graph.find_edge(node_c, node_d).unwrap())
571 .unwrap()
572 .component_id,
573 "component2".to_string()
574 );
575 assert_eq!(
576 graph
577 .edge_weight(graph.find_edge(node_d, node_c).unwrap())
578 .unwrap()
579 .component_id,
580 "component2".to_string()
581 );
582 }
583
584 #[test]
585 fn test_initialize_graph_multiple_edges_same_pair() {
586 let mut manager = PetgraphStableDiGraphManager::<()>::new();
587 let mut topology = HashMap::new();
588 let token_a = addr("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); let token_b = addr("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); topology.insert("component1".to_string(), vec![token_a.clone(), token_b.clone()]);
593 topology.insert("component2".to_string(), vec![token_a.clone(), token_b.clone()]);
594 topology.insert("component3".to_string(), vec![token_a.clone(), token_b.clone()]);
595
596 manager.initialize_graph(&topology);
597
598 let graph = manager.graph();
599 assert_eq!(graph.node_count(), 2);
601 assert_eq!(graph.edge_count(), 6);
603
604 let node_a = manager.find_node(&token_a).unwrap();
605 let node_b = manager.find_node(&token_b).unwrap();
606
607 let edges: Vec<_> = graph
609 .edges_connecting(node_a, node_b)
610 .collect();
611 assert_eq!(edges.len(), 3);
612
613 let component_ids: Vec<_> = edges
614 .iter()
615 .map(|e| &e.weight().component_id)
616 .collect();
617
618 assert!(component_ids.contains(&&"component1".to_string()));
620 assert!(component_ids.contains(&&"component2".to_string()));
621 assert!(component_ids.contains(&&"component3".to_string()));
622 }
623
624 #[test]
625 fn test_add_components_shared_tokens() {
626 let mut manager = PetgraphStableDiGraphManager::<()>::new();
627 let mut components = HashMap::new();
628 let token_a = addr("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); let token_b = addr("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); components.insert("component1".to_string(), vec![token_a.clone(), token_b.clone()]);
633 manager
634 .add_components(&components)
635 .unwrap();
636
637 let initial_node_count = manager.graph().node_count();
638 assert_eq!(initial_node_count, 2);
639
640 components.clear();
642 components.insert("component2".to_string(), vec![token_a.clone(), token_b.clone()]);
643 manager
644 .add_components(&components)
645 .unwrap();
646
647 assert_eq!(manager.graph().node_count(), 2, "Should not create duplicate nodes");
649 }
650
651 #[test]
652 fn test_add_tokenless_components_error() {
653 let mut manager = PetgraphStableDiGraphManager::<()>::new();
654 let mut components = HashMap::new();
655 let token_a = addr("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); let token_b = addr("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); components.insert("component1".to_string(), vec![token_a.clone(), token_b.clone()]);
660 components.insert("component2".to_string(), vec![]);
661 components.insert("component3".to_string(), vec![]);
662 let result = manager.add_components(&components);
663
664 assert!(result.is_err());
665 match result.unwrap_err() {
666 GraphError::InvalidComponents(ids) => {
667 assert_eq!(ids.len(), 2);
668 assert!(ids.contains(&"component2".to_string()));
669 assert!(ids.contains(&"component3".to_string()));
670 }
671 _ => panic!("Expected InvalidComponents error"),
672 }
673
674 assert_eq!(manager.graph().node_count(), 2);
676 assert_eq!(manager.graph().edge_count(), 2); }
678
679 #[test]
680 fn test_remove_components_not_found_error() {
681 let mut manager = PetgraphStableDiGraphManager::<()>::new();
682 let mut components = HashMap::new();
683 let token_a = addr("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); let token_b = addr("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); components.insert("component1".to_string(), vec![token_a.clone(), token_b.clone()]);
688 components.insert("component2".to_string(), vec![token_a.clone(), token_b.clone()]);
689 manager
690 .add_components(&components)
691 .unwrap();
692
693 let result = manager.remove_components(&[
695 "component1".to_string(),
696 "component3".to_string(),
697 "component4".to_string(),
698 ]);
699
700 assert!(result.is_err());
701 match result.unwrap_err() {
702 GraphError::ComponentsNotFound(ids) => {
703 assert_eq!(ids.len(), 2, "Expected 2 missing components");
704 assert!(ids.contains(&"component3".to_string()));
705 assert!(ids.contains(&"component4".to_string()));
706 }
707 _ => panic!("Expected ComponentsNotFound error"),
708 }
709
710 for edge in manager.graph().edge_indices() {
712 assert_eq!(
713 manager
714 .graph()
715 .edge_weight(edge)
716 .unwrap()
717 .component_id,
718 "component2".to_string()
719 );
720 }
721 }
722
723 #[test]
724 fn test_set_edge_weight_errors() {
725 let mut manager = PetgraphStableDiGraphManager::<()>::new();
726 let mut topology = HashMap::new();
727 let token_a = addr("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); let token_b = addr("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); let token_c = addr("0x6B175474E89094C44Da98b954EedeAC495271d0F"); topology.insert("component1".to_string(), vec![token_a.clone(), token_b.clone()]);
733 topology.insert("component2".to_string(), vec![token_b.clone(), token_c.clone()]);
734 manager.initialize_graph(&topology);
735
736 let result =
738 manager.set_edge_weight(&"component3".to_string(), &token_a, &token_b, (), true);
739 assert!(result.is_err());
740 match result.unwrap_err() {
741 GraphError::ComponentsNotFound(ids) => {
742 assert_eq!(ids, vec!["component3".to_string()]);
743 }
744 _ => panic!("Expected ComponentsNotFound error"),
745 }
746
747 let non_existent_token = addr("0x0000000000000000000000000000000000000000");
749 let result = manager.set_edge_weight(
750 &"component1".to_string(),
751 &token_a,
752 &non_existent_token, (),
754 true,
755 );
756 assert!(result.is_err());
757 match result.unwrap_err() {
758 GraphError::TokenNotFound(found_addr) => {
759 assert_eq!(found_addr, non_existent_token);
760 }
761 _ => panic!("Expected TokenNotFound error"),
762 }
763
764 let result = manager.set_edge_weight(
766 &"component1".to_string(),
767 &token_a,
768 &token_c, (),
770 true,
771 );
772 assert!(result.is_err());
773 match result.unwrap_err() {
774 GraphError::MissingComponentBetweenTokens(in_token, out_token, comp_id) => {
775 assert_eq!(in_token, token_a);
776 assert_eq!(out_token, token_c);
777 assert_eq!(comp_id, "component1".to_string());
778 }
779 _ => panic!("Expected MissingComponentBetweenTokens error"),
780 }
781 }
782
783 #[tokio::test]
784 async fn test_handle_event_propagates_errors() {
785 let mut manager = PetgraphStableDiGraphManager::<()>::new();
786 use std::collections::HashMap;
787
788 use crate::feed::events::{EventError, MarketEvent};
789
790 let event = MarketEvent::MarketUpdated {
792 added_components: HashMap::from([("component1".to_string(), vec![])]),
793 removed_components: vec!["component2".to_string()],
794 updated_components: vec![],
795 };
796
797 let result = manager.handle_event(&event).await;
798
799 assert!(result.is_err());
801 match result.unwrap_err() {
802 EventError::GraphErrors(errors) => {
803 assert_eq!(errors.len(), 2);
804 let has_add_error = errors
806 .iter()
807 .any(|e| matches!(e, GraphError::InvalidComponents(_)));
808 let has_remove_error = errors
809 .iter()
810 .any(|e| matches!(e, GraphError::ComponentsNotFound(_)));
811 assert!(has_add_error, "Should have InvalidComponents error");
812 assert!(has_remove_error, "Should have ComponentsNotFound error");
813 }
814 }
815 }
816
817 #[test]
818 fn test_add_components_skips_duplicates() {
819 let mut manager = PetgraphStableDiGraphManager::<()>::new();
820 let token_a = addr("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
821 let token_b = addr("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
822
823 let mut components = HashMap::new();
824 components.insert("component1".to_string(), vec![token_a.clone(), token_b.clone()]);
825
826 manager
827 .add_components(&components)
828 .unwrap();
829 let edge_count_after_first = manager.graph().edge_count();
830 assert_eq!(edge_count_after_first, 2); manager
834 .add_components(&components)
835 .unwrap();
836 let edge_count_after_second = manager.graph().edge_count();
837 assert_eq!(
838 edge_count_after_first, edge_count_after_second,
839 "Edge count should not change when re-adding the same component"
840 );
841 }
842
843 #[test]
844 fn edge_weight_cleared_on_spot_price_miss() {
845 use num_bigint::BigUint;
848 use num_traits::One;
849 use tycho_simulation::tycho_core::simulation::protocol_sim::Price;
850
851 use crate::{
852 algorithm::test_utils::{market_read, setup_market_weighted, token, MockProtocolSim},
853 derived::{types::TokenGasPrices, DerivedData},
854 };
855
856 let token_a = token(0x01, "A");
857 let token_b = token(0x02, "B");
858 let (market, mut manager) = setup_market_weighted(vec![(
859 "component1",
860 &token_a,
861 &token_b,
862 MockProtocolSim::new(2.0),
863 )]);
864
865 assert!(
866 manager
867 .graph()
868 .edge_indices()
869 .all(|e| manager
870 .graph()
871 .edge_weight(e)
872 .unwrap()
873 .data
874 .is_some()),
875 "edges should have weight data after setup"
876 );
877
878 let mut token_prices = TokenGasPrices::new();
879 for addr in [&token_a.address, &token_b.address] {
880 token_prices.insert(
881 addr.clone(),
882 Price { numerator: BigUint::one(), denominator: BigUint::one() },
883 );
884 }
885 let mut derived = DerivedData::new();
886 derived.set_spot_prices(Default::default(), vec![], 10, true);
887 derived.set_component_depths(Default::default(), vec![], 10, true);
888 derived.set_token_prices(token_prices, vec![], 10, true);
889
890 manager.update_edge_weights_with_derived(market_read(&market), &derived);
891
892 assert!(
893 manager
894 .graph()
895 .edge_indices()
896 .all(|e| manager
897 .graph()
898 .edge_weight(e)
899 .unwrap()
900 .data
901 .is_none()),
902 "stale edge weights must be cleared when spot price is unavailable"
903 );
904 }
905}