1use std::{any::Any, collections::HashMap, str::FromStr, sync::Arc};
4
5use tokio::sync::RwLock;
6use tycho_simulation::tycho_common::models::Address;
7
8use super::{
9 computation::{ComputationId, DerivedComputation, FailedItem, FailedItemError},
10 computations::{PoolDepthComputation, SpotPriceComputation, TokenGasPriceComputation},
11 types::{
12 PoolDepthKey, PoolDepths, SpotPriceKey, SpotPrices, TokenGasPriceKey, TokenGasPrices,
13 TokenPricesWithDeps,
14 },
15};
16use crate::derived::SharedDerivedDataRef;
17
18#[derive(Debug)]
20struct ComputedValue<T> {
21 data: T,
22 block: u64,
23}
24
25struct ComputedSlot {
27 data: Box<dyn Any + Send + Sync>,
28 block: u64,
29}
30
31impl std::fmt::Debug for ComputedSlot {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 f.debug_struct("ComputedSlot")
34 .field("block", &self.block)
35 .finish_non_exhaustive()
36 }
37}
38
39#[derive(Debug, Default)]
45pub struct DerivedData {
46 slots: HashMap<ComputationId, ComputedSlot>,
48 token_prices_failed: HashMap<TokenGasPriceKey, (u64, FailedItemError)>,
50 token_prices_deps: Option<ComputedValue<TokenPricesWithDeps>>,
52 pool_depths_failed: HashMap<PoolDepthKey, (u64, FailedItemError)>,
54 spot_prices_failed: HashMap<SpotPriceKey, (u64, FailedItemError)>,
56}
57
58fn parse_pair_key(s: &str) -> Option<(String, Address, Address)> {
60 let mut parts = s.rsplitn(3, '/');
61 let token_out_str = parts.next()?;
62 let token_in_str = parts.next()?;
63 let component_id = parts.next()?;
64 let token_in = Address::from_str(token_in_str).ok()?;
65 let token_out = Address::from_str(token_out_str).ok()?;
66 Some((component_id.to_string(), token_in, token_out))
67}
68
69impl DerivedData {
70 pub fn new() -> Self {
72 Self::default()
73 }
74
75 pub fn new_shared() -> SharedDerivedDataRef {
78 Arc::new(RwLock::new(Self::new()))
79 }
80
81 pub(crate) fn set_output<T: Any + Send + Sync>(
83 &mut self,
84 id: ComputationId,
85 data: T,
86 block: u64,
87 ) {
88 self.slots
89 .insert(id, ComputedSlot { data: Box::new(data), block });
90 }
91
92 pub(crate) fn output<T: Any>(&self, id: ComputationId) -> Option<&T> {
97 let slot = self.slots.get(id)?;
98 debug_assert!(slot.data.is::<T>(), "derived output {id} read as the wrong type");
99 slot.data.downcast_ref::<T>()
100 }
101
102 pub(crate) fn output_block(&self, id: ComputationId) -> Option<u64> {
104 self.slots
105 .get(id)
106 .map(|slot| slot.block)
107 }
108
109 fn clear_output(&mut self, id: ComputationId) {
111 self.slots.remove(id);
112 }
113
114 pub fn derived_data_ready(&self) -> bool {
116 self.token_prices_block().is_some() &&
117 self.token_prices_deps_block().is_some() &&
118 self.pool_depths_block().is_some() &&
119 self.spot_prices_block().is_some()
120 }
121
122 pub fn token_prices(&self) -> Option<&TokenGasPrices> {
128 self.output(TokenGasPriceComputation::ID)
129 }
130
131 pub fn token_prices_block(&self) -> Option<u64> {
133 self.output_block(TokenGasPriceComputation::ID)
134 }
135
136 pub fn set_token_prices(
142 &mut self,
143 prices: TokenGasPrices,
144 failed_items: Vec<FailedItem>,
145 block: u64,
146 is_full_recompute: bool,
147 ) {
148 let new_failures: HashMap<TokenGasPriceKey, (u64, FailedItemError)> = failed_items
149 .into_iter()
150 .filter_map(|f| {
151 Address::from_str(&f.key)
152 .ok()
153 .map(|k| (k, (block, f.error)))
154 })
155 .collect();
156
157 if is_full_recompute {
158 self.token_prices_failed = new_failures;
159 } else {
160 self.token_prices_failed
161 .retain(|k, _| !prices.contains_key(k));
162 self.token_prices_failed
163 .extend(new_failures);
164 }
165
166 self.set_output(TokenGasPriceComputation::ID, prices, block);
167 }
168
169 pub fn token_price_failure(&self, key: &TokenGasPriceKey) -> Option<(u64, &FailedItemError)> {
172 self.token_prices_failed
173 .get(key)
174 .map(|(block, error)| (*block, error))
175 }
176
177 pub fn clear_token_prices(&mut self) {
179 self.clear_output(TokenGasPriceComputation::ID);
180 self.token_prices_failed.clear();
181 }
182
183 pub fn token_prices_deps(&self) -> Option<&TokenPricesWithDeps> {
189 self.token_prices_deps
190 .as_ref()
191 .map(|v| &v.data)
192 }
193
194 pub fn token_prices_deps_block(&self) -> Option<u64> {
196 self.token_prices_deps
197 .as_ref()
198 .map(|v| v.block)
199 }
200
201 pub fn set_token_prices_deps(&mut self, prices: TokenPricesWithDeps, block: u64) {
203 self.token_prices_deps = Some(ComputedValue { data: prices, block });
204 }
205
206 pub fn clear_token_prices_deps(&mut self) {
208 self.token_prices_deps = None;
209 }
210
211 pub fn pool_depths(&self) -> Option<&PoolDepths> {
217 self.output(PoolDepthComputation::ID)
218 }
219
220 pub fn pool_depths_block(&self) -> Option<u64> {
222 self.output_block(PoolDepthComputation::ID)
223 }
224
225 pub fn set_pool_depths(
231 &mut self,
232 depths: PoolDepths,
233 failed_items: Vec<FailedItem>,
234 block: u64,
235 is_full_recompute: bool,
236 ) {
237 let new_failures: HashMap<PoolDepthKey, (u64, FailedItemError)> = failed_items
238 .into_iter()
239 .filter_map(|f| parse_pair_key(&f.key).map(|k| (k, (block, f.error))))
240 .collect();
241
242 if is_full_recompute {
243 self.pool_depths_failed = new_failures;
244 } else {
245 self.pool_depths_failed
246 .retain(|k, _| !depths.contains_key(k));
247 self.pool_depths_failed
248 .extend(new_failures);
249 }
250
251 self.set_output(PoolDepthComputation::ID, depths, block);
252 }
253
254 pub fn pool_depth_failure(&self, key: &PoolDepthKey) -> Option<(u64, &FailedItemError)> {
259 self.pool_depths_failed
260 .get(key)
261 .map(|(block, error)| (*block, error))
262 }
263
264 pub fn clear_pool_depths(&mut self) {
266 self.clear_output(PoolDepthComputation::ID);
267 self.pool_depths_failed.clear();
268 }
269
270 pub fn spot_prices(&self) -> Option<&SpotPrices> {
276 self.output(SpotPriceComputation::ID)
277 }
278
279 pub fn spot_prices_block(&self) -> Option<u64> {
281 self.output_block(SpotPriceComputation::ID)
282 }
283
284 pub fn set_spot_prices(
290 &mut self,
291 prices: SpotPrices,
292 failed_items: Vec<FailedItem>,
293 block: u64,
294 is_full_recompute: bool,
295 ) {
296 let new_failures: HashMap<SpotPriceKey, (u64, FailedItemError)> = failed_items
297 .into_iter()
298 .filter_map(|f| parse_pair_key(&f.key).map(|k| (k, (block, f.error))))
299 .collect();
300
301 if is_full_recompute {
302 self.spot_prices_failed = new_failures;
303 } else {
304 self.spot_prices_failed
305 .retain(|k, _| !prices.contains_key(k));
306 self.spot_prices_failed
307 .extend(new_failures);
308 }
309
310 self.set_output(SpotPriceComputation::ID, prices, block);
311 }
312
313 pub fn spot_price_failure(&self, key: &SpotPriceKey) -> Option<(u64, &FailedItemError)> {
318 self.spot_prices_failed
319 .get(key)
320 .map(|(block, error)| (*block, error))
321 }
322
323 pub fn clear_spot_prices(&mut self) {
325 self.clear_output(SpotPriceComputation::ID);
326 self.spot_prices_failed.clear();
327 }
328
329 pub fn clear_all(&mut self) {
335 self.slots.clear();
336 self.token_prices_failed.clear();
337 self.token_prices_deps = None;
338 self.pool_depths_failed.clear();
339 self.spot_prices_failed.clear();
340 }
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346 use crate::{algorithm::test_utils::addr, derived::types::SpotPrices};
347
348 fn failed(key: &str, error: FailedItemError) -> FailedItem {
349 FailedItem { key: key.to_string(), error }
350 }
351
352 fn pair_key(comp: &str, b_in: u8, b_out: u8) -> SpotPriceKey {
353 (comp.to_string(), addr(b_in), addr(b_out))
354 }
355
356 #[test]
357 fn test_token_prices_block_tracks_independently() {
358 let mut store = DerivedData::new();
359 assert_eq!(store.token_prices_block(), None);
360
361 store.set_token_prices(Default::default(), vec![], 42, true);
362 assert_eq!(store.token_prices_block(), Some(42));
363
364 assert_eq!(store.spot_prices_block(), None);
366 assert_eq!(store.pool_depths_block(), None);
367 }
368
369 #[test]
370 fn test_spot_prices_block_tracks_independently() {
371 let mut store = DerivedData::new();
372 store.set_spot_prices(Default::default(), vec![], 10, true);
373 assert_eq!(store.spot_prices_block(), Some(10));
374 assert_eq!(store.token_prices_block(), None);
375 }
376
377 #[test]
378 fn test_pool_depths_block_tracks_independently() {
379 let mut store = DerivedData::new();
380 store.set_pool_depths(Default::default(), vec![], 7, true);
381 assert_eq!(store.pool_depths_block(), Some(7));
382 assert_eq!(store.token_prices_block(), None);
383 }
384
385 #[test]
386 fn test_derived_data_ready() {
387 let mut store = DerivedData::new();
388 assert!(!store.derived_data_ready());
389
390 store.set_spot_prices(Default::default(), vec![], 5, true);
391 assert!(!store.derived_data_ready());
392
393 store.set_token_prices(Default::default(), vec![], 10, true);
394 assert!(!store.derived_data_ready());
395
396 store.set_token_prices_deps(Default::default(), 10);
397 assert!(!store.derived_data_ready());
398
399 store.set_pool_depths(Default::default(), vec![], 9, true);
400 assert!(store.derived_data_ready());
401 }
402
403 #[test]
404 fn test_clear_all_resets_all_fields() {
405 let mut store = DerivedData::new();
406 store.set_token_prices(Default::default(), vec![], 1, true);
407 store.set_spot_prices(Default::default(), vec![], 1, true);
408 store.set_pool_depths(Default::default(), vec![], 1, true);
409
410 store.clear_all();
411
412 assert!(store.token_prices().is_none());
413 assert!(store.spot_prices().is_none());
414 assert!(store.pool_depths().is_none());
415 assert!(!store.derived_data_ready());
416 }
417
418 #[test]
419 fn test_token_price_failure_stored_with_block() {
420 let token_addr = addr(0xab);
421 let key_str = format!("{token_addr}");
422 let mut store = DerivedData::new();
423 store.set_token_prices(
424 Default::default(),
425 vec![failed(&key_str, FailedItemError::SimulationFailed("sim error".into()))],
426 42,
427 true,
428 );
429 assert_eq!(
430 store.token_price_failure(&token_addr),
431 Some((42, &FailedItemError::SimulationFailed("sim error".into())))
432 );
433 assert_eq!(store.token_price_failure(&addr(0xcd)), None);
434 }
435
436 #[test]
437 fn test_spot_price_failure_stored_with_block() {
438 let key = pair_key("pool1", 0x01, 0x02);
439 let key_str = format!("pool1/{}/{}", addr(0x01), addr(0x02));
440 let mut store = DerivedData::new();
441 store.set_spot_prices(
442 Default::default(),
443 vec![failed(&key_str, FailedItemError::SimulationFailed("sim error".into()))],
444 10,
445 true,
446 );
447 assert_eq!(
448 store.spot_price_failure(&key),
449 Some((10, &FailedItemError::SimulationFailed("sim error".into())))
450 );
451 assert_eq!(store.spot_price_failure(&pair_key("pool1", 0x01, 0x03)), None);
452 }
453
454 #[test]
455 fn test_pool_depth_failure_stored_with_block() {
456 let key: PoolDepthKey = pair_key("pool1", 0x01, 0x02);
457 let key_str = format!("pool1/{}/{}", addr(0x01), addr(0x02));
458 let mut store = DerivedData::new();
459 store.set_pool_depths(
460 Default::default(),
461 vec![failed(&key_str, FailedItemError::SimulationFailed("depth error".into()))],
462 7,
463 true,
464 );
465 assert_eq!(
466 store.pool_depth_failure(&key),
467 Some((7, &FailedItemError::SimulationFailed("depth error".into())))
468 );
469 assert_eq!(store.pool_depth_failure(&pair_key("pool2", 0x01, 0x02)), None);
470 }
471
472 #[test]
473 fn test_rerunning_with_empty_failures_clears_old_reasons() {
474 let key = pair_key("pool1", 0x01, 0x02);
475 let key_str = format!("pool1/{}/{}", addr(0x01), addr(0x02));
476 let mut store = DerivedData::new();
477 store.set_spot_prices(
478 Default::default(),
479 vec![failed(&key_str, FailedItemError::MissingSimulationState)],
480 1,
481 true,
482 );
483 assert!(store.spot_price_failure(&key).is_some());
484
485 store.set_spot_prices(Default::default(), vec![], 2, true);
487 assert_eq!(store.spot_price_failure(&key), None);
488 }
489
490 #[test]
491 fn test_clear_token_prices_clears_failure_map() {
492 let token_addr = addr(0xab);
493 let key_str = format!("{token_addr}");
494 let mut store = DerivedData::new();
495 store.set_token_prices(
496 Default::default(),
497 vec![failed(&key_str, FailedItemError::AllSimulationPathsFailed)],
498 1,
499 true,
500 );
501 store.clear_token_prices();
502 assert_eq!(store.token_price_failure(&token_addr), None);
503 }
504
505 #[test]
506 fn test_clear_spot_prices_clears_failure_map() {
507 let key = pair_key("pool1", 0x01, 0x02);
508 let key_str = format!("pool1/{}/{}", addr(0x01), addr(0x02));
509 let mut store = DerivedData::new();
510 store.set_spot_prices(
511 Default::default(),
512 vec![failed(&key_str, FailedItemError::MissingSimulationState)],
513 1,
514 true,
515 );
516 store.clear_spot_prices();
517 assert_eq!(store.spot_price_failure(&key), None);
518 }
519
520 #[test]
521 fn test_clear_pool_depths_clears_failure_map() {
522 let key: PoolDepthKey = pair_key("pool1", 0x01, 0x02);
523 let key_str = format!("pool1/{}/{}", addr(0x01), addr(0x02));
524 let mut store = DerivedData::new();
525 store.set_pool_depths(
526 Default::default(),
527 vec![failed(&key_str, FailedItemError::MissingSpotPrice)],
528 1,
529 true,
530 );
531 store.clear_pool_depths();
532 assert_eq!(store.pool_depth_failure(&key), None);
533 }
534
535 #[test]
536 fn test_incremental_run_preserves_failures_for_unattempted_items() {
537 let key_a = pair_key("pool_a", 0x01, 0x02);
538 let key_a_str = format!("pool_a/{}/{}", addr(0x01), addr(0x02));
539 let key_b = pair_key("pool_b", 0x03, 0x04);
540 let key_b_str = format!("pool_b/{}/{}", addr(0x03), addr(0x04));
541
542 let mut store = DerivedData::new();
543
544 store.set_spot_prices(
546 Default::default(),
547 vec![
548 failed(&key_a_str, FailedItemError::MissingSimulationState),
549 failed(&key_b_str, FailedItemError::MissingTokenMetadata),
550 ],
551 10,
552 true,
553 );
554 assert_eq!(
555 store.spot_price_failure(&key_a),
556 Some((10, &FailedItemError::MissingSimulationState))
557 );
558 assert_eq!(
559 store.spot_price_failure(&key_b),
560 Some((10, &FailedItemError::MissingTokenMetadata))
561 );
562
563 let mut prices = SpotPrices::default();
565 prices.insert(key_b.clone(), 1.0);
566 store.set_spot_prices(prices, vec![], 11, false);
567
568 assert_eq!(
570 store.spot_price_failure(&key_a),
571 Some((10, &FailedItemError::MissingSimulationState))
572 );
573 assert_eq!(store.spot_price_failure(&key_b), None);
575 }
576
577 #[test]
578 fn test_incremental_run_updates_block_on_repeated_failure() {
579 let key = pair_key("pool_a", 0x01, 0x02);
580 let key_str = format!("pool_a/{}/{}", addr(0x01), addr(0x02));
581
582 let mut store = DerivedData::new();
583
584 store.set_spot_prices(
585 Default::default(),
586 vec![failed(&key_str, FailedItemError::MissingSimulationState)],
587 10,
588 true,
589 );
590 assert_eq!(
591 store.spot_price_failure(&key),
592 Some((10, &FailedItemError::MissingSimulationState))
593 );
594
595 store.set_spot_prices(
597 Default::default(),
598 vec![failed(&key_str, FailedItemError::MissingTokenMetadata)],
599 11,
600 false,
601 );
602 assert_eq!(
603 store.spot_price_failure(&key),
604 Some((11, &FailedItemError::MissingTokenMetadata))
605 );
606 }
607
608 #[test]
609 fn test_clear_all_clears_all_failure_maps() {
610 let token_addr = addr(0xab);
611 let token_str = format!("{token_addr}");
612 let pair = pair_key("pool1", 0x01, 0x02);
613 let pair_str = format!("pool1/{}/{}", addr(0x01), addr(0x02));
614
615 let mut store = DerivedData::new();
616 store.set_token_prices(
617 Default::default(),
618 vec![failed(&token_str, FailedItemError::AllSimulationPathsFailed)],
619 1,
620 true,
621 );
622 store.set_spot_prices(
623 Default::default(),
624 vec![failed(&pair_str, FailedItemError::MissingSimulationState)],
625 1,
626 true,
627 );
628 store.set_pool_depths(
629 Default::default(),
630 vec![failed(&pair_str, FailedItemError::MissingSpotPrice)],
631 1,
632 true,
633 );
634
635 store.clear_all();
636
637 assert_eq!(store.token_price_failure(&token_addr), None);
638 assert_eq!(store.spot_price_failure(&pair), None);
639 assert_eq!(store.pool_depth_failure(&pair), None);
640 }
641}