1use std::{
2 cell::RefCell,
3 collections::{BTreeMap, BTreeSet},
4 time::{SystemTime, UNIX_EPOCH},
5};
6
7#[cfg(feature = "aave")]
8use alloy_primitives::I256;
9use alloy_primitives::{Address, Bytes, U256};
10use alloy_sol_types::{SolCall, sol};
11use evm_fork_cache::{
12 cache::EvmCache,
13 multicall::{IMulticall3, MULTICALL3_ADDRESS, execute_batched, try_decode_result},
14};
15
16use crate::{
17 AggregatorLayoutEvidence, ChainlinkFeedProvider, Feed, FeedConfig, FeedId, FeedMetadata,
18 FeedRegistration, FeedSource, OracleError, OracleFeedStatus, OracleRegistry, OracleTracker,
19 ProviderFuture, RoundData, registry::derive_feed_id,
20};
21
22sol! {
23 interface AggregatorProxyInterface {
24 function latestRoundData() external view returns (
25 uint80 roundId,
26 int256 answer,
27 uint256 startedAt,
28 uint256 updatedAt,
29 uint80 answeredInRound
30 );
31 function decimals() external view returns (uint8);
32 function description() external view returns (string);
33 function version() external view returns (uint256);
34 function aggregator() external view returns (address);
35 function typeAndVersion() external view returns (string);
36 }
37}
38
39use AggregatorProxyInterface::{
40 aggregatorCall, decimalsCall, descriptionCall, latestRoundDataCall, typeAndVersionCall,
41 versionCall,
42};
43
44#[derive(Debug)]
45struct ChainlinkCoreRead {
46 decimals: u8,
47 description: String,
48 version: U256,
49 round: RoundData,
50 aggregator: Option<Address>,
51}
52
53#[cfg(feature = "aave")]
54sol! {
55 interface AaveOracleInterface {
56 function getSourceOfAsset(address asset) external view returns (address);
57 }
58
59 interface AavePriceCapAdapterStableInterface {
60 function ASSET_TO_USD_AGGREGATOR() external view returns (address);
61 function getPriceCap() external view returns (int256);
62 function decimals() external view returns (uint8);
63 function description() external view returns (string);
64 function latestAnswer() external view returns (int256);
65 }
66
67 interface AaveRatioCapAdapterInterface {
68 function BASE_TO_USD_AGGREGATOR() external view returns (address);
69 function RATIO_PROVIDER() external view returns (address);
70 function RATIO_DECIMALS() external view returns (uint8);
71 function getRatio() external view returns (int256);
72 function isCapped() external view returns (bool);
73 function decimals() external view returns (uint8);
74 function description() external view returns (string);
75 function latestAnswer() external view returns (int256);
76 }
77
78 interface AaveSynchronicityPegToBaseInterface {
79 function ASSET_TO_PEG() external view returns (address);
80 function PEG_TO_BASE() external view returns (address);
81 function decimals() external view returns (uint8);
82 function description() external view returns (string);
83 function latestAnswer() external view returns (int256);
84 }
85
86 interface AaveFixedPriceSourceInterface {
87 function price() external view returns (int256);
88 function decimals() external view returns (uint8);
89 function description() external view returns (string);
90 function latestAnswer() external view returns (int256);
91 }
92
93 interface AaveConstantPriceSourceInterface {
94 function PRICE() external view returns (int256);
95 }
96
97 interface AaveBaseToPegProbeInterface {
98 function BASE_TO_PEG() external view returns (address);
99 }
100
101 interface AaveDynamicSourceProbeInterface {
102 function REFERENCE_FEED() external view returns (address);
103 function DISCOUNT_RATE() external view returns (uint256);
104 function discount() external view returns (uint256);
105 function EXCHANGE_RATE() external view returns (uint256);
106 function PENDLE_PRINCIPAL_TOKEN() external view returns (address);
107 function PENDLE_ORACLE() external view returns (address);
108 }
109}
110
111#[cfg(feature = "aave")]
112use AaveBaseToPegProbeInterface::BASE_TO_PEGCall;
113#[cfg(feature = "aave")]
114use AaveConstantPriceSourceInterface::PRICECall;
115#[cfg(feature = "aave")]
116use AaveDynamicSourceProbeInterface::{
117 DISCOUNT_RATECall, EXCHANGE_RATECall, PENDLE_ORACLECall, PENDLE_PRINCIPAL_TOKENCall,
118 REFERENCE_FEEDCall, discountCall,
119};
120#[cfg(feature = "aave")]
121use AaveFixedPriceSourceInterface::{
122 decimalsCall as aaveFixedDecimalsCall, descriptionCall as aaveFixedDescriptionCall,
123 latestAnswerCall as aaveFixedLatestAnswerCall, priceCall as aaveFixedPriceCall,
124};
125#[cfg(feature = "aave")]
126use AaveOracleInterface::getSourceOfAssetCall;
127#[cfg(feature = "aave")]
128use AavePriceCapAdapterStableInterface::{
129 ASSET_TO_USD_AGGREGATORCall, decimalsCall as aaveDecimalsCall,
130 descriptionCall as aaveDescriptionCall, getPriceCapCall, latestAnswerCall,
131};
132#[cfg(feature = "aave")]
133use AaveRatioCapAdapterInterface::{
134 BASE_TO_USD_AGGREGATORCall, RATIO_DECIMALSCall, RATIO_PROVIDERCall,
135 decimalsCall as aaveRatioDecimalsCall, descriptionCall as aaveRatioDescriptionCall,
136 getRatioCall, isCappedCall, latestAnswerCall as aaveRatioLatestAnswerCall,
137};
138#[cfg(feature = "aave")]
139use AaveSynchronicityPegToBaseInterface::{
140 ASSET_TO_PEGCall, PEG_TO_BASECall, decimalsCall as aaveSynchronicityDecimalsCall,
141 descriptionCall as aaveSynchronicityDescriptionCall,
142 latestAnswerCall as aaveSynchronicityLatestAnswerCall,
143};
144
145#[cfg(feature = "aave")]
146#[derive(Clone, Debug, PartialEq, Eq)]
147pub(crate) struct AavePriceCapStableSource {
148 pub(crate) underlying_proxy: Address,
149 pub(crate) price_cap: I256,
150 pub(crate) decimals: u8,
151 pub(crate) description: String,
152 pub(crate) latest_answer: I256,
153}
154
155#[cfg(feature = "aave")]
156#[derive(Clone, Debug, PartialEq, Eq)]
157pub(crate) struct AaveRatioCapSource {
158 pub(crate) base_to_usd_proxy: Address,
159 pub(crate) ratio_provider: Address,
160 pub(crate) current_ratio: I256,
161 pub(crate) is_capped: bool,
162 pub(crate) ratio_decimals: u8,
163 pub(crate) decimals: u8,
164 pub(crate) description: String,
165 pub(crate) latest_answer: I256,
166}
167
168#[cfg(feature = "aave")]
169#[derive(Clone, Debug, PartialEq, Eq)]
170pub(crate) struct AaveSynchronicityPegToBaseSource {
171 pub(crate) asset_to_peg_proxy: Address,
172 pub(crate) peg_to_base_proxy: Address,
173 pub(crate) decimals: u8,
174 pub(crate) description: String,
175 pub(crate) latest_answer: I256,
176}
177
178#[cfg(feature = "aave")]
179#[derive(Clone, Debug, PartialEq, Eq)]
180pub(crate) struct AaveFixedPriceSource {
181 pub(crate) decimals: u8,
182 pub(crate) description: String,
183 pub(crate) latest_answer: I256,
184}
185
186pub struct EvmCacheChainlinkReader<'a> {
188 cache: RefCell<&'a mut EvmCache>,
189 multicall_reads: bool,
190}
191
192impl<'a> EvmCacheChainlinkReader<'a> {
193 pub fn new(cache: &'a mut EvmCache) -> Self {
195 Self {
196 cache: RefCell::new(cache),
197 multicall_reads: true,
198 }
199 }
200
201 pub fn without_multicall(cache: &'a mut EvmCache) -> Self {
203 Self {
204 cache: RefCell::new(cache),
205 multicall_reads: false,
206 }
207 }
208
209 pub fn with_multicall_reads(mut self, enabled: bool) -> Self {
211 self.multicall_reads = enabled;
212 self
213 }
214
215 pub async fn register_feed(
217 &self,
218 registry: &mut OracleRegistry,
219 feed: Feed,
220 ) -> Result<FeedId, OracleError> {
221 let config = FeedConfig::try_from(feed)?;
222 self.register_config(registry, config).await
223 }
224
225 pub async fn register_feeds(
227 &self,
228 registry: &mut OracleRegistry,
229 feeds: Vec<Feed>,
230 ) -> Result<Vec<OracleAdapterFeedSkip>, OracleError> {
231 let mut prepared = Vec::with_capacity(feeds.len());
232 for feed in feeds {
233 let proxy = feed.proxy().unwrap_or_default();
234 let config = FeedConfig::try_from(feed.clone())?;
235 let read_proxy = FeedSource::Chainlink.read_proxy(config.proxy);
236 prepared.push((feed, config, proxy, read_proxy));
237 }
238
239 let read_proxies = prepared
240 .iter()
241 .map(|(_, _, _, read_proxy)| *read_proxy)
242 .collect::<Vec<_>>();
243 let mut cores = self
244 .read_chainlink_cores_multicall(&read_proxies)
245 .unwrap_or_default();
246 let mut layouts = self.layout_evidence_for_cores(&cores);
247
248 let mut skipped = Vec::new();
249 for (index, (feed, config, proxy, read_proxy)) in prepared.into_iter().enumerate() {
250 let mut from_batched_core = false;
251 let core = match cores.get_mut(index).and_then(Option::take) {
252 Some(Ok(core)) => {
253 from_batched_core = true;
254 Ok(core)
255 }
256 Some(Err(error)) => Err(error),
257 None => self.read_chainlink_core(read_proxy),
258 };
259
260 match core {
261 Ok(core) => {
262 if from_batched_core {
263 if let Some(layouts) = layouts.as_mut() {
264 let layout = layouts.get_mut(index).and_then(Option::take);
265 self.insert_config_core_with_layout(registry, config, core, layout)?;
266 } else {
267 self.insert_config_core(registry, config, core).await?;
268 }
269 } else {
270 self.insert_config_core(registry, config, core).await?;
271 }
272 }
273 Err(OracleError::Provider(error)) => {
274 skipped.push(OracleAdapterFeedSkip {
275 feed,
276 proxy,
277 reason: OracleAdapterSkipReason::NotChainlinkCompatible { error },
278 });
279 }
280 Err(error @ OracleError::Decode(_)) => {
285 skipped.push(OracleAdapterFeedSkip {
286 feed,
287 proxy,
288 reason: OracleAdapterSkipReason::NotChainlinkCompatible {
289 error: error.to_string(),
290 },
291 });
292 }
293 Err(error) => return Err(error),
294 }
295 }
296
297 Ok(skipped)
298 }
299
300 pub async fn register_config(
302 &self,
303 registry: &mut OracleRegistry,
304 config: FeedConfig,
305 ) -> Result<FeedId, OracleError> {
306 let source = FeedSource::Chainlink;
307 let read_proxy = source.read_proxy(config.proxy);
308 let core = self.read_chainlink_core(read_proxy)?;
309 self.insert_config_core(registry, config, core).await
310 }
311
312 fn layout_evidence_for_cores(
313 &self,
314 cores: &[Option<Result<ChainlinkCoreRead, OracleError>>],
315 ) -> Option<Vec<Option<AggregatorLayoutEvidence>>> {
316 let mut aggregators = Vec::new();
317 let mut seen_aggregators = BTreeSet::new();
318 for core in cores.iter().filter_map(|core| match core {
319 Some(Ok(core)) => core.aggregator,
320 _ => None,
321 }) {
322 if seen_aggregators.insert(core) {
323 aggregators.push(core);
324 }
325 }
326 if aggregators.is_empty() {
327 return Some(vec![None; cores.len()]);
328 }
329
330 let mut type_and_versions = self.read_type_and_versions_multicall(&aggregators)?;
331 let mut evidence_by_aggregator = BTreeMap::new();
332 for (index, aggregator) in aggregators.iter().copied().enumerate() {
333 let evidence = type_and_versions
334 .get_mut(index)
335 .and_then(Option::take)
336 .map(|type_and_version| {
337 AggregatorLayoutEvidence::from_type_and_version(
338 aggregator,
339 type_and_version,
340 None,
341 )
342 })
343 .unwrap_or_else(|| AggregatorLayoutEvidence::unknown(aggregator, None));
344 evidence_by_aggregator.insert(aggregator, evidence);
345 }
346
347 Some(
348 cores
349 .iter()
350 .map(|core| {
351 let aggregator = match core {
352 Some(Ok(core)) => core.aggregator?,
353 _ => return None,
354 };
355 evidence_by_aggregator.get(&aggregator).cloned()
356 })
357 .collect(),
358 )
359 }
360
361 async fn insert_config_core(
362 &self,
363 registry: &mut OracleRegistry,
364 config: FeedConfig,
365 core: ChainlinkCoreRead,
366 ) -> Result<FeedId, OracleError> {
367 let aggregator_layout = registry
368 .detect_aggregator_layout(self, core.aggregator, None)
369 .await;
370 self.insert_config_core_with_layout(registry, config, core, aggregator_layout)
371 }
372
373 fn insert_config_core_with_layout(
374 &self,
375 registry: &mut OracleRegistry,
376 config: FeedConfig,
377 core: ChainlinkCoreRead,
378 aggregator_layout: Option<AggregatorLayoutEvidence>,
379 ) -> Result<FeedId, OracleError> {
380 let id = config
381 .id
382 .unwrap_or_else(|| derive_feed_id(config.label.as_deref(), config.proxy));
383 let source = FeedSource::Chainlink;
384 let registration = FeedRegistration {
385 id: id.clone(),
386 proxy: config.proxy,
387 label: config.label,
388 base: config.base,
389 quote: config.quote,
390 staleness: config.staleness,
391 current_aggregator: core.aggregator,
392 aggregator_layout,
393 metadata: FeedMetadata {
394 decimals: core.decimals,
395 description: core.description,
396 version: core.version,
397 },
398 source,
399 status: OracleFeedStatus::Ready,
400 };
401 registry.insert_seeded_registration(registration, core.round)?;
402 Ok(id)
403 }
404
405 pub fn read_decimals(&self, proxy: Address) -> Result<u8, OracleError> {
407 self.cache
408 .borrow_mut()
409 .call_sol(proxy, decimalsCall {})
410 .map_err(provider_error)
411 }
412
413 pub fn read_description(&self, proxy: Address) -> Result<String, OracleError> {
415 self.cache
416 .borrow_mut()
417 .call_sol(proxy, descriptionCall {})
418 .map_err(provider_error)
419 }
420
421 pub fn read_version(&self, proxy: Address) -> Result<U256, OracleError> {
423 self.cache
424 .borrow_mut()
425 .call_sol(proxy, versionCall {})
426 .map_err(provider_error)
427 }
428
429 pub fn read_latest_round_data(&self, proxy: Address) -> Result<RoundData, OracleError> {
431 let round = self
432 .cache
433 .borrow_mut()
434 .call_sol(proxy, latestRoundDataCall {})
435 .map_err(provider_error)?;
436 round_from_raw(round)
437 }
438
439 pub fn read_aggregator(&self, proxy: Address) -> Result<Option<Address>, OracleError> {
441 let aggregator = self
442 .cache
443 .borrow_mut()
444 .call_sol(proxy, aggregatorCall {})
445 .map_err(provider_error)?;
446 Ok(Some(aggregator))
447 }
448
449 pub fn read_type_and_version(
451 &self,
452 aggregator: Address,
453 ) -> Result<Option<String>, OracleError> {
454 let type_and_version = self
455 .cache
456 .borrow_mut()
457 .call_sol(aggregator, typeAndVersionCall {})
458 .map_err(provider_error)?;
459 Ok(Some(type_and_version))
460 }
461
462 fn read_chainlink_core(&self, proxy: Address) -> Result<ChainlinkCoreRead, OracleError> {
463 if let Some(read) = self.read_chainlink_core_multicall(proxy) {
464 return read;
465 }
466
467 Ok(ChainlinkCoreRead {
468 decimals: self.read_decimals(proxy)?,
469 description: self.read_description(proxy)?,
470 version: self.read_version(proxy)?,
471 round: self.read_latest_round_data(proxy)?,
472 aggregator: self.read_aggregator(proxy)?,
473 })
474 }
475
476 fn read_chainlink_core_multicall(
477 &self,
478 proxy: Address,
479 ) -> Option<Result<ChainlinkCoreRead, OracleError>> {
480 let results = self.execute_multicall([
481 multicall_call(proxy, decimalsCall {}, true),
482 multicall_call(proxy, descriptionCall {}, true),
483 multicall_call(proxy, versionCall {}, true),
484 multicall_call(proxy, latestRoundDataCall {}, true),
485 multicall_call(proxy, aggregatorCall {}, true),
486 ])?;
487 if results.len() != 5 {
488 return None;
489 }
490
491 let decimals = try_decode_result::<decimalsCall>(&results[0])?;
492 let description = try_decode_result::<descriptionCall>(&results[1])?;
493 let version = try_decode_result::<versionCall>(&results[2])?;
494 let raw_round = try_decode_result::<latestRoundDataCall>(&results[3])?;
495 let aggregator = try_decode_result::<aggregatorCall>(&results[4]);
496 let round = match round_from_raw(raw_round) {
497 Ok(round) => round,
498 Err(error) => return Some(Err(error)),
499 };
500 Some(Ok(ChainlinkCoreRead {
501 decimals,
502 description,
503 version,
504 round,
505 aggregator,
506 }))
507 }
508
509 fn read_chainlink_cores_multicall(
510 &self,
511 proxies: &[Address],
512 ) -> Option<Vec<Option<Result<ChainlinkCoreRead, OracleError>>>> {
513 let calls = proxies
514 .iter()
515 .flat_map(|proxy| {
516 [
517 multicall_call(*proxy, decimalsCall {}, true),
518 multicall_call(*proxy, descriptionCall {}, true),
519 multicall_call(*proxy, versionCall {}, true),
520 multicall_call(*proxy, latestRoundDataCall {}, true),
521 multicall_call(*proxy, aggregatorCall {}, true),
522 ]
523 })
524 .collect::<Vec<_>>();
525 let results = self.execute_multicall(calls)?;
526 if results.len() != proxies.len() * 5 {
527 return None;
528 }
529
530 let mut cores = Vec::with_capacity(proxies.len());
531 for chunk in results.chunks_exact(5) {
532 let decimals = try_decode_result::<decimalsCall>(&chunk[0]);
533 let description = try_decode_result::<descriptionCall>(&chunk[1]);
534 let version = try_decode_result::<versionCall>(&chunk[2]);
535 let raw_round = try_decode_result::<latestRoundDataCall>(&chunk[3]);
536 let aggregator = try_decode_result::<aggregatorCall>(&chunk[4]);
537 let Some((decimals, description, version, raw_round)) =
538 decimals.zip(description).zip(version).zip(raw_round).map(
539 |(((decimals, description), version), raw_round)| {
540 (decimals, description, version, raw_round)
541 },
542 )
543 else {
544 cores.push(None);
545 continue;
546 };
547
548 let round = match round_from_raw(raw_round) {
549 Ok(round) => round,
550 Err(error) => {
551 cores.push(Some(Err(error)));
552 continue;
553 }
554 };
555 cores.push(Some(Ok(ChainlinkCoreRead {
556 decimals,
557 description,
558 version,
559 round,
560 aggregator,
561 })));
562 }
563 Some(cores)
564 }
565
566 fn read_type_and_versions_multicall(
567 &self,
568 aggregators: &[Address],
569 ) -> Option<Vec<Option<String>>> {
570 let calls = aggregators
571 .iter()
572 .map(|aggregator| multicall_call(*aggregator, typeAndVersionCall {}, true))
573 .collect::<Vec<_>>();
574 let results = self.execute_multicall(calls)?;
575 if results.len() != aggregators.len() {
576 return None;
577 }
578 Some(
579 results
580 .iter()
581 .map(try_decode_result::<typeAndVersionCall>)
582 .collect(),
583 )
584 }
585
586 fn execute_multicall<I>(&self, calls: I) -> Option<Vec<IMulticall3::Result>>
587 where
588 I: IntoIterator<Item = (Address, Bytes, bool)>,
589 {
590 if !self.multicall_reads {
591 return None;
592 }
593 let calls = calls.into_iter().collect::<Vec<_>>();
594 if calls.is_empty() {
595 return Some(Vec::new());
596 }
597 if let Some(results) = self.execute_direct_rpc_multicall(&calls) {
598 return Some(results);
599 }
600 let mut cache = self.cache.borrow_mut();
601 execute_batched(&mut cache, calls).ok()
602 }
603
604 fn execute_direct_rpc_multicall(
605 &self,
606 calls: &[(Address, Bytes, bool)],
607 ) -> Option<Vec<IMulticall3::Result>> {
608 let calls = calls
609 .iter()
610 .map(|(target, call_data, allow_failure)| IMulticall3::Call3 {
611 target: *target,
612 allowFailure: *allow_failure,
613 callData: call_data.clone(),
614 })
615 .collect::<Vec<_>>();
616 let call = IMulticall3::aggregate3Call { calls };
617 let cache = self.cache.borrow();
618 let bytes = cache
619 .rpc_call(MULTICALL3_ADDRESS, Bytes::from(call.abi_encode()))
620 .and_then(Result::ok)?;
621 IMulticall3::aggregate3Call::abi_decode_returns(&bytes).ok()
622 }
623
624 #[cfg(feature = "aave")]
625 pub(crate) fn read_aave_source(
626 &self,
627 oracle: Address,
628 asset: Address,
629 ) -> Result<Address, OracleError> {
630 self.cache
631 .borrow_mut()
632 .call_sol(oracle, getSourceOfAssetCall { asset })
633 .map_err(provider_error)
634 }
635
636 #[cfg(feature = "aave")]
637 pub(crate) fn read_aave_price_cap_stable(
638 &self,
639 source: Address,
640 ) -> Result<AavePriceCapStableSource, OracleError> {
641 let underlying_proxy = self
642 .cache
643 .borrow_mut()
644 .call_sol(source, ASSET_TO_USD_AGGREGATORCall {})
645 .map_err(provider_error)?;
646 let price_cap = self
647 .cache
648 .borrow_mut()
649 .call_sol(source, getPriceCapCall {})
650 .map_err(provider_error)?;
651 let decimals = self
652 .cache
653 .borrow_mut()
654 .call_sol(source, aaveDecimalsCall {})
655 .map_err(provider_error)?;
656 let description = self
657 .cache
658 .borrow_mut()
659 .call_sol(source, aaveDescriptionCall {})
660 .map_err(provider_error)?;
661 let latest_answer = self
662 .cache
663 .borrow_mut()
664 .call_sol(source, latestAnswerCall {})
665 .map_err(provider_error)?;
666
667 Ok(AavePriceCapStableSource {
668 underlying_proxy,
669 price_cap,
670 decimals,
671 description,
672 latest_answer,
673 })
674 }
675
676 #[cfg(feature = "aave")]
677 pub(crate) fn read_aave_ratio_cap(
678 &self,
679 source: Address,
680 ) -> Result<AaveRatioCapSource, OracleError> {
681 let base_to_usd_proxy = self
682 .cache
683 .borrow_mut()
684 .call_sol(source, BASE_TO_USD_AGGREGATORCall {})
685 .map_err(provider_error)?;
686 let ratio_provider = self
687 .cache
688 .borrow_mut()
689 .call_sol(source, RATIO_PROVIDERCall {})
690 .map_err(provider_error)?;
691 let current_ratio = self
692 .cache
693 .borrow_mut()
694 .call_sol(source, getRatioCall {})
695 .map_err(provider_error)?;
696 let is_capped = self
697 .cache
698 .borrow_mut()
699 .call_sol(source, isCappedCall {})
700 .map_err(provider_error)?;
701 let ratio_decimals = self
702 .cache
703 .borrow_mut()
704 .call_sol(source, RATIO_DECIMALSCall {})
705 .map_err(provider_error)?;
706 let decimals = self
707 .cache
708 .borrow_mut()
709 .call_sol(source, aaveRatioDecimalsCall {})
710 .map_err(provider_error)?;
711 let description = self
712 .cache
713 .borrow_mut()
714 .call_sol(source, aaveRatioDescriptionCall {})
715 .map_err(provider_error)?;
716 let latest_answer = self
717 .cache
718 .borrow_mut()
719 .call_sol(source, aaveRatioLatestAnswerCall {})
720 .map_err(provider_error)?;
721
722 Ok(AaveRatioCapSource {
723 base_to_usd_proxy,
724 ratio_provider,
725 current_ratio,
726 is_capped,
727 ratio_decimals,
728 decimals,
729 description,
730 latest_answer,
731 })
732 }
733
734 #[cfg(feature = "aave")]
735 pub(crate) fn read_aave_synchronicity_peg_to_base(
736 &self,
737 source: Address,
738 ) -> Result<AaveSynchronicityPegToBaseSource, OracleError> {
739 let asset_to_peg_proxy = self
740 .cache
741 .borrow_mut()
742 .call_sol(source, ASSET_TO_PEGCall {})
743 .map_err(provider_error)?;
744 let peg_to_base_proxy = self
745 .cache
746 .borrow_mut()
747 .call_sol(source, PEG_TO_BASECall {})
748 .map_err(provider_error)?;
749 let decimals = self
750 .cache
751 .borrow_mut()
752 .call_sol(source, aaveSynchronicityDecimalsCall {})
753 .map_err(provider_error)?;
754 let description = self
755 .cache
756 .borrow_mut()
757 .call_sol(source, aaveSynchronicityDescriptionCall {})
758 .map_err(provider_error)?;
759 let latest_answer = self
760 .cache
761 .borrow_mut()
762 .call_sol(source, aaveSynchronicityLatestAnswerCall {})
763 .map_err(provider_error)?;
764
765 Ok(AaveSynchronicityPegToBaseSource {
766 asset_to_peg_proxy,
767 peg_to_base_proxy,
768 decimals,
769 description,
770 latest_answer,
771 })
772 }
773
774 #[cfg(feature = "aave")]
775 pub(crate) fn read_aave_fixed_price(
776 &self,
777 source: Address,
778 ) -> Result<AaveFixedPriceSource, OracleError> {
779 if self.has_known_aave_dependency_or_config(source) {
780 return Err(OracleError::Unsupported(
781 "source exposes dependency/config view(s); not fixed-price".to_string(),
782 ));
783 }
784 let fixed_price = self.read_fixed_price_value(source)?;
785 let decimals = self
786 .cache
787 .borrow_mut()
788 .call_sol(source, aaveFixedDecimalsCall {})
789 .map_err(provider_error)?;
790 let description = self
791 .cache
792 .borrow_mut()
793 .call_sol(source, aaveFixedDescriptionCall {})
794 .map_err(provider_error)?;
795 let latest_answer = self
796 .cache
797 .borrow_mut()
798 .call_sol(source, aaveFixedLatestAnswerCall {})
799 .map_err(provider_error)?;
800 if latest_answer != fixed_price {
801 return Err(OracleError::Unsupported(
802 "latestAnswer() does not match fixed price getter".to_string(),
803 ));
804 }
805
806 Ok(AaveFixedPriceSource {
807 decimals,
808 description,
809 latest_answer,
810 })
811 }
812
813 #[cfg(feature = "aave")]
814 fn read_fixed_price_value(&self, source: Address) -> Result<I256, OracleError> {
815 match self
816 .cache
817 .borrow_mut()
818 .call_sol(source, aaveFixedPriceCall {})
819 {
820 Ok(price) => Ok(price),
821 Err(price_error) => match self.cache.borrow_mut().call_sol(source, PRICECall {}) {
822 Ok(price) => Ok(price),
823 Err(constant_error) => Err(OracleError::Unsupported(format!(
824 "missing fixed-price getter price() ({price_error:?}) or PRICE() ({constant_error:?})"
825 ))),
826 },
827 }
828 }
829
830 #[cfg(feature = "aave")]
831 fn has_known_aave_dependency_or_config(&self, source: Address) -> bool {
832 self.cache
833 .borrow_mut()
834 .call_sol(source, ASSET_TO_USD_AGGREGATORCall {})
835 .is_ok()
836 || self
837 .cache
838 .borrow_mut()
839 .call_sol(source, BASE_TO_USD_AGGREGATORCall {})
840 .is_ok()
841 || self
842 .cache
843 .borrow_mut()
844 .call_sol(source, ASSET_TO_PEGCall {})
845 .is_ok()
846 || self
847 .cache
848 .borrow_mut()
849 .call_sol(source, PEG_TO_BASECall {})
850 .is_ok()
851 || self
852 .cache
853 .borrow_mut()
854 .call_sol(source, BASE_TO_PEGCall {})
855 .is_ok()
856 || self
857 .cache
858 .borrow_mut()
859 .call_sol(source, REFERENCE_FEEDCall {})
860 .is_ok()
861 || self
862 .cache
863 .borrow_mut()
864 .call_sol(source, DISCOUNT_RATECall {})
865 .is_ok()
866 || self
867 .cache
868 .borrow_mut()
869 .call_sol(source, discountCall {})
870 .is_ok()
871 || self
872 .cache
873 .borrow_mut()
874 .call_sol(source, EXCHANGE_RATECall {})
875 .is_ok()
876 || self
877 .cache
878 .borrow_mut()
879 .call_sol(source, PENDLE_PRINCIPAL_TOKENCall {})
880 .is_ok()
881 || self
882 .cache
883 .borrow_mut()
884 .call_sol(source, PENDLE_ORACLECall {})
885 .is_ok()
886 }
887}
888
889impl ChainlinkFeedProvider for EvmCacheChainlinkReader<'_> {
890 fn decimals(&self, proxy: Address) -> ProviderFuture<'_, u8> {
891 let result = self.read_decimals(proxy);
892 Box::pin(async move { result })
893 }
894
895 fn description(&self, proxy: Address) -> ProviderFuture<'_, String> {
896 let result = self.read_description(proxy);
897 Box::pin(async move { result })
898 }
899
900 fn version(&self, proxy: Address) -> ProviderFuture<'_, U256> {
901 let result = self.read_version(proxy);
902 Box::pin(async move { result })
903 }
904
905 fn latest_round_data(&self, proxy: Address) -> ProviderFuture<'_, RoundData> {
906 let result = self.read_latest_round_data(proxy);
907 Box::pin(async move { result })
908 }
909
910 fn aggregator(&self, proxy: Address) -> ProviderFuture<'_, Option<Address>> {
911 let result = self.read_aggregator(proxy).or(Ok(None));
912 Box::pin(async move { result })
913 }
914
915 fn aggregator_type_and_version(
916 &self,
917 aggregator: Address,
918 ) -> ProviderFuture<'_, Option<String>> {
919 let result = self.read_type_and_version(aggregator).or(Ok(None));
920 Box::pin(async move { result })
921 }
922}
923
924pub struct OracleAdapter;
926
927impl OracleAdapter {
928 pub fn builder() -> OracleAdapterBuilder {
930 OracleAdapterBuilder::default()
931 }
932}
933
934#[derive(Clone, Debug)]
936pub struct OracleAdapterBuilder {
937 feeds: Vec<Feed>,
938 now_timestamp: Option<u64>,
939 multicall_reads: bool,
940}
941
942impl Default for OracleAdapterBuilder {
943 fn default() -> Self {
944 Self {
945 feeds: Vec::new(),
946 now_timestamp: None,
947 multicall_reads: true,
948 }
949 }
950}
951
952#[non_exhaustive]
954#[derive(Clone, Debug)]
955pub struct OracleAdapterBuildReport {
956 pub tracker: OracleTracker,
958 pub skipped: Vec<OracleAdapterFeedSkip>,
960}
961
962#[derive(Clone, Debug)]
964pub struct OracleAdapterFeedSkip {
965 pub feed: Feed,
967 pub proxy: Address,
969 pub reason: OracleAdapterSkipReason,
971}
972
973#[derive(Clone, Debug, PartialEq, Eq)]
977pub struct OracleFeedSkip {
978 pub feed_id: Option<FeedId>,
980 pub label: Option<String>,
982 pub proxy: Address,
984 pub reason: OracleAdapterSkipReason,
986}
987
988impl OracleFeedSkip {
989 pub(crate) fn from_adapter_skip(skipped: &OracleAdapterFeedSkip) -> Self {
990 Self {
991 feed_id: skipped.feed.id(),
992 label: skipped.feed.label().map(str::to_string),
993 proxy: skipped.proxy,
994 reason: skipped.reason.clone(),
995 }
996 }
997
998 fn display_name(&self) -> &str {
999 self.label
1000 .as_deref()
1001 .or_else(|| self.feed_id.as_ref().map(|id| id.as_str()))
1002 .unwrap_or("unknown")
1003 }
1004}
1005
1006impl std::fmt::Display for OracleFeedSkip {
1007 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1008 write!(
1009 f,
1010 "adapter feed `{}` (proxy {:?}) was skipped: {}",
1011 self.display_name(),
1012 self.proxy,
1013 self.reason
1014 )
1015 }
1016}
1017
1018#[non_exhaustive]
1020#[derive(Clone, Debug, PartialEq, Eq)]
1021pub enum OracleAdapterSkipReason {
1022 NotChainlinkCompatible {
1024 error: String,
1026 },
1027 UnsupportedAaveSource {
1029 error: String,
1031 },
1032 UnsupportedMorphoSource {
1034 error: String,
1036 },
1037 UnsupportedEulerSource {
1039 error: String,
1041 },
1042 #[cfg(feature = "redstone")]
1044 UnsupportedRedstoneSource {
1045 error: String,
1047 },
1048}
1049
1050impl OracleAdapterBuilder {
1051 pub fn feed(mut self, feed: Feed) -> Self {
1053 self.feeds.push(feed);
1054 self
1055 }
1056
1057 pub fn feeds(mut self, feeds: impl IntoIterator<Item = Feed>) -> Self {
1059 self.feeds.extend(feeds);
1060 self
1061 }
1062
1063 pub fn now_timestamp(mut self, now_timestamp: u64) -> Self {
1065 self.now_timestamp = Some(now_timestamp);
1066 self
1067 }
1068
1069 pub fn multicall_reads(mut self, enabled: bool) -> Self {
1077 self.multicall_reads = enabled;
1078 self
1079 }
1080
1081 pub fn disable_multicall_reads(self) -> Self {
1083 self.multicall_reads(false)
1084 }
1085
1086 pub async fn build(self, cache: &mut EvmCache) -> Result<OracleTracker, OracleError> {
1096 let report = self.build_report(cache).await?;
1097 if let Some(skipped) = report.skipped.first() {
1098 return Err(OracleError::FeedSkipped(Box::new(
1099 OracleFeedSkip::from_adapter_skip(skipped),
1100 )));
1101 }
1102 Ok(report.tracker)
1103 }
1104
1105 pub async fn build_report(
1107 self,
1108 cache: &mut EvmCache,
1109 ) -> Result<OracleAdapterBuildReport, OracleError> {
1110 let now_timestamp = match self.now_timestamp {
1111 Some(now_timestamp) => now_timestamp,
1112 None => SystemTime::now()
1113 .duration_since(UNIX_EPOCH)
1114 .map_err(crate::error::clock_error)?
1115 .as_secs(),
1116 };
1117 let reader = EvmCacheChainlinkReader::new(cache).with_multicall_reads(self.multicall_reads);
1118 let mut registry = OracleRegistry::new_at_timestamp(now_timestamp);
1119 let skipped = reader.register_feeds(&mut registry, self.feeds).await?;
1120 Ok(OracleAdapterBuildReport {
1121 tracker: OracleTracker::new(registry),
1122 skipped,
1123 })
1124 }
1125}
1126
1127impl std::fmt::Display for OracleAdapterSkipReason {
1128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1129 match self {
1130 Self::NotChainlinkCompatible { error } => {
1131 write!(f, "not Chainlink-compatible ({error})")
1132 }
1133 Self::UnsupportedAaveSource { error } => {
1134 write!(f, "unsupported Aave oracle source ({error})")
1135 }
1136 Self::UnsupportedMorphoSource { error } => {
1137 write!(f, "unsupported Morpho oracle source ({error})")
1138 }
1139 Self::UnsupportedEulerSource { error } => {
1140 write!(f, "unsupported Euler oracle source ({error})")
1141 }
1142 #[cfg(feature = "redstone")]
1143 Self::UnsupportedRedstoneSource { error } => {
1144 write!(f, "unsupported RedStone oracle source ({error})")
1145 }
1146 }
1147 }
1148}
1149
1150fn u64_from_u256(value: U256, field: &'static str) -> Result<u64, OracleError> {
1151 u64::try_from(value).map_err(|_| {
1152 OracleError::Decode(crate::ChainlinkEventDecodeError::Uint64Overflow { field, value })
1153 })
1154}
1155
1156fn round_from_raw(
1157 round: <latestRoundDataCall as SolCall>::Return,
1158) -> Result<RoundData, OracleError> {
1159 Ok(RoundData {
1160 round_id: U256::from(round.roundId),
1161 answer: round.answer,
1162 started_at: u64_from_u256(round.startedAt, "startedAt")?,
1163 updated_at: u64_from_u256(round.updatedAt, "updatedAt")?,
1164 answered_in_round: U256::from(round.answeredInRound),
1165 })
1166}
1167
1168fn multicall_call<C: SolCall>(
1169 target: Address,
1170 call: C,
1171 allow_failure: bool,
1172) -> (Address, Bytes, bool) {
1173 (target, Bytes::from(call.abi_encode()), allow_failure)
1174}
1175
1176fn provider_error(error: impl std::fmt::Debug) -> OracleError {
1177 OracleError::Provider(format!("{error:?}"))
1178}