Skip to main content

evm_oracle_state/
signal.rs

1use evm_fork_cache::reactive::HookSignal;
2
3use crate::{
4    AggregatorChange, OracleHookEvent, OraclePriceConfirmed, OraclePriceCorrected,
5    OraclePriceStale, OraclePriceUpdate, OracleSignalDecodeError, OracleUpdate,
6};
7
8/// Namespace used by every oracle hook signal.
9pub const ORACLE_SIGNAL_NAMESPACE: &str = "evm-oracle-state";
10
11/// Legacy event-update hook kind retained for compatibility.
12pub const ORACLE_LEGACY_ANSWER_UPDATED_KIND: &str = "oracle.answer_updated";
13
14/// Strongly typed oracle hook signal kind.
15#[non_exhaustive]
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
17pub enum OracleSignalKind {
18    /// Immediate event-derived price update.
19    PriceUpdate,
20    /// Event price matched the authoritative proxy read.
21    PriceConfirmed,
22    /// Event price was corrected by the authoritative proxy read.
23    PriceCorrected,
24    /// Authoritative proxy read was stale under policy.
25    PriceStale,
26    /// Proxy now points at a different aggregator.
27    AggregatorChanged,
28}
29
30impl OracleSignalKind {
31    /// Hook signal kind string.
32    pub const fn as_str(self) -> &'static str {
33        match self {
34            Self::PriceUpdate => "oracle.price_update",
35            Self::PriceConfirmed => "oracle.price_confirmed",
36            Self::PriceCorrected => "oracle.price_corrected",
37            Self::PriceStale => "oracle.price_stale",
38            Self::AggregatorChanged => "oracle.aggregator_changed",
39        }
40    }
41
42    /// Parse an oracle hook signal kind.
43    pub fn parse(kind: &str) -> Option<Self> {
44        match kind {
45            "oracle.price_update" => Some(Self::PriceUpdate),
46            "oracle.price_confirmed" => Some(Self::PriceConfirmed),
47            "oracle.price_corrected" => Some(Self::PriceCorrected),
48            "oracle.price_stale" => Some(Self::PriceStale),
49            "oracle.aggregator_changed" => Some(Self::AggregatorChanged),
50            _ => None,
51        }
52    }
53}
54
55/// Borrowed typed view over an oracle hook signal payload.
56#[non_exhaustive]
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
58pub enum OracleSignal<'a> {
59    /// Immediate event-derived price update.
60    PriceUpdate(&'a OraclePriceUpdate),
61    /// Event price matched the authoritative proxy read.
62    PriceConfirmed(&'a OraclePriceConfirmed),
63    /// Event price was corrected by the authoritative proxy read.
64    PriceCorrected(&'a OraclePriceCorrected),
65    /// Authoritative proxy read was stale under policy.
66    PriceStale(&'a OraclePriceStale),
67    /// Proxy now points at a different aggregator.
68    AggregatorChanged(&'a AggregatorChange),
69}
70
71impl<'a> OracleSignal<'a> {
72    /// Decode a generic reactive hook into a typed oracle signal.
73    pub fn from_hook(signal: &'a HookSignal) -> Result<Option<Self>, OracleSignalDecodeError> {
74        if signal.namespace.as_ref() != ORACLE_SIGNAL_NAMESPACE {
75            return Ok(None);
76        }
77
78        let Some(kind) = OracleSignalKind::parse(signal.kind.as_ref()) else {
79            return Ok(None);
80        };
81        let payload =
82            signal
83                .payload
84                .as_ref()
85                .ok_or_else(|| OracleSignalDecodeError::MissingPayload {
86                    kind: signal.kind.to_string(),
87                })?;
88
89        if let Some(event) = payload.downcast_ref::<OracleHookEvent>() {
90            return event
91                .as_signal()
92                .ok_or_else(|| OracleSignalDecodeError::WrongPayloadType {
93                    kind: signal.kind.to_string(),
94                })
95                .map(Some);
96        }
97
98        match kind {
99            OracleSignalKind::PriceUpdate => payload
100                .downcast_ref::<OraclePriceUpdate>()
101                .map(Self::PriceUpdate),
102            OracleSignalKind::PriceConfirmed => payload
103                .downcast_ref::<OraclePriceConfirmed>()
104                .map(Self::PriceConfirmed),
105            OracleSignalKind::PriceCorrected => payload
106                .downcast_ref::<OraclePriceCorrected>()
107                .map(Self::PriceCorrected),
108            OracleSignalKind::PriceStale => payload
109                .downcast_ref::<OraclePriceStale>()
110                .map(Self::PriceStale),
111            OracleSignalKind::AggregatorChanged => payload
112                .downcast_ref::<AggregatorChange>()
113                .map(Self::AggregatorChanged),
114        }
115        .ok_or_else(|| OracleSignalDecodeError::WrongPayloadType {
116            kind: signal.kind.to_string(),
117        })
118        .map(Some)
119    }
120
121    /// Own this borrowed signal as an `OracleHookEvent`.
122    pub fn to_event(self) -> OracleHookEvent {
123        match self {
124            Self::PriceUpdate(update) => OracleHookEvent::PriceUpdate(update.clone()),
125            Self::PriceConfirmed(confirmed) => OracleHookEvent::PriceConfirmed(confirmed.clone()),
126            Self::PriceCorrected(corrected) => OracleHookEvent::PriceCorrected(corrected.clone()),
127            Self::PriceStale(stale) => OracleHookEvent::PriceStale(stale.clone()),
128            Self::AggregatorChanged(change) => OracleHookEvent::AggregatorChanged(change.clone()),
129        }
130    }
131}
132
133impl OracleHookEvent {
134    /// Return this event's signal kind.
135    pub fn signal_kind(&self) -> OracleSignalKind {
136        match self {
137            Self::PriceUpdate(_) => OracleSignalKind::PriceUpdate,
138            Self::PriceConfirmed(_) => OracleSignalKind::PriceConfirmed,
139            Self::PriceCorrected(_) => OracleSignalKind::PriceCorrected,
140            Self::PriceStale(_) => OracleSignalKind::PriceStale,
141            Self::AggregatorChanged(_) => OracleSignalKind::AggregatorChanged,
142        }
143    }
144
145    /// Return this event's hook kind string.
146    pub fn kind_str(&self) -> &'static str {
147        self.signal_kind().as_str()
148    }
149
150    pub(crate) fn as_signal(&self) -> Option<OracleSignal<'_>> {
151        match self {
152            Self::PriceUpdate(update) => Some(OracleSignal::PriceUpdate(update)),
153            Self::PriceConfirmed(confirmed) => Some(OracleSignal::PriceConfirmed(confirmed)),
154            Self::PriceCorrected(corrected) => Some(OracleSignal::PriceCorrected(corrected)),
155            Self::PriceStale(stale) => Some(OracleSignal::PriceStale(stale)),
156            Self::AggregatorChanged(change) => Some(OracleSignal::AggregatorChanged(change)),
157        }
158    }
159}
160
161impl OracleUpdate {
162    /// Legacy hook kind for this payload.
163    pub const KIND: &'static str = ORACLE_LEGACY_ANSWER_UPDATED_KIND;
164}