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
30type OracleEventCallback = Arc<dyn Fn(&OracleHookEvent) + Send + Sync>;
31
32alloy_sol_types::sol! {
33 function latestRoundData() external view returns (
34 uint80 roundId,
35 int256 answer,
36 uint256 startedAt,
37 uint256 updatedAt,
38 uint80 answeredInRound
39 );
40}
41
42#[derive(Clone, Debug)]
44pub struct ChainlinkFeed {
45 config: FeedConfig,
46}
47
48impl ChainlinkFeed {
49 pub fn new(proxy: Address) -> Self {
51 Self {
52 config: FeedConfig {
53 proxy,
54 ..Default::default()
55 },
56 }
57 }
58
59 pub fn id(mut self, id: impl Into<String>) -> Self {
61 self.config.id = Some(FeedId::new(id));
62 self
63 }
64
65 pub fn feed_id(mut self, id: FeedId) -> Self {
67 self.config.id = Some(id);
68 self
69 }
70
71 pub fn label(mut self, label: impl Into<String>) -> Self {
73 self.config.label = Some(label.into());
74 self
75 }
76
77 pub fn base(mut self, base: impl Into<String>) -> Self {
79 self.config.base = Some(base.into());
80 self
81 }
82
83 pub fn quote(mut self, quote: impl Into<String>) -> Self {
85 self.config.quote = Some(quote.into());
86 self
87 }
88
89 pub fn max_age_secs(mut self, max_age_secs: u64) -> Self {
91 self.config.staleness = StalenessPolicy::max_age(max_age_secs);
92 self
93 }
94
95 pub fn staleness(mut self, staleness: StalenessPolicy) -> Self {
97 self.config.staleness = staleness;
98 self
99 }
100
101 pub fn allow_zero_or_negative_answer(mut self, allow: bool) -> Self {
103 self.config.staleness = self.config.staleness.allow_zero_or_negative_answer(allow);
104 self
105 }
106
107 pub fn into_config(self) -> FeedConfig {
109 self.config
110 }
111}
112
113impl From<ChainlinkFeed> for FeedConfig {
114 fn from(feed: ChainlinkFeed) -> Self {
115 feed.into_config()
116 }
117}
118
119pub struct OracleRuntimeBuilder<P> {
121 provider: P,
122 feeds: Vec<ChainlinkFeed>,
123 now_timestamp: Option<u64>,
124 storage_sync: OracleStorageSync,
125 callbacks: Vec<OracleEventCallback>,
126}
127
128pub struct OracleCacheRuntimeBuilder {
130 adapters: Vec<Arc<dyn OracleAdapterPlugin>>,
131 now_timestamp: Option<u64>,
132 storage_sync: OracleStorageSync,
133 storage_warmup_enabled: bool,
134 storage_warmup_mode: OracleStorageWarmupMode,
135 code_registry: OracleCodeRegistry,
136 code_warmup_policy: OracleCodeWarmupPolicy,
137 callbacks: Vec<OracleEventCallback>,
138}
139
140#[non_exhaustive]
142#[derive(Debug)]
143pub struct OracleCacheRuntimeBuildReport {
144 pub runtime: OracleRuntime<()>,
146 pub skipped: Vec<OracleAdapterFeedSkip>,
148 pub feed_statuses: Vec<OracleFeedReadinessReport>,
150 pub storage_warmup: OracleStorageWarmupReport,
152 pub code_warmup: OracleCodeWarmupReport,
154}
155
156#[derive(Clone, Debug, Default, PartialEq, Eq)]
158pub struct OracleStorageWarmupReport {
159 pub mode: OracleStorageWarmupMode,
161 pub feed_statuses: Vec<OracleFeedReadinessReport>,
163 pub requested_slots: usize,
165 pub loaded_slots: usize,
167 pub failed_slots: Vec<OracleStorageWarmupFailure>,
169 pub discovery_calls: usize,
171 pub cold_start: Option<OracleColdStartWarmupReport>,
173}
174
175impl OracleStorageWarmupReport {
176 pub fn is_empty(&self) -> bool {
178 self.requested_slots == 0
179 && self.loaded_slots == 0
180 && self.failed_slots.is_empty()
181 && self.discovery_calls == 0
182 && self.cold_start.is_none()
183 }
184}
185
186#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
188pub enum OracleStorageWarmupMode {
189 #[default]
191 PrewarmSlots,
192 ColdStart,
194}
195
196#[derive(Clone, Debug, Default, PartialEq, Eq)]
198pub struct OracleColdStartWarmupReport {
199 pub rounds: usize,
201 pub verified_slots: usize,
203 pub changed_slots: usize,
205 pub failed_slots: usize,
207 pub discovered_slots: usize,
209 pub discovered_accounts: usize,
211 pub discover_calls: usize,
213}
214
215#[derive(Clone, Debug, PartialEq, Eq)]
217pub struct OracleStorageWarmupFailure {
218 pub address: Address,
220 pub slot: U256,
222 pub reason: String,
224}
225
226#[derive(Clone, Debug, Default, PartialEq, Eq)]
228pub struct OracleReactiveInstallReport {
229 pub handler_ids: Vec<HandlerId>,
231}
232
233impl OracleReactiveInstallReport {
234 pub fn len(&self) -> usize {
236 self.handler_ids.len()
237 }
238
239 pub fn is_empty(&self) -> bool {
241 self.handler_ids.is_empty()
242 }
243}
244
245#[derive(Clone, Debug, Default, PartialEq, Eq)]
247pub struct OracleReactiveUninstallReport {
248 pub handler_ids: Vec<HandlerId>,
250 pub removed_handler_ids: Vec<HandlerId>,
252}
253
254#[derive(Clone, Debug, Default, PartialEq, Eq)]
256pub struct OracleReactiveRefreshReport {
257 pub previous_handler_ids: Vec<HandlerId>,
259 pub removed_handler_ids: Vec<HandlerId>,
261 pub installed_handler_ids: Vec<HandlerId>,
263}
264
265#[derive(Clone, Debug, Default)]
267pub struct OracleRuntimeMutationReport {
268 pub registered_feed_ids: Vec<FeedId>,
270 pub removed_feed_ids: Vec<FeedId>,
272 pub skipped: Vec<OracleAdapterFeedSkip>,
274 pub feed_statuses: Vec<OracleFeedReadinessReport>,
276 pub current_handler_ids: Vec<HandlerId>,
278}
279
280#[derive(Clone)]
281struct OracleAdapterRuntimeState {
282 adapter: Arc<dyn OracleAdapterPlugin>,
283 registrations: Vec<FeedRegistration>,
284}
285
286impl fmt::Debug for OracleAdapterRuntimeState {
287 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288 f.debug_struct("OracleAdapterRuntimeState")
289 .field("adapter_id", &self.adapter.adapter_id())
290 .field("registrations_len", &self.registrations.len())
291 .finish()
292 }
293}
294
295impl<P> OracleRuntimeBuilder<P> {
296 pub fn new(provider: P) -> Self {
298 Self {
299 provider,
300 feeds: Vec::new(),
301 now_timestamp: None,
302 storage_sync: OracleStorageSync::chainlink_defaults(),
303 callbacks: Vec::new(),
304 }
305 }
306
307 pub fn feed(mut self, feed: ChainlinkFeed) -> Self {
309 self.feeds.push(feed);
310 self
311 }
312
313 pub fn now_timestamp(mut self, now_timestamp: u64) -> Self {
315 self.now_timestamp = Some(now_timestamp);
316 self
317 }
318
319 pub fn storage_adapter<A>(mut self, adapter: A) -> Self
321 where
322 A: OracleStorageAdapter + 'static,
323 {
324 self.storage_sync.push_adapter(Arc::new(adapter));
325 self
326 }
327
328 pub fn storage_sync(mut self, storage_sync: OracleStorageSync) -> Self {
330 self.storage_sync = storage_sync;
331 self
332 }
333
334 pub fn on_event<F>(mut self, callback: F) -> Self
336 where
337 F: Fn(&OracleHookEvent) + Send + Sync + 'static,
338 {
339 self.callbacks.push(Arc::new(callback));
340 self
341 }
342
343 pub fn on_price_update<F>(self, callback: F) -> Self
345 where
346 F: Fn(&OraclePriceUpdate) + Send + Sync + 'static,
347 {
348 self.on_event(move |event| {
349 if let OracleHookEvent::PriceUpdate(update) = event {
350 callback(update);
351 }
352 })
353 }
354
355 pub fn on_price_corrected<F>(self, callback: F) -> Self
357 where
358 F: Fn(&OraclePriceCorrected) + Send + Sync + 'static,
359 {
360 self.on_event(move |event| {
361 if let OracleHookEvent::PriceCorrected(corrected) = event {
362 callback(corrected);
363 }
364 })
365 }
366}
367
368impl<P: ChainlinkFeedProvider> OracleRuntimeBuilder<P> {
369 pub async fn build(self) -> Result<OracleRuntime<P>, OracleError> {
378 let now_timestamp = match self.now_timestamp {
379 Some(now_timestamp) => now_timestamp,
380 None => SystemTime::now()
381 .duration_since(UNIX_EPOCH)
382 .map_err(crate::error::clock_error)?
383 .as_secs(),
384 };
385 let mut registry = OracleRegistry::new_at_timestamp(now_timestamp);
386 for feed in self.feeds {
387 registry
388 .register_chainlink_feed(&self.provider, feed.into_config())
389 .await?;
390 }
391
392 Ok(OracleRuntime {
393 provider: self.provider,
394 tracker: OracleTracker::new(registry),
395 storage_sync: self.storage_sync,
396 callbacks: self.callbacks,
397 adapter_states: Vec::new(),
398 })
399 }
400}
401
402impl Default for OracleCacheRuntimeBuilder {
403 fn default() -> Self {
404 Self {
405 adapters: Vec::new(),
406 now_timestamp: None,
407 storage_sync: OracleStorageSync::chainlink_defaults(),
408 storage_warmup_enabled: true,
409 storage_warmup_mode: OracleStorageWarmupMode::default(),
410 code_registry: OracleCodeRegistry::default(),
411 code_warmup_policy: OracleCodeWarmupPolicy::default(),
412 callbacks: Vec::new(),
413 }
414 }
415}
416
417impl OracleCacheRuntimeBuilder {
418 pub fn install_adapter<A>(mut self, adapter: A) -> Self
420 where
421 A: OracleAdapterPlugin + 'static,
422 {
423 self.adapters.push(Arc::new(adapter));
424 self
425 }
426
427 pub fn now_timestamp(mut self, now_timestamp: u64) -> Self {
429 self.now_timestamp = Some(now_timestamp);
430 self
431 }
432
433 pub fn storage_adapter<A>(mut self, adapter: A) -> Self
435 where
436 A: OracleStorageAdapter + 'static,
437 {
438 self.storage_sync.push_adapter(Arc::new(adapter));
439 self
440 }
441
442 pub fn storage_sync(mut self, storage_sync: OracleStorageSync) -> Self {
444 self.storage_sync = storage_sync;
445 self
446 }
447
448 pub fn storage_warmup(mut self, enabled: bool) -> Self {
454 self.storage_warmup_enabled = enabled;
455 self
456 }
457
458 pub fn storage_warmup_mode(mut self, mode: OracleStorageWarmupMode) -> Self {
460 self.storage_warmup_mode = mode;
461 self
462 }
463
464 pub fn storage_cold_start(self) -> Self {
466 self.storage_warmup_mode(OracleStorageWarmupMode::ColdStart)
467 }
468
469 pub fn disable_storage_warmup(self) -> Self {
471 self.storage_warmup(false)
472 }
473
474 pub fn code_registry(mut self, registry: OracleCodeRegistry) -> Self {
476 self.code_registry = registry;
477 self
478 }
479
480 pub fn code_warmup_policy(mut self, policy: OracleCodeWarmupPolicy) -> Self {
482 self.code_warmup_policy = policy;
483 self
484 }
485
486 pub fn code_seed(mut self, address: Address, code: Bytes) -> Self {
488 self.code_registry = self.code_registry.seed(address, code);
489 self
490 }
491
492 pub fn code_seeds<I>(mut self, seeds: I) -> Self
494 where
495 I: IntoIterator<Item = (Address, Bytes)>,
496 {
497 self.code_registry = self.code_registry.seed_many(seeds);
498 self
499 }
500
501 pub fn code_etch(mut self, address: Address, code: Bytes) -> Self {
503 self.code_registry = self.code_registry.etch(address, code);
504 self
505 }
506
507 pub fn code_etches<I>(mut self, etches: I) -> Self
509 where
510 I: IntoIterator<Item = (Address, Bytes)>,
511 {
512 self.code_registry = self.code_registry.etch_many(etches);
513 self
514 }
515
516 pub fn on_event<F>(mut self, callback: F) -> Self
518 where
519 F: Fn(&OracleHookEvent) + Send + Sync + 'static,
520 {
521 self.callbacks.push(Arc::new(callback));
522 self
523 }
524
525 pub fn on_price_update<F>(self, callback: F) -> Self
527 where
528 F: Fn(&OraclePriceUpdate) + Send + Sync + 'static,
529 {
530 self.on_event(move |event| {
531 if let OracleHookEvent::PriceUpdate(update) = event {
532 callback(update);
533 }
534 })
535 }
536
537 pub async fn build(self, cache: &mut EvmCache) -> Result<OracleRuntime<()>, OracleError> {
550 let report = self.build_report(cache).await?;
551 if let Some(skipped) = report.skipped.first() {
552 return Err(cache_runtime_skip_error(skipped));
553 }
554 Ok(report.runtime)
555 }
556
557 pub async fn build_report(
559 self,
560 cache: &mut EvmCache,
561 ) -> Result<OracleCacheRuntimeBuildReport, OracleError> {
562 let now_timestamp = match self.now_timestamp {
563 Some(now_timestamp) => now_timestamp,
564 None => SystemTime::now()
565 .duration_since(UNIX_EPOCH)
566 .map_err(crate::error::clock_error)?
567 .as_secs(),
568 };
569
570 let code_warmup = self
571 .code_registry
572 .apply_to_cache_with_policy(cache, self.code_warmup_policy)?;
573 let mut discovered_by_adapter = Vec::new();
574 let mut seeded = Vec::new();
575 let mut warmup_registrations = Vec::new();
576 let mut skipped = Vec::new();
577 for adapter in &self.adapters {
578 let report = adapter
579 .discover(OracleDiscoveryContext {
580 cache,
581 now_timestamp,
582 })
583 .await?;
584 skipped.extend(report.skipped);
585 let registrations = report
586 .feeds
587 .iter()
588 .map(|feed| feed.registration.clone())
589 .collect::<Vec<_>>();
590 seeded.extend(report.feeds.into_iter().map(|feed| {
591 warmup_registrations.push(feed.registration.clone());
592 (feed.registration, feed.round)
593 }));
594 discovered_by_adapter.push(OracleAdapterRuntimeState {
595 adapter: Arc::clone(adapter),
596 registrations,
597 });
598 }
599
600 let storage_warmup = if self.storage_warmup_enabled {
601 prewarm_oracle_storage(
602 cache,
603 &self.storage_sync,
604 &warmup_registrations.iter().collect::<Vec<_>>(),
605 self.storage_warmup_mode,
606 )
607 } else {
608 OracleStorageWarmupReport {
609 feed_statuses: feed_readiness_from_registrations(&warmup_registrations),
610 ..OracleStorageWarmupReport::default()
611 }
612 };
613 let mut feed_statuses = storage_warmup.feed_statuses.clone();
614 feed_statuses.extend(skipped.iter().map(skipped_feed_readiness));
615 let tracker = OracleTracker::from_registrations_at_timestamp(seeded, now_timestamp)?;
616 Ok(OracleCacheRuntimeBuildReport {
617 runtime: OracleRuntime {
618 provider: (),
619 tracker,
620 storage_sync: self.storage_sync,
621 callbacks: self.callbacks,
622 adapter_states: discovered_by_adapter,
623 },
624 skipped,
625 feed_statuses,
626 storage_warmup,
627 code_warmup,
628 })
629 }
630}
631
632pub struct OracleRuntime<P> {
634 provider: P,
635 tracker: OracleTracker,
636 storage_sync: OracleStorageSync,
637 callbacks: Vec<OracleEventCallback>,
638 adapter_states: Vec<OracleAdapterRuntimeState>,
639}
640
641impl<P: fmt::Debug> fmt::Debug for OracleRuntime<P> {
642 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
643 f.debug_struct("OracleRuntime")
644 .field("provider", &self.provider)
645 .field("tracker", &self.tracker)
646 .field("storage_sync", &self.storage_sync)
647 .field("callbacks_len", &self.callbacks.len())
648 .field("adapter_states", &self.adapter_states)
649 .finish()
650 }
651}
652
653impl<P> OracleRuntime<P> {
654 pub fn builder(provider: P) -> OracleRuntimeBuilder<P> {
656 OracleRuntimeBuilder::new(provider)
657 }
658
659 pub fn provider(&self) -> &P {
661 &self.provider
662 }
663
664 pub fn tracker(&self) -> &OracleTracker {
666 &self.tracker
667 }
668
669 pub fn tracker_mut(&mut self) -> &mut OracleTracker {
683 &mut self.tracker
684 }
685
686 pub fn feed_readiness(&self) -> Vec<OracleFeedReadinessReport> {
688 self.tracker.feed_readiness()
689 }
690
691 pub(crate) fn storage_sync(&self) -> &OracleStorageSync {
692 &self.storage_sync
693 }
694
695 pub fn with_storage_sync(mut self, storage_sync: OracleStorageSync) -> Self {
708 self.storage_sync = storage_sync;
709 self
710 }
711
712 pub fn reactive_handler(&self) -> OracleReactiveHandler {
718 OracleReactiveHandler::with_storage_sync(
719 self.tracker.registrations().collect(),
720 self.storage_sync.clone(),
721 )
722 }
723
724 pub fn reactive_handlers(&self) -> Vec<Arc<dyn ReactiveHandler<Ethereum>>> {
737 let built_in: Arc<dyn ReactiveHandler<Ethereum>> = Arc::new(self.reactive_handler());
738 let built_in_id = built_in.id();
739 let mut adapter_ids = BTreeSet::new();
740 let mut handlers = vec![built_in];
741 for state in self
742 .adapter_states
743 .iter()
744 .filter(|state| !state.registrations.is_empty())
745 {
746 let handler = state
747 .adapter
748 .reactive_handler(state.registrations.clone(), self.storage_sync.clone());
749 let id = handler.id();
750 if id == built_in_id {
751 continue;
754 }
755 if !adapter_ids.insert(id.clone()) {
756 tracing::warn!(
757 handler_id = %id,
758 adapter_id = %state.adapter.adapter_id(),
759 "duplicate reactive HandlerId from adapter plugins; \
760 dropping this adapter's handler routing"
761 );
762 continue;
763 }
764 handlers.push(handler);
765 }
766 handlers
767 }
768
769 pub fn reactive_handler_ids(&self) -> Vec<HandlerId> {
771 self.reactive_handlers()
772 .into_iter()
773 .map(|handler| handler.id())
774 .collect()
775 }
776
777 pub fn reactive_runtime(&self) -> Result<ReactiveRuntime<Ethereum>, OracleError> {
779 let mut runtime = ReactiveRuntime::<Ethereum>::new(ReactiveConfig::default());
780 for handler in self.reactive_handlers() {
781 runtime.register_handler(handler).map_err(runtime_error)?;
782 }
783 Ok(runtime)
784 }
785
786 pub fn install_handlers<S>(
793 &self,
794 engine: &mut ReactiveEngine<S, Ethereum>,
795 ) -> Result<OracleReactiveInstallReport, OracleError>
796 where
797 S: InterestOwnerSubscriber<Ethereum>,
798 {
799 let mut report = OracleReactiveInstallReport::default();
800 for handler in self.reactive_handlers() {
801 let id = handler.id();
802 engine.register_handler(handler).map_err(runtime_error)?;
803 report.handler_ids.push(id);
804 }
805 Ok(report)
806 }
807
808 pub fn refresh_handlers<S>(
814 &self,
815 engine: &mut ReactiveEngine<S, Ethereum>,
816 installed: &mut OracleReactiveInstallReport,
817 ) -> Result<OracleReactiveRefreshReport, OracleError>
818 where
819 S: InterestOwnerSubscriber<Ethereum>,
820 {
821 self.refresh_handlers_inner(engine, installed, OracleHandlerRefreshBackfill::Continuity)
822 }
823
824 pub fn refresh_handlers_with_backfill<S>(
826 &self,
827 engine: &mut ReactiveEngine<S, Ethereum>,
828 installed: &mut OracleReactiveInstallReport,
829 backfill: SubscriberBackfill,
830 ) -> Result<OracleReactiveRefreshReport, OracleError>
831 where
832 S: InterestOwnerSubscriber<Ethereum>,
833 {
834 self.refresh_handlers_inner(
835 engine,
836 installed,
837 OracleHandlerRefreshBackfill::Explicit(backfill),
838 )
839 }
840
841 pub fn refresh_handlers_live_only<S>(
843 &self,
844 engine: &mut ReactiveEngine<S, Ethereum>,
845 installed: &mut OracleReactiveInstallReport,
846 ) -> Result<OracleReactiveRefreshReport, OracleError>
847 where
848 S: InterestOwnerSubscriber<Ethereum>,
849 {
850 self.refresh_handlers_inner(engine, installed, OracleHandlerRefreshBackfill::LiveOnly)
851 }
852
853 pub fn uninstall_installed_handlers<S>(
855 &self,
856 engine: &mut ReactiveEngine<S, Ethereum>,
857 installed: &mut OracleReactiveInstallReport,
858 ) -> OracleReactiveUninstallReport
859 where
860 S: InterestOwnerSubscriber<Ethereum>,
861 {
862 let handler_ids = std::mem::take(&mut installed.handler_ids);
863 let removed_handler_ids = unregister_handler_ids(engine, &handler_ids);
864 OracleReactiveUninstallReport {
865 handler_ids,
866 removed_handler_ids,
867 }
868 }
869
870 pub fn install_handlers_with_backfill<S>(
877 &self,
878 engine: &mut ReactiveEngine<S, Ethereum>,
879 backfill: SubscriberBackfill,
880 ) -> Result<OracleReactiveInstallReport, OracleError>
881 where
882 S: InterestOwnerSubscriber<Ethereum>,
883 {
884 let mut report = OracleReactiveInstallReport::default();
885 for handler in self.reactive_handlers() {
886 let id = handler.id();
887 engine
888 .register_handler_with_backfill(handler, backfill)
889 .map_err(runtime_error)?;
890 report.handler_ids.push(id);
891 }
892 Ok(report)
893 }
894
895 pub fn install_handlers_live_only<S>(
902 &self,
903 engine: &mut ReactiveEngine<S, Ethereum>,
904 ) -> Result<OracleReactiveInstallReport, OracleError>
905 where
906 S: InterestOwnerSubscriber<Ethereum>,
907 {
908 let mut report = OracleReactiveInstallReport::default();
909 for handler in self.reactive_handlers() {
910 let id = handler.id();
911 engine
912 .register_handler_live_only(handler)
913 .map_err(runtime_error)?;
914 report.handler_ids.push(id);
915 }
916 Ok(report)
917 }
918
919 pub fn uninstall_handlers<S>(
930 &self,
931 engine: &mut ReactiveEngine<S, Ethereum>,
932 ) -> OracleReactiveUninstallReport
933 where
934 S: InterestOwnerSubscriber<Ethereum>,
935 {
936 let mut report = OracleReactiveUninstallReport::default();
937 for handler in self.reactive_handlers() {
938 let id = handler.id();
939 report.handler_ids.push(id.clone());
940 if engine.unregister_handler(&id).is_some() {
941 report.removed_handler_ids.push(id);
942 }
943 }
944 report
945 }
946
947 pub fn register_subscriber<S>(&self, subscriber: &mut S) -> Result<(), OracleError>
949 where
950 S: EventSubscriber<Ethereum>,
951 {
952 subscriber
953 .register_interests(&self.reactive_interests())
954 .map_err(runtime_error)
955 }
956
957 pub async fn next_subscriber_events<S>(
960 &mut self,
961 cache: &mut EvmCache,
962 runtime: &mut ReactiveRuntime<Ethereum>,
963 subscriber: &mut S,
964 ) -> Result<Option<crate::OracleBatchReport>, OracleError>
965 where
966 S: EventSubscriber<Ethereum>,
967 {
968 let Some(batch) = subscriber.next_batch().await.map_err(runtime_error)? else {
969 return Ok(None);
970 };
971 let report = runtime.ingest_batch(cache, batch).map_err(runtime_error)?;
972 self.apply_batch_report(&report).map(Some)
973 }
974
975 pub fn read_overlay(&self) -> OracleReadOverlay<'_> {
977 OracleReadOverlay::new(&self.tracker)
978 }
979
980 pub fn register_seeded_feed(
993 &mut self,
994 registration: FeedRegistration,
995 round: RoundData,
996 ) -> Result<OracleRuntimeMutationReport, OracleError> {
997 let id = registration.id.clone();
998 self.tracker
999 .insert_seeded_registration(registration, round)?;
1000 Ok(self.mutation_report([id], []))
1001 }
1002
1003 pub fn register_adapter_seeded_feed(
1016 &mut self,
1017 adapter_id: impl AsRef<str>,
1018 registration: FeedRegistration,
1019 round: RoundData,
1020 ) -> Result<OracleRuntimeMutationReport, OracleError> {
1021 let id = registration.id.clone();
1022 let state_index = self
1023 .adapter_state_index(adapter_id.as_ref())
1024 .ok_or_else(|| adapter_not_installed(adapter_id.as_ref()))?;
1025 self.tracker
1026 .insert_seeded_registration(registration.clone(), round)?;
1027 self.adapter_states[state_index]
1028 .registrations
1029 .push(registration);
1030 Ok(self.mutation_report([id], []))
1031 }
1032
1033 pub fn unregister_feed_by_id(
1044 &mut self,
1045 id: FeedId,
1046 ) -> Result<OracleRuntimeMutationReport, OracleError> {
1047 let removed = self
1048 .tracker
1049 .remove_by_id(id)
1050 .ok_or(OracleError::FeedNotFound)?;
1051 let id = removed.id.clone();
1052 self.remove_adapter_registration(id.clone());
1053 Ok(self.mutation_report([], [id]))
1054 }
1055
1056 pub fn unregister_feed_by_proxy(
1063 &mut self,
1064 proxy: Address,
1065 ) -> Result<OracleRuntimeMutationReport, OracleError> {
1066 let removed = self
1067 .tracker
1068 .remove_by_proxy(proxy)
1069 .ok_or(OracleError::FeedNotFound)?;
1070 let id = removed.id.clone();
1071 self.remove_adapter_registration(id.clone());
1072 Ok(self.mutation_report([], [id]))
1073 }
1074
1075 pub fn price(&self, id: impl AsRef<str>) -> Result<OraclePrice, OracleError> {
1082 self.tracker.price(id)
1083 }
1084
1085 pub fn price_by_proxy(&self, proxy: Address) -> Result<OraclePrice, OracleError> {
1092 self.tracker.price_by_proxy(proxy)
1093 }
1094
1095 pub fn latest_round(&self, id: impl AsRef<str>) -> Result<RoundData, OracleError> {
1102 self.tracker.latest_round(id)
1103 }
1104
1105 pub fn apply_batch_report<N: Network>(
1120 &mut self,
1121 report: &ReactiveBatchReport<N>,
1122 ) -> Result<crate::OracleBatchReport, OracleError> {
1123 let mut events = Vec::new();
1124 for signal in report
1125 .applied
1126 .iter()
1127 .flat_map(|applied| applied.hook_signals.iter())
1128 {
1129 if let Some(signal) = OracleSignal::from_hook(signal)? {
1130 events.push(signal.to_event());
1131 }
1132 }
1133
1134 self.tracker.apply_batch_report(report)?;
1135 self.emit(&events);
1136 Ok(crate::OracleBatchReport::from_events(events))
1137 }
1138
1139 pub async fn reconcile_pending(&mut self) -> Result<Vec<OracleHookEvent>, OracleError>
1145 where
1146 P: ChainlinkFeedProvider,
1147 {
1148 let mut reconciler = OracleReconciler::default();
1149 for request in self
1150 .tracker
1151 .pending_reconciliations()
1152 .iter()
1153 .filter(|request| request.kind == crate::OracleReconciliationKind::Proxy)
1154 .cloned()
1155 {
1156 reconciler.enqueue(request);
1157 }
1158
1159 let mut events = Vec::new();
1160 while let Some(result) = reconciler
1161 .reconcile_next(&mut self.tracker, &self.provider)
1162 .await?
1163 {
1164 events.extend(result.hooks);
1165 }
1166 self.emit(&events);
1167 Ok(events)
1168 }
1169
1170 pub fn reconcile_derived(
1179 &mut self,
1180 cache: &mut evm_fork_cache::cache::EvmCache,
1181 ) -> crate::DerivedReconcileReport {
1182 let report = self.tracker.reconcile_derived_pending_with(cache);
1183 let events: Vec<OracleHookEvent> = report
1184 .reconciled
1185 .iter()
1186 .flat_map(|result| result.hooks.iter().cloned())
1187 .collect();
1188 self.emit(&events);
1189 report
1190 }
1191
1192 fn emit(&self, events: &[OracleHookEvent]) {
1193 for event in events {
1194 for callback in &self.callbacks {
1195 callback(event);
1196 }
1197 }
1198 }
1199
1200 pub fn reactive_interests(&self) -> Vec<ReactiveInterest<Ethereum>> {
1202 let mut interests = Vec::new();
1203 for handler in self.reactive_handlers() {
1204 interests.extend(handler.interests());
1205 }
1206 interests
1207 }
1208
1209 pub fn storage_warmup_slots(&self) -> Vec<(Address, U256)> {
1211 self.storage_sync
1212 .warm_slots_for_registrations(self.tracker.registrations_iter())
1213 }
1214
1215 pub fn prewarm_storage(&self, cache: &mut EvmCache) -> OracleStorageWarmupReport {
1217 let registrations = self.tracker.registrations_iter().collect::<Vec<_>>();
1218 prewarm_oracle_storage(
1219 cache,
1220 &self.storage_sync,
1221 ®istrations,
1222 OracleStorageWarmupMode::default(),
1223 )
1224 }
1225
1226 pub fn cold_start_storage(&self, cache: &mut EvmCache) -> OracleStorageWarmupReport {
1228 let registrations = self.tracker.registrations_iter().collect::<Vec<_>>();
1229 prewarm_oracle_storage(
1230 cache,
1231 &self.storage_sync,
1232 ®istrations,
1233 OracleStorageWarmupMode::ColdStart,
1234 )
1235 }
1236
1237 fn refresh_handlers_inner<S>(
1238 &self,
1239 engine: &mut ReactiveEngine<S, Ethereum>,
1240 installed: &mut OracleReactiveInstallReport,
1241 backfill: OracleHandlerRefreshBackfill,
1242 ) -> Result<OracleReactiveRefreshReport, OracleError>
1243 where
1244 S: InterestOwnerSubscriber<Ethereum>,
1245 {
1246 let previous_handler_ids = std::mem::take(&mut installed.handler_ids);
1247 let removed_handler_ids = unregister_handler_ids(engine, &previous_handler_ids);
1248 let new_install = match backfill {
1249 OracleHandlerRefreshBackfill::Continuity => self.install_handlers(engine)?,
1250 OracleHandlerRefreshBackfill::Explicit(backfill) => {
1251 self.install_handlers_with_backfill(engine, backfill)?
1252 }
1253 OracleHandlerRefreshBackfill::LiveOnly => self.install_handlers_live_only(engine)?,
1254 };
1255 installed.handler_ids = new_install.handler_ids.clone();
1256 Ok(OracleReactiveRefreshReport {
1257 previous_handler_ids,
1258 removed_handler_ids,
1259 installed_handler_ids: installed.handler_ids.clone(),
1260 })
1261 }
1262
1263 fn mutation_report(
1264 &self,
1265 registered_feed_ids: impl IntoIterator<Item = FeedId>,
1266 removed_feed_ids: impl IntoIterator<Item = FeedId>,
1267 ) -> OracleRuntimeMutationReport {
1268 OracleRuntimeMutationReport {
1269 registered_feed_ids: registered_feed_ids.into_iter().collect(),
1270 removed_feed_ids: removed_feed_ids.into_iter().collect(),
1271 skipped: Vec::new(),
1272 feed_statuses: feed_readiness_from_registrations(self.tracker.registrations_iter()),
1273 current_handler_ids: self.reactive_handler_ids(),
1274 }
1275 }
1276
1277 fn adapter_state_index(&self, adapter_id: &str) -> Option<usize> {
1278 self.adapter_states
1279 .iter()
1280 .position(|state| state.adapter.adapter_id().as_str() == adapter_id)
1281 }
1282
1283 fn push_adapter_state(
1284 &mut self,
1285 adapter: Arc<dyn OracleAdapterPlugin>,
1286 registrations: Vec<FeedRegistration>,
1287 ) {
1288 if registrations.is_empty() {
1289 return;
1290 }
1291
1292 let adapter_id = adapter.adapter_id();
1293 if let Some(existing) = self
1294 .adapter_states
1295 .iter_mut()
1296 .find(|state| state.adapter.adapter_id().as_str() == adapter_id.as_str())
1297 {
1298 existing.registrations.extend(registrations);
1299 return;
1300 }
1301
1302 self.adapter_states.push(OracleAdapterRuntimeState {
1303 adapter,
1304 registrations,
1305 });
1306 }
1307
1308 fn remove_adapter_registration(&mut self, id: FeedId) {
1309 for state in &mut self.adapter_states {
1310 state
1311 .registrations
1312 .retain(|registration| registration.id != id);
1313 }
1314 }
1315}
1316
1317impl OracleRuntime<()> {
1318 pub fn cache_builder() -> OracleCacheRuntimeBuilder {
1320 OracleCacheRuntimeBuilder::default()
1321 }
1322
1323 pub fn from_tracker(tracker: OracleTracker) -> Self {
1336 Self {
1337 provider: (),
1338 tracker,
1339 storage_sync: OracleStorageSync::chainlink_defaults(),
1340 callbacks: Vec::new(),
1341 adapter_states: Vec::new(),
1342 }
1343 }
1344
1345 pub async fn register_adapter<A>(
1359 &mut self,
1360 adapter: A,
1361 cache: &mut EvmCache,
1362 ) -> Result<OracleRuntimeMutationReport, OracleError>
1363 where
1364 A: OracleAdapterPlugin + 'static,
1365 {
1366 let adapter = Arc::new(adapter);
1367 let discovery = adapter
1368 .discover(OracleDiscoveryContext {
1369 cache,
1370 now_timestamp: self.tracker.now_timestamp(),
1371 })
1372 .await?;
1373
1374 let mut registered_feed_ids: Vec<FeedId> = Vec::new();
1382 let mut registrations = Vec::new();
1383 for feed in &discovery.feeds {
1384 if let Err(error) = self
1385 .tracker
1386 .insert_seeded_registration(feed.registration.clone(), feed.round.clone())
1387 {
1388 for id in registered_feed_ids {
1389 self.tracker.remove_by_id(id);
1390 }
1391 return Err(error);
1392 }
1393 registered_feed_ids.push(feed.registration.id.clone());
1394 registrations.push(feed.registration.clone());
1395 }
1396
1397 self.push_adapter_state(adapter, registrations);
1398 let mut feed_statuses =
1399 feed_readiness_from_registrations(self.tracker.registrations_iter());
1400 feed_statuses.extend(discovery.skipped.iter().map(skipped_feed_readiness));
1401 Ok(OracleRuntimeMutationReport {
1402 registered_feed_ids,
1403 removed_feed_ids: Vec::new(),
1404 skipped: discovery.skipped,
1405 feed_statuses,
1406 current_handler_ids: self.reactive_handler_ids(),
1407 })
1408 }
1409}
1410
1411#[derive(Clone, Copy, Debug)]
1412enum OracleHandlerRefreshBackfill {
1413 Continuity,
1414 Explicit(SubscriberBackfill),
1415 LiveOnly,
1416}
1417
1418fn runtime_error(error: impl ToString) -> OracleError {
1419 OracleError::Reactive(error.to_string())
1420}
1421
1422fn adapter_not_installed(adapter_id: &str) -> OracleError {
1423 OracleError::Config(crate::error::OracleConfigError::AdapterNotInstalled {
1424 adapter: crate::OracleAdapterId::new(adapter_id.to_string()),
1425 })
1426}
1427
1428fn feed_readiness_from_registrations<'a>(
1429 registrations: impl IntoIterator<Item = &'a FeedRegistration>,
1430) -> Vec<OracleFeedReadinessReport> {
1431 registrations
1432 .into_iter()
1433 .map(|registration| OracleFeedReadinessReport {
1434 id: Some(registration.id.clone()),
1435 proxy: registration.proxy,
1436 status: registration.status,
1437 reason: None,
1438 })
1439 .collect()
1440}
1441
1442fn skipped_feed_readiness(skipped: &OracleAdapterFeedSkip) -> OracleFeedReadinessReport {
1443 OracleFeedReadinessReport {
1444 id: skipped.feed.id(),
1445 proxy: skipped.proxy,
1446 status: OracleFeedStatus::Unsupported,
1447 reason: Some(skipped.reason.to_string()),
1448 }
1449}
1450
1451fn storage_warmup_feed_readiness(
1452 registrations: &[&FeedRegistration],
1453 failed_slots: &[OracleStorageWarmupFailure],
1454) -> Vec<OracleFeedReadinessReport> {
1455 if failed_slots.is_empty() {
1456 return feed_readiness_from_registrations(registrations.iter().copied());
1457 }
1458
1459 let failed_addresses = failed_slots
1460 .iter()
1461 .map(|failure| failure.address)
1462 .collect::<BTreeSet<_>>();
1463 registrations
1464 .iter()
1465 .map(|registration| {
1466 let failed = feed_storage_addresses(registration)
1467 .into_iter()
1468 .any(|address| failed_addresses.contains(&address));
1469 OracleFeedReadinessReport {
1470 id: Some(registration.id.clone()),
1471 proxy: registration.proxy,
1472 status: if failed {
1473 OracleFeedStatus::Degraded
1474 } else {
1475 registration.status
1476 },
1477 reason: failed
1478 .then(|| "oracle storage warmup failed for one or more feed slots".to_string()),
1479 }
1480 })
1481 .collect()
1482}
1483
1484fn feed_storage_addresses(registration: &FeedRegistration) -> Vec<Address> {
1485 let mut addresses = vec![registration.proxy];
1486 addresses.extend(registration.current_aggregator);
1487 addresses.extend(
1488 registration
1489 .source
1490 .event_aggregators(registration.current_aggregator),
1491 );
1492 addresses.sort_unstable();
1493 addresses.dedup();
1494 addresses
1495}
1496
1497fn unregister_handler_ids<S>(
1498 engine: &mut ReactiveEngine<S, Ethereum>,
1499 handler_ids: &[HandlerId],
1500) -> Vec<HandlerId>
1501where
1502 S: InterestOwnerSubscriber<Ethereum>,
1503{
1504 let mut removed = Vec::new();
1505 for id in handler_ids {
1506 if engine.unregister_handler(id).is_some() {
1507 removed.push(id.clone());
1508 }
1509 }
1510 removed
1511}
1512
1513fn cache_runtime_skip_error(skipped: &OracleAdapterFeedSkip) -> OracleError {
1514 OracleError::FeedSkipped(Box::new(crate::OracleFeedSkip::from_adapter_skip(skipped)))
1515}
1516
1517fn prewarm_oracle_storage(
1518 cache: &mut EvmCache,
1519 storage_sync: &OracleStorageSync,
1520 registrations: &[&crate::FeedRegistration],
1521 mode: OracleStorageWarmupMode,
1522) -> OracleStorageWarmupReport {
1523 let slots = storage_sync.warm_slots_for_registrations(registrations.iter().copied());
1524 let discovery_calls = oracle_cold_start_discovery_calls(registrations);
1525 let requested_slots = slots.len();
1526 if slots.is_empty() && discovery_calls.is_empty() {
1527 return OracleStorageWarmupReport {
1528 mode,
1529 feed_statuses: feed_readiness_from_registrations(registrations.iter().copied()),
1530 ..OracleStorageWarmupReport::default()
1531 };
1532 }
1533
1534 match mode {
1535 OracleStorageWarmupMode::PrewarmSlots => {
1536 let report = cache.prewarm_slots(&slots);
1537 let failed_slots = report
1538 .failed
1539 .into_iter()
1540 .map(|(address, slot, error)| OracleStorageWarmupFailure {
1541 address,
1542 slot,
1543 reason: error.to_string(),
1544 })
1545 .collect::<Vec<_>>();
1546 let feed_statuses = storage_warmup_feed_readiness(registrations, &failed_slots);
1547 OracleStorageWarmupReport {
1548 mode,
1549 feed_statuses,
1550 requested_slots,
1551 loaded_slots: report.loaded,
1552 failed_slots,
1553 discovery_calls: 0,
1554 cold_start: None,
1555 }
1556 }
1557 OracleStorageWarmupMode::ColdStart => {
1558 let mut planner =
1559 OracleSlotColdStartPlanner::new(slots.clone(), discovery_calls.clone());
1560 match cache.run_cold_start(&mut planner, ColdStartConfig::default()) {
1561 Ok(report) => {
1562 let failed = report.failed_slots;
1563 let cold_start = OracleColdStartWarmupReport::from(report);
1564 OracleStorageWarmupReport {
1565 mode,
1566 feed_statuses: feed_readiness_from_registrations(
1567 registrations.iter().copied(),
1568 ),
1569 requested_slots,
1570 loaded_slots: requested_slots.saturating_sub(failed),
1571 failed_slots: Vec::new(),
1572 discovery_calls: cold_start.discover_calls,
1573 cold_start: Some(cold_start),
1574 }
1575 }
1576 Err(error) => {
1577 let failed_slots = slots
1578 .into_iter()
1579 .map(|(address, slot)| OracleStorageWarmupFailure {
1580 address,
1581 slot,
1582 reason: error.to_string(),
1583 })
1584 .collect::<Vec<_>>();
1585 let feed_statuses = storage_warmup_feed_readiness(registrations, &failed_slots);
1586 OracleStorageWarmupReport {
1587 mode,
1588 feed_statuses,
1589 requested_slots,
1590 loaded_slots: 0,
1591 failed_slots,
1592 discovery_calls: discovery_calls.len(),
1593 cold_start: None,
1594 }
1595 }
1596 }
1597 }
1598 }
1599}
1600
1601struct OracleSlotColdStartPlanner {
1602 slots: Vec<(Address, U256)>,
1603 slot_set: BTreeSet<(Address, U256)>,
1604 discover: Vec<ColdStartCall>,
1605 verified_discovered_slots: bool,
1606}
1607
1608impl OracleSlotColdStartPlanner {
1609 fn new(slots: Vec<(Address, U256)>, discover: Vec<ColdStartCall>) -> Self {
1610 let slot_set = slots.iter().copied().collect();
1611 Self {
1612 slots,
1613 slot_set,
1614 discover,
1615 verified_discovered_slots: false,
1616 }
1617 }
1618}
1619
1620impl ColdStartPlanner for OracleSlotColdStartPlanner {
1621 fn initial_plan(&mut self, _state: &dyn StateView) -> ColdStartPlan {
1622 ColdStartPlan {
1623 verify: self.slots.clone(),
1624 discover: self.discover.clone(),
1625 ..Default::default()
1626 }
1627 }
1628
1629 fn on_results(&mut self, results: &ColdStartResults, _state: &dyn StateView) -> ColdStartStep {
1630 if self.verified_discovered_slots {
1631 return ColdStartStep::Done;
1632 }
1633 self.verified_discovered_slots = true;
1634
1635 let mut slots = results
1636 .discovered
1637 .iter()
1638 .flat_map(|discovered| discovered.access.slots.iter().copied())
1639 .filter(|slot| !self.slot_set.contains(slot))
1640 .collect::<Vec<_>>();
1641 slots.sort_unstable();
1642 slots.dedup();
1643 if slots.is_empty() {
1644 return ColdStartStep::Done;
1645 }
1646
1647 ColdStartStep::Continue(ColdStartPlan {
1648 verify: slots,
1649 ..Default::default()
1650 })
1651 }
1652}
1653
1654impl From<evm_fork_cache::ColdStartRunReport> for OracleColdStartWarmupReport {
1655 fn from(report: evm_fork_cache::ColdStartRunReport) -> Self {
1656 Self {
1657 rounds: report.rounds,
1658 verified_slots: report.verified_slots,
1659 changed_slots: report.changed_slots,
1660 failed_slots: report.failed_slots,
1661 discovered_slots: report.discovered_slots,
1662 discovered_accounts: report.discovered_accounts,
1663 discover_calls: report
1664 .per_round
1665 .iter()
1666 .map(|round| round.discover_calls)
1667 .sum(),
1668 }
1669 }
1670}
1671
1672fn oracle_cold_start_discovery_calls(
1673 registrations: &[&crate::FeedRegistration],
1674) -> Vec<ColdStartCall> {
1675 registrations
1676 .iter()
1677 .map(|registration| {
1678 let read_proxy = registration.source.read_proxy(registration.proxy);
1679 ColdStartCall {
1680 from: Address::ZERO,
1681 to: read_proxy,
1682 calldata: Bytes::from(latestRoundDataCall {}.abi_encode()),
1683 restrict_to: Some(vec![read_proxy]),
1684 }
1685 })
1686 .collect()
1687}
1688
1689#[cfg(test)]
1690mod tests {
1691 use super::*;
1692 use evm_fork_cache::{ColdStartCallResult, StorageAccessList};
1693 use revm::context::result::{ExecutionResult, Output, SuccessReason};
1694
1695 #[derive(Default)]
1696 struct EmptyStateView;
1697
1698 impl StateView for EmptyStateView {
1699 fn storage(&self, _address: Address, _slot: U256) -> Option<U256> {
1700 None
1701 }
1702 }
1703
1704 #[test]
1705 fn cold_start_planner_verifies_discovered_slots_in_second_round() {
1706 let declared = (Address::repeat_byte(0x11), U256::from(1_u64));
1707 let discovered = (Address::repeat_byte(0x22), U256::from(2_u64));
1708 let mut planner = OracleSlotColdStartPlanner::new(vec![declared], Vec::new());
1709 let initial = planner.initial_plan(&EmptyStateView);
1710 assert_eq!(initial.verify, vec![declared]);
1711
1712 let mut access = StorageAccessList::default();
1713 access.slots.insert(discovered);
1714 access.slots.insert(declared);
1715 let results = ColdStartResults {
1716 discovered: vec![ColdStartCallResult {
1717 result: ExecutionResult::Success {
1718 reason: SuccessReason::Return,
1719 gas_used: 0,
1720 gas_refunded: 0,
1721 logs: Vec::new(),
1722 output: Output::Call(Bytes::new()),
1723 },
1724 access,
1725 }],
1726 ..Default::default()
1727 };
1728
1729 let ColdStartStep::Continue(next) = planner.on_results(&results, &EmptyStateView) else {
1730 panic!("discovered slots should schedule a follow-up verify round");
1731 };
1732 assert_eq!(next.verify, vec![discovered]);
1733 assert!(next.discover.is_empty());
1734
1735 assert!(matches!(
1736 planner.on_results(&ColdStartResults::default(), &EmptyStateView),
1737 ColdStartStep::Done
1738 ));
1739 }
1740}