1use std::{
2 collections::BTreeSet,
3 fmt,
4 sync::Arc,
5 time::{SystemTime, UNIX_EPOCH},
6};
7
8use alloy_network::{Ethereum, Network};
9use alloy_primitives::{Address, Bytes, U256};
10use alloy_sol_types::SolCall;
11use evm_fork_cache::{
12 ColdStartCall, ColdStartConfig, ColdStartPlan, ColdStartPlanner, ColdStartResults,
13 ColdStartStep, StateView,
14 cache::EvmCache,
15 reactive::{
16 EventSubscriber, HandlerId, InterestOwnerSubscriber, ReactiveBatchReport, ReactiveConfig,
17 ReactiveEngine, ReactiveHandler, ReactiveInterest, ReactiveRuntime, SubscriberBackfill,
18 },
19};
20
21use crate::{
22 ChainlinkFeedProvider, FeedConfig, FeedId, FeedRegistration, OracleAdapterFeedSkip,
23 OracleAdapterPlugin, OracleCodeRegistry, OracleCodeWarmupPolicy, OracleCodeWarmupReport,
24 OracleDiscoveryContext, OracleError, OracleFeedReadinessReport, OracleFeedStatus,
25 OracleHookEvent, OraclePrice, OraclePriceCorrected, OraclePriceUpdate, OracleReactiveHandler,
26 OracleReadOverlay, OracleReconciler, OracleRegistry, OracleSignal, OracleStorageAdapter,
27 OracleStorageSync, OracleTracker, RoundData, StalenessPolicy,
28};
29
30#[cfg(feature = "pending-oracle-updates")]
31use evm_fork_cache::reactive::ReactiveInput;
32
33#[cfg(feature = "pending-oracle-updates")]
34use crate::{
35 PendingOracleAdapter, PendingOracleCandidateSource, PendingOracleConfig, PendingOracleRuntime,
36 PendingOracleSourceError, PendingOracleSourceSession,
37};
38
39type OracleEventCallback = Arc<dyn Fn(&OracleHookEvent) + Send + Sync>;
40
41alloy_sol_types::sol! {
42 function latestRoundData() external view returns (
43 uint80 roundId,
44 int256 answer,
45 uint256 startedAt,
46 uint256 updatedAt,
47 uint80 answeredInRound
48 );
49}
50
51#[derive(Clone, Debug)]
53pub struct ChainlinkFeed {
54 config: FeedConfig,
55}
56
57impl ChainlinkFeed {
58 pub fn new(proxy: Address) -> Self {
60 Self {
61 config: FeedConfig {
62 proxy,
63 ..Default::default()
64 },
65 }
66 }
67
68 pub fn id(mut self, id: impl Into<String>) -> Self {
70 self.config.id = Some(FeedId::new(id));
71 self
72 }
73
74 pub fn feed_id(mut self, id: FeedId) -> Self {
76 self.config.id = Some(id);
77 self
78 }
79
80 pub fn label(mut self, label: impl Into<String>) -> Self {
82 self.config.label = Some(label.into());
83 self
84 }
85
86 pub fn base(mut self, base: impl Into<String>) -> Self {
88 self.config.base = Some(base.into());
89 self
90 }
91
92 pub fn quote(mut self, quote: impl Into<String>) -> Self {
94 self.config.quote = Some(quote.into());
95 self
96 }
97
98 pub fn max_age_secs(mut self, max_age_secs: u64) -> Self {
100 self.config.staleness = StalenessPolicy::max_age(max_age_secs);
101 self
102 }
103
104 pub fn staleness(mut self, staleness: StalenessPolicy) -> Self {
106 self.config.staleness = staleness;
107 self
108 }
109
110 pub fn allow_zero_or_negative_answer(mut self, allow: bool) -> Self {
112 self.config.staleness = self.config.staleness.allow_zero_or_negative_answer(allow);
113 self
114 }
115
116 pub fn into_config(self) -> FeedConfig {
118 self.config
119 }
120}
121
122impl From<ChainlinkFeed> for FeedConfig {
123 fn from(feed: ChainlinkFeed) -> Self {
124 feed.into_config()
125 }
126}
127
128pub struct OracleRuntimeBuilder<P> {
130 provider: P,
131 feeds: Vec<ChainlinkFeed>,
132 now_timestamp: Option<u64>,
133 storage_sync: OracleStorageSync,
134 callbacks: Vec<OracleEventCallback>,
135 #[cfg(feature = "pending-oracle-updates")]
136 pending_config: Option<PendingOracleConfig>,
137 #[cfg(feature = "pending-oracle-updates")]
138 pending_adapters: Vec<Arc<dyn PendingOracleAdapter>>,
139}
140
141pub struct OracleCacheRuntimeBuilder {
143 adapters: Vec<Arc<dyn OracleAdapterPlugin>>,
144 now_timestamp: Option<u64>,
145 storage_sync: OracleStorageSync,
146 storage_warmup_enabled: bool,
147 storage_warmup_mode: OracleStorageWarmupMode,
148 code_registry: OracleCodeRegistry,
149 code_warmup_policy: OracleCodeWarmupPolicy,
150 callbacks: Vec<OracleEventCallback>,
151 #[cfg(feature = "pending-oracle-updates")]
152 pending_config: Option<PendingOracleConfig>,
153 #[cfg(feature = "pending-oracle-updates")]
154 pending_adapters: Vec<Arc<dyn PendingOracleAdapter>>,
155}
156
157#[non_exhaustive]
159#[derive(Debug)]
160pub struct OracleCacheRuntimeBuildReport {
161 pub runtime: OracleRuntime<()>,
163 pub skipped: Vec<OracleAdapterFeedSkip>,
165 pub feed_statuses: Vec<OracleFeedReadinessReport>,
167 pub storage_warmup: OracleStorageWarmupReport,
169 pub code_warmup: OracleCodeWarmupReport,
171}
172
173#[derive(Clone, Debug, Default, PartialEq, Eq)]
175pub struct OracleStorageWarmupReport {
176 pub mode: OracleStorageWarmupMode,
178 pub feed_statuses: Vec<OracleFeedReadinessReport>,
180 pub requested_slots: usize,
182 pub loaded_slots: usize,
184 pub failed_slots: Vec<OracleStorageWarmupFailure>,
186 pub discovery_calls: usize,
188 pub cold_start: Option<OracleColdStartWarmupReport>,
190}
191
192impl OracleStorageWarmupReport {
193 pub fn is_empty(&self) -> bool {
195 self.requested_slots == 0
196 && self.loaded_slots == 0
197 && self.failed_slots.is_empty()
198 && self.discovery_calls == 0
199 && self.cold_start.is_none()
200 }
201}
202
203#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
205pub enum OracleStorageWarmupMode {
206 #[default]
208 PrewarmSlots,
209 ColdStart,
211}
212
213#[derive(Clone, Debug, Default, PartialEq, Eq)]
215pub struct OracleColdStartWarmupReport {
216 pub rounds: usize,
218 pub verified_slots: usize,
220 pub changed_slots: usize,
222 pub failed_slots: usize,
224 pub discovered_slots: usize,
226 pub discovered_accounts: usize,
228 pub discover_calls: usize,
230}
231
232#[derive(Clone, Debug, PartialEq, Eq)]
234pub struct OracleStorageWarmupFailure {
235 pub address: Address,
237 pub slot: U256,
239 pub reason: String,
241}
242
243#[derive(Clone, Debug, Default, PartialEq, Eq)]
245pub struct OracleReactiveInstallReport {
246 pub handler_ids: Vec<HandlerId>,
248}
249
250impl OracleReactiveInstallReport {
251 pub fn len(&self) -> usize {
253 self.handler_ids.len()
254 }
255
256 pub fn is_empty(&self) -> bool {
258 self.handler_ids.is_empty()
259 }
260}
261
262#[derive(Clone, Debug, Default, PartialEq, Eq)]
264pub struct OracleReactiveUninstallReport {
265 pub handler_ids: Vec<HandlerId>,
267 pub removed_handler_ids: Vec<HandlerId>,
269}
270
271#[derive(Clone, Debug, Default, PartialEq, Eq)]
273pub struct OracleReactiveRefreshReport {
274 pub previous_handler_ids: Vec<HandlerId>,
276 pub removed_handler_ids: Vec<HandlerId>,
278 pub installed_handler_ids: Vec<HandlerId>,
280}
281
282#[derive(Clone, Debug, Default)]
284pub struct OracleRuntimeMutationReport {
285 pub registered_feed_ids: Vec<FeedId>,
287 pub removed_feed_ids: Vec<FeedId>,
289 pub skipped: Vec<OracleAdapterFeedSkip>,
291 pub feed_statuses: Vec<OracleFeedReadinessReport>,
293 pub current_handler_ids: Vec<HandlerId>,
295}
296
297#[derive(Clone)]
298struct OracleAdapterRuntimeState {
299 adapter: Arc<dyn OracleAdapterPlugin>,
300 registrations: Vec<FeedRegistration>,
301}
302
303impl fmt::Debug for OracleAdapterRuntimeState {
304 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305 f.debug_struct("OracleAdapterRuntimeState")
306 .field("adapter_id", &self.adapter.adapter_id())
307 .field("registrations_len", &self.registrations.len())
308 .finish()
309 }
310}
311
312impl<P> OracleRuntimeBuilder<P> {
313 pub fn new(provider: P) -> Self {
315 Self {
316 provider,
317 feeds: Vec::new(),
318 now_timestamp: None,
319 storage_sync: OracleStorageSync::chainlink_defaults(),
320 callbacks: Vec::new(),
321 #[cfg(feature = "pending-oracle-updates")]
322 pending_config: None,
323 #[cfg(feature = "pending-oracle-updates")]
324 pending_adapters: Vec::new(),
325 }
326 }
327
328 pub fn feed(mut self, feed: ChainlinkFeed) -> Self {
330 self.feeds.push(feed);
331 self
332 }
333
334 pub fn now_timestamp(mut self, now_timestamp: u64) -> Self {
336 self.now_timestamp = Some(now_timestamp);
337 self
338 }
339
340 pub fn storage_adapter<A>(mut self, adapter: A) -> Self
342 where
343 A: OracleStorageAdapter + 'static,
344 {
345 self.storage_sync.push_adapter(Arc::new(adapter));
346 self
347 }
348
349 pub fn storage_sync(mut self, storage_sync: OracleStorageSync) -> Self {
351 self.storage_sync = storage_sync;
352 self
353 }
354
355 pub fn on_event<F>(mut self, callback: F) -> Self
357 where
358 F: Fn(&OracleHookEvent) + Send + Sync + 'static,
359 {
360 self.callbacks.push(Arc::new(callback));
361 self
362 }
363
364 pub fn on_price_update<F>(self, callback: F) -> Self
366 where
367 F: Fn(&OraclePriceUpdate) + Send + Sync + 'static,
368 {
369 self.on_event(move |event| {
370 if let OracleHookEvent::PriceUpdate(update) = event {
371 callback(update);
372 }
373 })
374 }
375
376 pub fn on_price_corrected<F>(self, callback: F) -> Self
378 where
379 F: Fn(&OraclePriceCorrected) + Send + Sync + 'static,
380 {
381 self.on_event(move |event| {
382 if let OracleHookEvent::PriceCorrected(corrected) = event {
383 callback(corrected);
384 }
385 })
386 }
387
388 #[cfg(feature = "pending-oracle-updates")]
390 pub fn pending_updates(mut self, config: PendingOracleConfig) -> Self {
391 self.pending_config = Some(config);
392 self
393 }
394
395 #[cfg(feature = "pending-oracle-updates")]
397 pub fn pending_adapter<A>(mut self, adapter: A) -> Self
398 where
399 A: PendingOracleAdapter,
400 {
401 self.pending_adapters.push(Arc::new(adapter));
402 self
403 }
404}
405
406impl<P: ChainlinkFeedProvider> OracleRuntimeBuilder<P> {
407 pub async fn build(self) -> Result<OracleRuntime<P>, OracleError> {
416 let now_timestamp = match self.now_timestamp {
417 Some(now_timestamp) => now_timestamp,
418 None => SystemTime::now()
419 .duration_since(UNIX_EPOCH)
420 .map_err(crate::error::clock_error)?
421 .as_secs(),
422 };
423 let mut registry = OracleRegistry::new_at_timestamp(now_timestamp);
424 for feed in self.feeds {
425 registry
426 .register_chainlink_feed(&self.provider, feed.into_config())
427 .await?;
428 }
429
430 let tracker = OracleTracker::new(registry);
431 #[cfg(feature = "pending-oracle-updates")]
432 let pending = self.pending_config.map(|config| {
433 PendingOracleRuntime::from_registrations(
434 config,
435 tracker.registrations_iter(),
436 self.pending_adapters,
437 )
438 });
439
440 Ok(OracleRuntime {
441 provider: self.provider,
442 tracker,
443 storage_sync: self.storage_sync,
444 callbacks: self.callbacks,
445 adapter_states: Vec::new(),
446 #[cfg(feature = "pending-oracle-updates")]
447 pending,
448 #[cfg(feature = "pending-oracle-updates")]
449 pending_source_sessions: Vec::new(),
450 })
451 }
452}
453
454impl Default for OracleCacheRuntimeBuilder {
455 fn default() -> Self {
456 Self {
457 adapters: Vec::new(),
458 now_timestamp: None,
459 storage_sync: OracleStorageSync::chainlink_defaults(),
460 storage_warmup_enabled: true,
461 storage_warmup_mode: OracleStorageWarmupMode::default(),
462 code_registry: OracleCodeRegistry::default(),
463 code_warmup_policy: OracleCodeWarmupPolicy::default(),
464 callbacks: Vec::new(),
465 #[cfg(feature = "pending-oracle-updates")]
466 pending_config: None,
467 #[cfg(feature = "pending-oracle-updates")]
468 pending_adapters: Vec::new(),
469 }
470 }
471}
472
473impl OracleCacheRuntimeBuilder {
474 pub fn install_adapter<A>(mut self, adapter: A) -> Self
476 where
477 A: OracleAdapterPlugin + 'static,
478 {
479 self.adapters.push(Arc::new(adapter));
480 self
481 }
482
483 pub fn now_timestamp(mut self, now_timestamp: u64) -> Self {
485 self.now_timestamp = Some(now_timestamp);
486 self
487 }
488
489 pub fn storage_adapter<A>(mut self, adapter: A) -> Self
491 where
492 A: OracleStorageAdapter + 'static,
493 {
494 self.storage_sync.push_adapter(Arc::new(adapter));
495 self
496 }
497
498 pub fn storage_sync(mut self, storage_sync: OracleStorageSync) -> Self {
500 self.storage_sync = storage_sync;
501 self
502 }
503
504 pub fn storage_warmup(mut self, enabled: bool) -> Self {
510 self.storage_warmup_enabled = enabled;
511 self
512 }
513
514 pub fn storage_warmup_mode(mut self, mode: OracleStorageWarmupMode) -> Self {
516 self.storage_warmup_mode = mode;
517 self
518 }
519
520 pub fn storage_cold_start(self) -> Self {
522 self.storage_warmup_mode(OracleStorageWarmupMode::ColdStart)
523 }
524
525 pub fn disable_storage_warmup(self) -> Self {
527 self.storage_warmup(false)
528 }
529
530 pub fn code_registry(mut self, registry: OracleCodeRegistry) -> Self {
532 self.code_registry = registry;
533 self
534 }
535
536 pub fn code_warmup_policy(mut self, policy: OracleCodeWarmupPolicy) -> Self {
538 self.code_warmup_policy = policy;
539 self
540 }
541
542 pub fn code_seed(mut self, address: Address, code: Bytes) -> Self {
544 self.code_registry = self.code_registry.seed(address, code);
545 self
546 }
547
548 pub fn code_seeds<I>(mut self, seeds: I) -> Self
550 where
551 I: IntoIterator<Item = (Address, Bytes)>,
552 {
553 self.code_registry = self.code_registry.seed_many(seeds);
554 self
555 }
556
557 pub fn code_etch(mut self, address: Address, code: Bytes) -> Self {
559 self.code_registry = self.code_registry.etch(address, code);
560 self
561 }
562
563 pub fn code_etches<I>(mut self, etches: I) -> Self
565 where
566 I: IntoIterator<Item = (Address, Bytes)>,
567 {
568 self.code_registry = self.code_registry.etch_many(etches);
569 self
570 }
571
572 pub fn on_event<F>(mut self, callback: F) -> Self
574 where
575 F: Fn(&OracleHookEvent) + Send + Sync + 'static,
576 {
577 self.callbacks.push(Arc::new(callback));
578 self
579 }
580
581 pub fn on_price_update<F>(self, callback: F) -> Self
583 where
584 F: Fn(&OraclePriceUpdate) + Send + Sync + 'static,
585 {
586 self.on_event(move |event| {
587 if let OracleHookEvent::PriceUpdate(update) = event {
588 callback(update);
589 }
590 })
591 }
592
593 #[cfg(feature = "pending-oracle-updates")]
595 pub fn pending_updates(mut self, config: PendingOracleConfig) -> Self {
596 self.pending_config = Some(config);
597 self
598 }
599
600 #[cfg(feature = "pending-oracle-updates")]
602 pub fn pending_adapter<A>(mut self, adapter: A) -> Self
603 where
604 A: PendingOracleAdapter,
605 {
606 self.pending_adapters.push(Arc::new(adapter));
607 self
608 }
609
610 pub async fn build(self, cache: &mut EvmCache) -> Result<OracleRuntime<()>, OracleError> {
623 let report = self.build_report(cache).await?;
624 if let Some(skipped) = report.skipped.first() {
625 return Err(cache_runtime_skip_error(skipped));
626 }
627 Ok(report.runtime)
628 }
629
630 pub async fn build_report(
632 self,
633 cache: &mut EvmCache,
634 ) -> Result<OracleCacheRuntimeBuildReport, OracleError> {
635 let now_timestamp = match self.now_timestamp {
636 Some(now_timestamp) => now_timestamp,
637 None => SystemTime::now()
638 .duration_since(UNIX_EPOCH)
639 .map_err(crate::error::clock_error)?
640 .as_secs(),
641 };
642
643 let code_warmup = self
644 .code_registry
645 .apply_to_cache_with_policy(cache, self.code_warmup_policy)?;
646 let mut discovered_by_adapter = Vec::new();
647 let mut seeded = Vec::new();
648 let mut warmup_registrations = Vec::new();
649 let mut skipped = Vec::new();
650 for adapter in &self.adapters {
651 let report = adapter
652 .discover(OracleDiscoveryContext {
653 cache,
654 now_timestamp,
655 })
656 .await?;
657 skipped.extend(report.skipped);
658 let registrations = report
659 .feeds
660 .iter()
661 .map(|feed| feed.registration.clone())
662 .collect::<Vec<_>>();
663 seeded.extend(report.feeds.into_iter().map(|feed| {
664 warmup_registrations.push(feed.registration.clone());
665 (feed.registration, feed.round)
666 }));
667 discovered_by_adapter.push(OracleAdapterRuntimeState {
668 adapter: Arc::clone(adapter),
669 registrations,
670 });
671 }
672
673 let storage_warmup = if self.storage_warmup_enabled {
674 prewarm_oracle_storage(
675 cache,
676 &self.storage_sync,
677 &warmup_registrations.iter().collect::<Vec<_>>(),
678 self.storage_warmup_mode,
679 )
680 } else {
681 OracleStorageWarmupReport {
682 feed_statuses: feed_readiness_from_registrations(&warmup_registrations),
683 ..OracleStorageWarmupReport::default()
684 }
685 };
686 let mut feed_statuses = storage_warmup.feed_statuses.clone();
687 feed_statuses.extend(skipped.iter().map(skipped_feed_readiness));
688 let tracker = OracleTracker::from_registrations_at_timestamp(seeded, now_timestamp)?;
689 #[cfg(feature = "pending-oracle-updates")]
690 let pending = self.pending_config.map(|config| {
691 PendingOracleRuntime::from_registrations(
692 config,
693 tracker.registrations_iter(),
694 self.pending_adapters,
695 )
696 });
697 Ok(OracleCacheRuntimeBuildReport {
698 runtime: OracleRuntime {
699 provider: (),
700 tracker,
701 storage_sync: self.storage_sync,
702 callbacks: self.callbacks,
703 adapter_states: discovered_by_adapter,
704 #[cfg(feature = "pending-oracle-updates")]
705 pending,
706 #[cfg(feature = "pending-oracle-updates")]
707 pending_source_sessions: Vec::new(),
708 },
709 skipped,
710 feed_statuses,
711 storage_warmup,
712 code_warmup,
713 })
714 }
715}
716
717pub struct OracleRuntime<P> {
719 provider: P,
720 tracker: OracleTracker,
721 storage_sync: OracleStorageSync,
722 callbacks: Vec<OracleEventCallback>,
723 adapter_states: Vec<OracleAdapterRuntimeState>,
724 #[cfg(feature = "pending-oracle-updates")]
725 pending: Option<PendingOracleRuntime>,
726 #[cfg(feature = "pending-oracle-updates")]
727 pending_source_sessions: Vec<PendingOracleSourceSession>,
728}
729
730impl<P: fmt::Debug> fmt::Debug for OracleRuntime<P> {
731 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
732 let mut debug = f.debug_struct("OracleRuntime");
733 debug
734 .field("provider", &self.provider)
735 .field("tracker", &self.tracker)
736 .field("storage_sync", &self.storage_sync)
737 .field("callbacks_len", &self.callbacks.len())
738 .field("adapter_states", &self.adapter_states);
739 #[cfg(feature = "pending-oracle-updates")]
740 debug.field("pending", &self.pending);
741 #[cfg(feature = "pending-oracle-updates")]
742 debug.field(
743 "pending_source_sessions_len",
744 &self.pending_source_sessions.len(),
745 );
746 debug.finish()
747 }
748}
749
750impl<P> OracleRuntime<P> {
751 pub fn builder(provider: P) -> OracleRuntimeBuilder<P> {
753 OracleRuntimeBuilder::new(provider)
754 }
755
756 pub fn provider(&self) -> &P {
758 &self.provider
759 }
760
761 pub fn tracker(&self) -> &OracleTracker {
763 &self.tracker
764 }
765
766 pub fn tracker_mut(&mut self) -> &mut OracleTracker {
780 &mut self.tracker
781 }
782
783 pub fn feed_readiness(&self) -> Vec<OracleFeedReadinessReport> {
785 self.tracker.feed_readiness()
786 }
787
788 #[cfg(feature = "pending-oracle-updates")]
790 pub fn pending_updates(&self) -> Option<&PendingOracleRuntime> {
791 self.pending.as_ref()
792 }
793
794 #[cfg(feature = "pending-oracle-updates")]
799 pub fn start_pending_source<S>(&mut self, source: S) -> Result<(), PendingOracleSourceError>
800 where
801 S: PendingOracleCandidateSource,
802 {
803 let pending = self
804 .pending
805 .as_ref()
806 .ok_or(PendingOracleSourceError::PendingUpdatesDisabled)?;
807 let session = pending.start_source(source)?;
808 self.pending_source_sessions.push(session);
809 Ok(())
810 }
811
812 pub(crate) fn storage_sync(&self) -> &OracleStorageSync {
813 &self.storage_sync
814 }
815
816 pub fn with_storage_sync(mut self, storage_sync: OracleStorageSync) -> Self {
829 self.storage_sync = storage_sync;
830 self
831 }
832
833 pub fn reactive_handler(&self) -> OracleReactiveHandler {
839 OracleReactiveHandler::with_storage_sync(
840 self.tracker.registrations().collect(),
841 self.storage_sync.clone(),
842 )
843 }
844
845 pub fn reactive_handlers(&self) -> Vec<Arc<dyn ReactiveHandler<Ethereum>>> {
858 let built_in: Arc<dyn ReactiveHandler<Ethereum>> = Arc::new(self.reactive_handler());
859 let built_in_id = built_in.id();
860 let mut adapter_ids = BTreeSet::new();
861 let mut handlers = vec![built_in];
862 for state in self
863 .adapter_states
864 .iter()
865 .filter(|state| !state.registrations.is_empty())
866 {
867 let handler = state
868 .adapter
869 .reactive_handler(state.registrations.clone(), self.storage_sync.clone());
870 let id = handler.id();
871 if id == built_in_id {
872 continue;
875 }
876 if !adapter_ids.insert(id.clone()) {
877 tracing::warn!(
878 handler_id = %id,
879 adapter_id = %state.adapter.adapter_id(),
880 "duplicate reactive HandlerId from adapter plugins; \
881 dropping this adapter's handler routing"
882 );
883 continue;
884 }
885 handlers.push(handler);
886 }
887 handlers
888 }
889
890 pub fn reactive_handler_ids(&self) -> Vec<HandlerId> {
892 self.reactive_handlers()
893 .into_iter()
894 .map(|handler| handler.id())
895 .collect()
896 }
897
898 pub fn reactive_runtime(&self) -> Result<ReactiveRuntime<Ethereum>, OracleError> {
900 let mut runtime = ReactiveRuntime::<Ethereum>::new(ReactiveConfig::default());
901 for handler in self.reactive_handlers() {
902 runtime.register_handler(handler).map_err(runtime_error)?;
903 }
904 Ok(runtime)
905 }
906
907 pub async fn install_handlers<S>(
914 &self,
915 engine: &mut ReactiveEngine<S, Ethereum>,
916 ) -> Result<OracleReactiveInstallReport, OracleError>
917 where
918 S: InterestOwnerSubscriber<Ethereum>,
919 {
920 let mut report = OracleReactiveInstallReport::default();
921 for handler in self.reactive_handlers() {
922 let id = handler.id();
923 engine
924 .register_handler(handler)
925 .await
926 .map_err(runtime_error)?;
927 report.handler_ids.push(id);
928 }
929 Ok(report)
930 }
931
932 pub async fn refresh_handlers<S>(
938 &self,
939 engine: &mut ReactiveEngine<S, Ethereum>,
940 installed: &mut OracleReactiveInstallReport,
941 ) -> Result<OracleReactiveRefreshReport, OracleError>
942 where
943 S: InterestOwnerSubscriber<Ethereum>,
944 {
945 self.refresh_handlers_inner(engine, installed, OracleHandlerRefreshBackfill::Continuity)
946 .await
947 }
948
949 pub async fn refresh_handlers_with_backfill<S>(
951 &self,
952 engine: &mut ReactiveEngine<S, Ethereum>,
953 installed: &mut OracleReactiveInstallReport,
954 backfill: SubscriberBackfill,
955 ) -> Result<OracleReactiveRefreshReport, OracleError>
956 where
957 S: InterestOwnerSubscriber<Ethereum>,
958 {
959 self.refresh_handlers_inner(
960 engine,
961 installed,
962 OracleHandlerRefreshBackfill::Explicit(backfill),
963 )
964 .await
965 }
966
967 pub async fn refresh_handlers_live_only<S>(
969 &self,
970 engine: &mut ReactiveEngine<S, Ethereum>,
971 installed: &mut OracleReactiveInstallReport,
972 ) -> Result<OracleReactiveRefreshReport, OracleError>
973 where
974 S: InterestOwnerSubscriber<Ethereum>,
975 {
976 self.refresh_handlers_inner(engine, installed, OracleHandlerRefreshBackfill::LiveOnly)
977 .await
978 }
979
980 pub async fn uninstall_installed_handlers<S>(
985 &self,
986 engine: &mut ReactiveEngine<S, Ethereum>,
987 installed: &mut OracleReactiveInstallReport,
988 ) -> Result<OracleReactiveUninstallReport, OracleError>
989 where
990 S: InterestOwnerSubscriber<Ethereum>,
991 {
992 let handler_ids = installed.handler_ids.clone();
993 let removed_handler_ids =
994 unregister_handler_ids(engine, &mut installed.handler_ids).await?;
995 Ok(OracleReactiveUninstallReport {
996 handler_ids,
997 removed_handler_ids,
998 })
999 }
1000
1001 pub async fn install_handlers_with_backfill<S>(
1012 &self,
1013 engine: &mut ReactiveEngine<S, Ethereum>,
1014 backfill: SubscriberBackfill,
1015 ) -> Result<OracleReactiveInstallReport, OracleError>
1016 where
1017 S: InterestOwnerSubscriber<Ethereum>,
1018 {
1019 let mut report = OracleReactiveInstallReport::default();
1020 for handler in self.reactive_handlers() {
1021 let id = handler.id();
1022 engine
1023 .register_handler_with_backfill(handler, backfill)
1024 .await
1025 .map_err(runtime_error)?;
1026 report.handler_ids.push(id);
1027 }
1028 Ok(report)
1029 }
1030
1031 pub async fn install_handlers_live_only<S>(
1038 &self,
1039 engine: &mut ReactiveEngine<S, Ethereum>,
1040 ) -> Result<OracleReactiveInstallReport, OracleError>
1041 where
1042 S: InterestOwnerSubscriber<Ethereum>,
1043 {
1044 let mut report = OracleReactiveInstallReport::default();
1045 for handler in self.reactive_handlers() {
1046 let id = handler.id();
1047 engine
1048 .register_handler_live_only(handler)
1049 .await
1050 .map_err(runtime_error)?;
1051 report.handler_ids.push(id);
1052 }
1053 Ok(report)
1054 }
1055
1056 pub async fn uninstall_handlers<S>(
1067 &self,
1068 engine: &mut ReactiveEngine<S, Ethereum>,
1069 ) -> Result<OracleReactiveUninstallReport, OracleError>
1070 where
1071 S: InterestOwnerSubscriber<Ethereum>,
1072 {
1073 let mut report = OracleReactiveUninstallReport::default();
1074 for handler in self.reactive_handlers() {
1075 let id = handler.id();
1076 report.handler_ids.push(id.clone());
1077 if engine
1078 .unregister_handler(&id)
1079 .await
1080 .map_err(runtime_error)?
1081 .is_some()
1082 {
1083 report.removed_handler_ids.push(id);
1084 }
1085 }
1086 Ok(report)
1087 }
1088
1089 pub async fn register_subscriber<S>(&self, subscriber: &mut S) -> Result<(), OracleError>
1091 where
1092 S: EventSubscriber<Ethereum>,
1093 {
1094 subscriber
1095 .register_interests(&self.reactive_interests())
1096 .await
1097 .map_err(runtime_error)
1098 }
1099
1100 pub async fn next_subscriber_events<S>(
1103 &mut self,
1104 cache: &mut EvmCache,
1105 runtime: &mut ReactiveRuntime<Ethereum>,
1106 subscriber: &mut S,
1107 ) -> Result<Option<crate::OracleBatchReport>, OracleError>
1108 where
1109 S: EventSubscriber<Ethereum>,
1110 {
1111 let Some(batch) = subscriber.next_batch().await.map_err(runtime_error)? else {
1112 return Ok(None);
1113 };
1114 #[cfg(feature = "pending-oracle-updates")]
1115 let committed_logs = batch
1116 .records()
1117 .iter()
1118 .filter_map(|record| match &record.input {
1119 ReactiveInput::Log(log) => Some(log.clone()),
1120 _ => None,
1121 })
1122 .collect::<Vec<_>>();
1123 let report = runtime.ingest_batch(cache, batch).map_err(runtime_error)?;
1124 let typed = self.apply_batch_report(&report)?;
1125 #[cfg(feature = "pending-oracle-updates")]
1126 if let Some(pending) = &self.pending {
1127 for log in &committed_logs {
1128 pending.observe_confirmed_log(log);
1129 }
1130 }
1131 Ok(Some(typed))
1132 }
1133
1134 pub fn read_overlay(&self) -> OracleReadOverlay<'_> {
1136 OracleReadOverlay::new(&self.tracker)
1137 }
1138
1139 pub fn register_seeded_feed(
1152 &mut self,
1153 registration: FeedRegistration,
1154 round: RoundData,
1155 ) -> Result<OracleRuntimeMutationReport, OracleError> {
1156 let id = registration.id.clone();
1157 self.tracker
1158 .insert_seeded_registration(registration, round)?;
1159 #[cfg(feature = "pending-oracle-updates")]
1160 self.refresh_pending_scope();
1161 Ok(self.mutation_report([id], []))
1162 }
1163
1164 pub fn register_adapter_seeded_feed(
1177 &mut self,
1178 adapter_id: impl AsRef<str>,
1179 registration: FeedRegistration,
1180 round: RoundData,
1181 ) -> Result<OracleRuntimeMutationReport, OracleError> {
1182 let id = registration.id.clone();
1183 let state_index = self
1184 .adapter_state_index(adapter_id.as_ref())
1185 .ok_or_else(|| adapter_not_installed(adapter_id.as_ref()))?;
1186 self.tracker
1187 .insert_seeded_registration(registration.clone(), round)?;
1188 self.adapter_states[state_index]
1189 .registrations
1190 .push(registration);
1191 #[cfg(feature = "pending-oracle-updates")]
1192 self.refresh_pending_scope();
1193 Ok(self.mutation_report([id], []))
1194 }
1195
1196 pub fn unregister_feed_by_id(
1207 &mut self,
1208 id: FeedId,
1209 ) -> Result<OracleRuntimeMutationReport, OracleError> {
1210 let removed = self
1211 .tracker
1212 .remove_by_id(id)
1213 .ok_or(OracleError::FeedNotFound)?;
1214 let id = removed.id.clone();
1215 self.remove_adapter_registration(id.clone());
1216 #[cfg(feature = "pending-oracle-updates")]
1217 self.refresh_pending_scope();
1218 Ok(self.mutation_report([], [id]))
1219 }
1220
1221 pub fn unregister_feed_by_proxy(
1228 &mut self,
1229 proxy: Address,
1230 ) -> Result<OracleRuntimeMutationReport, OracleError> {
1231 let removed = self
1232 .tracker
1233 .remove_by_proxy(proxy)
1234 .ok_or(OracleError::FeedNotFound)?;
1235 let id = removed.id.clone();
1236 self.remove_adapter_registration(id.clone());
1237 #[cfg(feature = "pending-oracle-updates")]
1238 self.refresh_pending_scope();
1239 Ok(self.mutation_report([], [id]))
1240 }
1241
1242 pub fn price(&self, id: impl AsRef<str>) -> Result<OraclePrice, OracleError> {
1249 self.tracker.price(id)
1250 }
1251
1252 pub fn price_by_proxy(&self, proxy: Address) -> Result<OraclePrice, OracleError> {
1259 self.tracker.price_by_proxy(proxy)
1260 }
1261
1262 pub fn latest_round(&self, id: impl AsRef<str>) -> Result<RoundData, OracleError> {
1269 self.tracker.latest_round(id)
1270 }
1271
1272 pub fn apply_batch_report<N: Network>(
1287 &mut self,
1288 report: &ReactiveBatchReport<N>,
1289 ) -> Result<crate::OracleBatchReport, OracleError> {
1290 let mut events = Vec::new();
1291 for signal in report
1292 .applied
1293 .iter()
1294 .flat_map(|applied| applied.hook_signals.iter())
1295 {
1296 if let Some(signal) = OracleSignal::from_hook(signal)? {
1297 events.push(signal.to_event());
1298 }
1299 }
1300
1301 self.tracker.apply_batch_report(report)?;
1302 self.emit(&events);
1303 Ok(crate::OracleBatchReport::from_events(events))
1304 }
1305
1306 pub async fn reconcile_pending(&mut self) -> Result<Vec<OracleHookEvent>, OracleError>
1312 where
1313 P: ChainlinkFeedProvider,
1314 {
1315 let mut reconciler = OracleReconciler::default();
1316 for request in self
1317 .tracker
1318 .pending_reconciliations()
1319 .iter()
1320 .filter(|request| request.kind == crate::OracleReconciliationKind::Proxy)
1321 .cloned()
1322 {
1323 reconciler.enqueue(request);
1324 }
1325
1326 let mut events = Vec::new();
1327 while let Some(result) = reconciler
1328 .reconcile_next(&mut self.tracker, &self.provider)
1329 .await?
1330 {
1331 events.extend(result.hooks);
1332 }
1333 #[cfg(feature = "pending-oracle-updates")]
1334 self.refresh_pending_scope();
1335 self.emit(&events);
1336 Ok(events)
1337 }
1338
1339 pub fn reconcile_derived(
1348 &mut self,
1349 cache: &mut evm_fork_cache::cache::EvmCache,
1350 ) -> crate::DerivedReconcileReport {
1351 let report = self.tracker.reconcile_derived_pending_with(cache);
1352 let events: Vec<OracleHookEvent> = report
1353 .reconciled
1354 .iter()
1355 .flat_map(|result| result.hooks.iter().cloned())
1356 .collect();
1357 self.emit(&events);
1358 report
1359 }
1360
1361 fn emit(&self, events: &[OracleHookEvent]) {
1362 for event in events {
1363 for callback in &self.callbacks {
1364 callback(event);
1365 }
1366 }
1367 }
1368
1369 pub fn reactive_interests(&self) -> Vec<ReactiveInterest<Ethereum>> {
1371 let mut interests = Vec::new();
1372 for handler in self.reactive_handlers() {
1373 interests.extend(handler.interests());
1374 }
1375 interests
1376 }
1377
1378 pub fn storage_warmup_slots(&self) -> Vec<(Address, U256)> {
1380 self.storage_sync
1381 .warm_slots_for_registrations(self.tracker.registrations_iter())
1382 }
1383
1384 pub fn prewarm_storage(&self, cache: &mut EvmCache) -> OracleStorageWarmupReport {
1386 let registrations = self.tracker.registrations_iter().collect::<Vec<_>>();
1387 prewarm_oracle_storage(
1388 cache,
1389 &self.storage_sync,
1390 ®istrations,
1391 OracleStorageWarmupMode::default(),
1392 )
1393 }
1394
1395 pub fn cold_start_storage(&self, cache: &mut EvmCache) -> OracleStorageWarmupReport {
1397 let registrations = self.tracker.registrations_iter().collect::<Vec<_>>();
1398 prewarm_oracle_storage(
1399 cache,
1400 &self.storage_sync,
1401 ®istrations,
1402 OracleStorageWarmupMode::ColdStart,
1403 )
1404 }
1405
1406 async fn refresh_handlers_inner<S>(
1407 &self,
1408 engine: &mut ReactiveEngine<S, Ethereum>,
1409 installed: &mut OracleReactiveInstallReport,
1410 backfill: OracleHandlerRefreshBackfill,
1411 ) -> Result<OracleReactiveRefreshReport, OracleError>
1412 where
1413 S: InterestOwnerSubscriber<Ethereum>,
1414 {
1415 let previous_handler_ids = installed.handler_ids.clone();
1416 let removed_handler_ids =
1417 unregister_handler_ids(engine, &mut installed.handler_ids).await?;
1418 for handler in self.reactive_handlers() {
1419 let id = handler.id();
1420 match backfill {
1421 OracleHandlerRefreshBackfill::Continuity => engine.register_handler(handler).await,
1422 OracleHandlerRefreshBackfill::Explicit(backfill) => {
1423 engine
1424 .register_handler_with_backfill(handler, backfill)
1425 .await
1426 }
1427 OracleHandlerRefreshBackfill::LiveOnly => {
1428 engine.register_handler_live_only(handler).await
1429 }
1430 }
1431 .map_err(runtime_error)?;
1432 installed.handler_ids.push(id);
1433 }
1434 Ok(OracleReactiveRefreshReport {
1435 previous_handler_ids,
1436 removed_handler_ids,
1437 installed_handler_ids: installed.handler_ids.clone(),
1438 })
1439 }
1440
1441 fn mutation_report(
1442 &self,
1443 registered_feed_ids: impl IntoIterator<Item = FeedId>,
1444 removed_feed_ids: impl IntoIterator<Item = FeedId>,
1445 ) -> OracleRuntimeMutationReport {
1446 OracleRuntimeMutationReport {
1447 registered_feed_ids: registered_feed_ids.into_iter().collect(),
1448 removed_feed_ids: removed_feed_ids.into_iter().collect(),
1449 skipped: Vec::new(),
1450 feed_statuses: feed_readiness_from_registrations(self.tracker.registrations_iter()),
1451 current_handler_ids: self.reactive_handler_ids(),
1452 }
1453 }
1454
1455 fn adapter_state_index(&self, adapter_id: &str) -> Option<usize> {
1456 self.adapter_states
1457 .iter()
1458 .position(|state| state.adapter.adapter_id().as_str() == adapter_id)
1459 }
1460
1461 fn push_adapter_state(
1462 &mut self,
1463 adapter: Arc<dyn OracleAdapterPlugin>,
1464 registrations: Vec<FeedRegistration>,
1465 ) {
1466 if registrations.is_empty() {
1467 return;
1468 }
1469
1470 let adapter_id = adapter.adapter_id();
1471 if let Some(existing) = self
1472 .adapter_states
1473 .iter_mut()
1474 .find(|state| state.adapter.adapter_id().as_str() == adapter_id.as_str())
1475 {
1476 existing.registrations.extend(registrations);
1477 return;
1478 }
1479
1480 self.adapter_states.push(OracleAdapterRuntimeState {
1481 adapter,
1482 registrations,
1483 });
1484 }
1485
1486 fn remove_adapter_registration(&mut self, id: FeedId) {
1487 for state in &mut self.adapter_states {
1488 state
1489 .registrations
1490 .retain(|registration| registration.id != id);
1491 }
1492 }
1493
1494 #[cfg(feature = "pending-oracle-updates")]
1495 fn refresh_pending_scope(&mut self) {
1496 if let Some(pending) = &mut self.pending {
1497 pending.refresh(self.tracker.registrations_iter());
1498 }
1499 }
1500}
1501
1502impl OracleRuntime<()> {
1503 pub fn cache_builder() -> OracleCacheRuntimeBuilder {
1505 OracleCacheRuntimeBuilder::default()
1506 }
1507
1508 pub fn from_tracker(tracker: OracleTracker) -> Self {
1521 Self {
1522 provider: (),
1523 tracker,
1524 storage_sync: OracleStorageSync::chainlink_defaults(),
1525 callbacks: Vec::new(),
1526 adapter_states: Vec::new(),
1527 #[cfg(feature = "pending-oracle-updates")]
1528 pending: None,
1529 #[cfg(feature = "pending-oracle-updates")]
1530 pending_source_sessions: Vec::new(),
1531 }
1532 }
1533
1534 pub async fn register_adapter<A>(
1548 &mut self,
1549 adapter: A,
1550 cache: &mut EvmCache,
1551 ) -> Result<OracleRuntimeMutationReport, OracleError>
1552 where
1553 A: OracleAdapterPlugin + 'static,
1554 {
1555 let adapter = Arc::new(adapter);
1556 let discovery = adapter
1557 .discover(OracleDiscoveryContext {
1558 cache,
1559 now_timestamp: self.tracker.now_timestamp(),
1560 })
1561 .await?;
1562
1563 let mut registered_feed_ids: Vec<FeedId> = Vec::new();
1571 let mut registrations = Vec::new();
1572 for feed in &discovery.feeds {
1573 if let Err(error) = self
1574 .tracker
1575 .insert_seeded_registration(feed.registration.clone(), feed.round.clone())
1576 {
1577 for id in registered_feed_ids {
1578 self.tracker.remove_by_id(id);
1579 }
1580 return Err(error);
1581 }
1582 registered_feed_ids.push(feed.registration.id.clone());
1583 registrations.push(feed.registration.clone());
1584 }
1585
1586 self.push_adapter_state(adapter, registrations);
1587 #[cfg(feature = "pending-oracle-updates")]
1588 self.refresh_pending_scope();
1589 let mut feed_statuses =
1590 feed_readiness_from_registrations(self.tracker.registrations_iter());
1591 feed_statuses.extend(discovery.skipped.iter().map(skipped_feed_readiness));
1592 Ok(OracleRuntimeMutationReport {
1593 registered_feed_ids,
1594 removed_feed_ids: Vec::new(),
1595 skipped: discovery.skipped,
1596 feed_statuses,
1597 current_handler_ids: self.reactive_handler_ids(),
1598 })
1599 }
1600}
1601
1602#[derive(Clone, Copy, Debug)]
1603enum OracleHandlerRefreshBackfill {
1604 Continuity,
1605 Explicit(SubscriberBackfill),
1606 LiveOnly,
1607}
1608
1609fn runtime_error(error: impl ToString) -> OracleError {
1610 OracleError::Reactive(error.to_string())
1611}
1612
1613fn adapter_not_installed(adapter_id: &str) -> OracleError {
1614 OracleError::Config(crate::error::OracleConfigError::AdapterNotInstalled {
1615 adapter: crate::OracleAdapterId::new(adapter_id.to_string()),
1616 })
1617}
1618
1619fn feed_readiness_from_registrations<'a>(
1620 registrations: impl IntoIterator<Item = &'a FeedRegistration>,
1621) -> Vec<OracleFeedReadinessReport> {
1622 registrations
1623 .into_iter()
1624 .map(|registration| OracleFeedReadinessReport {
1625 id: Some(registration.id.clone()),
1626 proxy: registration.proxy,
1627 status: registration.status,
1628 reason: None,
1629 })
1630 .collect()
1631}
1632
1633fn skipped_feed_readiness(skipped: &OracleAdapterFeedSkip) -> OracleFeedReadinessReport {
1634 OracleFeedReadinessReport {
1635 id: skipped.feed.id(),
1636 proxy: skipped.proxy,
1637 status: OracleFeedStatus::Unsupported,
1638 reason: Some(skipped.reason.to_string()),
1639 }
1640}
1641
1642fn storage_warmup_feed_readiness(
1643 registrations: &[&FeedRegistration],
1644 failed_slots: &[OracleStorageWarmupFailure],
1645) -> Vec<OracleFeedReadinessReport> {
1646 if failed_slots.is_empty() {
1647 return feed_readiness_from_registrations(registrations.iter().copied());
1648 }
1649
1650 let failed_addresses = failed_slots
1651 .iter()
1652 .map(|failure| failure.address)
1653 .collect::<BTreeSet<_>>();
1654 registrations
1655 .iter()
1656 .map(|registration| {
1657 let failed = feed_storage_addresses(registration)
1658 .into_iter()
1659 .any(|address| failed_addresses.contains(&address));
1660 OracleFeedReadinessReport {
1661 id: Some(registration.id.clone()),
1662 proxy: registration.proxy,
1663 status: if failed {
1664 OracleFeedStatus::Degraded
1665 } else {
1666 registration.status
1667 },
1668 reason: failed
1669 .then(|| "oracle storage warmup failed for one or more feed slots".to_string()),
1670 }
1671 })
1672 .collect()
1673}
1674
1675fn feed_storage_addresses(registration: &FeedRegistration) -> Vec<Address> {
1676 let mut addresses = vec![registration.proxy];
1677 addresses.extend(registration.current_aggregator);
1678 addresses.extend(
1679 registration
1680 .source
1681 .event_aggregators(registration.current_aggregator),
1682 );
1683 addresses.sort_unstable();
1684 addresses.dedup();
1685 addresses
1686}
1687
1688async fn unregister_handler_ids<S>(
1689 engine: &mut ReactiveEngine<S, Ethereum>,
1690 handler_ids: &mut Vec<HandlerId>,
1691) -> Result<Vec<HandlerId>, OracleError>
1692where
1693 S: InterestOwnerSubscriber<Ethereum>,
1694{
1695 let mut removed = Vec::new();
1696 while let Some(id) = handler_ids.first().cloned() {
1697 if engine
1698 .unregister_handler(&id)
1699 .await
1700 .map_err(runtime_error)?
1701 .is_some()
1702 {
1703 removed.push(id);
1704 }
1705 handler_ids.remove(0);
1707 }
1708 Ok(removed)
1709}
1710
1711fn cache_runtime_skip_error(skipped: &OracleAdapterFeedSkip) -> OracleError {
1712 OracleError::FeedSkipped(Box::new(crate::OracleFeedSkip::from_adapter_skip(skipped)))
1713}
1714
1715fn prewarm_oracle_storage(
1716 cache: &mut EvmCache,
1717 storage_sync: &OracleStorageSync,
1718 registrations: &[&crate::FeedRegistration],
1719 mode: OracleStorageWarmupMode,
1720) -> OracleStorageWarmupReport {
1721 let slots = storage_sync.warm_slots_for_registrations(registrations.iter().copied());
1722 let discovery_calls = oracle_cold_start_discovery_calls(registrations);
1723 let requested_slots = slots.len();
1724 if slots.is_empty() && discovery_calls.is_empty() {
1725 return OracleStorageWarmupReport {
1726 mode,
1727 feed_statuses: feed_readiness_from_registrations(registrations.iter().copied()),
1728 ..OracleStorageWarmupReport::default()
1729 };
1730 }
1731
1732 match mode {
1733 OracleStorageWarmupMode::PrewarmSlots => {
1734 let report = cache.prewarm_slots(&slots);
1735 let failed_slots = report
1736 .failed
1737 .into_iter()
1738 .map(|(address, slot, error)| OracleStorageWarmupFailure {
1739 address,
1740 slot,
1741 reason: error.to_string(),
1742 })
1743 .collect::<Vec<_>>();
1744 let feed_statuses = storage_warmup_feed_readiness(registrations, &failed_slots);
1745 OracleStorageWarmupReport {
1746 mode,
1747 feed_statuses,
1748 requested_slots,
1749 loaded_slots: report.loaded,
1750 failed_slots,
1751 discovery_calls: 0,
1752 cold_start: None,
1753 }
1754 }
1755 OracleStorageWarmupMode::ColdStart => {
1756 let mut planner =
1757 OracleSlotColdStartPlanner::new(slots.clone(), discovery_calls.clone());
1758 match cache.run_cold_start(&mut planner, ColdStartConfig::default()) {
1759 Ok(report) => {
1760 let failed = report.failed_slots;
1761 let cold_start = OracleColdStartWarmupReport::from(report);
1762 OracleStorageWarmupReport {
1763 mode,
1764 feed_statuses: feed_readiness_from_registrations(
1765 registrations.iter().copied(),
1766 ),
1767 requested_slots,
1768 loaded_slots: requested_slots.saturating_sub(failed),
1769 failed_slots: Vec::new(),
1770 discovery_calls: cold_start.discover_calls,
1771 cold_start: Some(cold_start),
1772 }
1773 }
1774 Err(error) => {
1775 let failed_slots = slots
1776 .into_iter()
1777 .map(|(address, slot)| OracleStorageWarmupFailure {
1778 address,
1779 slot,
1780 reason: error.to_string(),
1781 })
1782 .collect::<Vec<_>>();
1783 let feed_statuses = storage_warmup_feed_readiness(registrations, &failed_slots);
1784 OracleStorageWarmupReport {
1785 mode,
1786 feed_statuses,
1787 requested_slots,
1788 loaded_slots: 0,
1789 failed_slots,
1790 discovery_calls: discovery_calls.len(),
1791 cold_start: None,
1792 }
1793 }
1794 }
1795 }
1796 }
1797}
1798
1799struct OracleSlotColdStartPlanner {
1800 slots: Vec<(Address, U256)>,
1801 slot_set: BTreeSet<(Address, U256)>,
1802 discover: Vec<ColdStartCall>,
1803 verified_discovered_slots: bool,
1804}
1805
1806impl OracleSlotColdStartPlanner {
1807 fn new(slots: Vec<(Address, U256)>, discover: Vec<ColdStartCall>) -> Self {
1808 let slot_set = slots.iter().copied().collect();
1809 Self {
1810 slots,
1811 slot_set,
1812 discover,
1813 verified_discovered_slots: false,
1814 }
1815 }
1816}
1817
1818impl ColdStartPlanner for OracleSlotColdStartPlanner {
1819 fn initial_plan(&mut self, _state: &dyn StateView) -> ColdStartPlan {
1820 ColdStartPlan {
1821 verify: self.slots.clone(),
1822 discover: self.discover.clone(),
1823 ..Default::default()
1824 }
1825 }
1826
1827 fn on_results(&mut self, results: &ColdStartResults, _state: &dyn StateView) -> ColdStartStep {
1828 if self.verified_discovered_slots {
1829 return ColdStartStep::Done;
1830 }
1831 self.verified_discovered_slots = true;
1832
1833 let mut slots = results
1834 .discovered
1835 .iter()
1836 .flat_map(|discovered| discovered.access.slots.iter().copied())
1837 .filter(|slot| !self.slot_set.contains(slot))
1838 .collect::<Vec<_>>();
1839 slots.sort_unstable();
1840 slots.dedup();
1841 if slots.is_empty() {
1842 return ColdStartStep::Done;
1843 }
1844
1845 ColdStartStep::Continue(ColdStartPlan {
1846 verify: slots,
1847 ..Default::default()
1848 })
1849 }
1850}
1851
1852impl From<evm_fork_cache::ColdStartRunReport> for OracleColdStartWarmupReport {
1853 fn from(report: evm_fork_cache::ColdStartRunReport) -> Self {
1854 Self {
1855 rounds: report.rounds,
1856 verified_slots: report.verified_slots,
1857 changed_slots: report.changed_slots,
1858 failed_slots: report.failed_slots,
1859 discovered_slots: report.discovered_slots,
1860 discovered_accounts: report.discovered_accounts,
1861 discover_calls: report
1862 .per_round
1863 .iter()
1864 .map(|round| round.discover_calls)
1865 .sum(),
1866 }
1867 }
1868}
1869
1870fn oracle_cold_start_discovery_calls(
1871 registrations: &[&crate::FeedRegistration],
1872) -> Vec<ColdStartCall> {
1873 registrations
1874 .iter()
1875 .map(|registration| {
1876 let read_proxy = registration.source.read_proxy(registration.proxy);
1877 ColdStartCall {
1878 from: Address::ZERO,
1879 to: read_proxy,
1880 calldata: Bytes::from(latestRoundDataCall {}.abi_encode()),
1881 restrict_to: Some(vec![read_proxy]),
1882 }
1883 })
1884 .collect()
1885}
1886
1887#[cfg(test)]
1888mod tests {
1889 use super::*;
1890 use evm_fork_cache::{ColdStartCallResult, StorageAccessList};
1891 use revm::context::result::{ExecutionResult, Output, SuccessReason};
1892
1893 #[derive(Default)]
1894 struct EmptyStateView;
1895
1896 impl StateView for EmptyStateView {
1897 fn storage(&self, _address: Address, _slot: U256) -> Option<U256> {
1898 None
1899 }
1900 }
1901
1902 #[test]
1903 fn cold_start_planner_verifies_discovered_slots_in_second_round() {
1904 let declared = (Address::repeat_byte(0x11), U256::from(1_u64));
1905 let discovered = (Address::repeat_byte(0x22), U256::from(2_u64));
1906 let mut planner = OracleSlotColdStartPlanner::new(vec![declared], Vec::new());
1907 let initial = planner.initial_plan(&EmptyStateView);
1908 assert_eq!(initial.verify, vec![declared]);
1909
1910 let mut access = StorageAccessList::default();
1911 access.slots.insert(discovered);
1912 access.slots.insert(declared);
1913 let results = ColdStartResults {
1914 discovered: vec![ColdStartCallResult {
1915 result: ExecutionResult::Success {
1916 reason: SuccessReason::Return,
1917 gas_used: 0,
1918 gas_refunded: 0,
1919 logs: Vec::new(),
1920 output: Output::Call(Bytes::new()),
1921 },
1922 access,
1923 }],
1924 ..Default::default()
1925 };
1926
1927 let ColdStartStep::Continue(next) = planner.on_results(&results, &EmptyStateView) else {
1928 panic!("discovered slots should schedule a follow-up verify round");
1929 };
1930 assert_eq!(next.verify, vec![discovered]);
1931 assert!(next.discover.is_empty());
1932
1933 assert!(matches!(
1934 planner.on_results(&ColdStartResults::default(), &EmptyStateView),
1935 ColdStartStep::Done
1936 ));
1937 }
1938}