1use alloy_primitives::{Address, B256, I256, U256};
2
3use crate::{FeedId, StalenessPolicy};
4
5#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct FeedMetadata {
8 pub decimals: u8,
10 pub description: String,
12 pub version: U256,
14}
15
16#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct RoundData {
19 pub round_id: U256,
21 pub answer: I256,
23 pub started_at: u64,
25 pub updated_at: u64,
27 pub answered_in_round: U256,
29}
30
31#[non_exhaustive]
37#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
38pub enum OracleFeedStatus {
39 #[default]
41 Pending,
42 Cold,
44 Ready,
46 Degraded,
48 Disabled,
50 Unsupported,
52}
53
54#[non_exhaustive]
56#[derive(Clone, Debug, PartialEq, Eq)]
57pub enum OracleRoundStatus {
58 Fresh,
60 Stale {
62 age_secs: u64,
64 max_age_secs: u64,
66 },
67 IncompleteRound,
69 InvalidAnswer,
71 Unknown,
73}
74
75#[non_exhaustive]
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub enum OracleValueStatus {
79 EventPending,
81 Confirmed,
85 Corrected,
89 RequiresRepair,
94 Unknown,
96}
97
98#[non_exhaustive]
100#[derive(Clone, Copy, Debug, PartialEq, Eq)]
101pub enum OracleValueSource {
102 Proxy,
104 Event,
106 Derived,
108 Mock,
110 Unknown,
112}
113
114#[derive(Clone, Debug, PartialEq, Eq)]
116pub struct OracleSnapshot {
117 pub id: FeedId,
119 pub proxy: Address,
121 pub aggregator: Option<Address>,
123 pub metadata: FeedMetadata,
125 pub round: RoundData,
127 pub round_status: OracleRoundStatus,
129 pub value_status: OracleValueStatus,
131 pub source: OracleValueSource,
133 pub block_number: Option<u64>,
135 pub block_hash: Option<B256>,
137 requires_reconciliation: bool,
138}
139
140impl OracleSnapshot {
141 pub(crate) fn proxy_read(
142 id: FeedId,
143 proxy: Address,
144 aggregator: Option<Address>,
145 metadata: FeedMetadata,
146 round: RoundData,
147 now_timestamp: u64,
148 staleness: &StalenessPolicy,
149 ) -> Self {
150 let round_status = classify_round(&round, now_timestamp, staleness);
151 Self {
152 id,
153 proxy,
154 aggregator,
155 metadata,
156 round,
157 round_status,
158 value_status: OracleValueStatus::Confirmed,
159 source: OracleValueSource::Proxy,
160 block_number: None,
161 block_hash: None,
162 requires_reconciliation: false,
163 }
164 }
165
166 pub(crate) fn event(input: EventSnapshotInput<'_>) -> Self {
167 let round_status = classify_round(&input.round, input.now_timestamp, input.staleness);
168 Self {
169 id: input.id,
170 proxy: input.proxy,
171 aggregator: input.aggregator,
172 metadata: input.metadata,
173 round: input.round,
174 round_status,
175 value_status: input.value_status,
176 source: input.source,
177 block_number: input.block_number,
178 block_hash: input.block_hash,
179 requires_reconciliation: matches!(
180 input.value_status,
181 OracleValueStatus::EventPending
182 | OracleValueStatus::RequiresRepair
183 | OracleValueStatus::Unknown
184 ),
185 }
186 }
187
188 pub(crate) fn mark_unknown(&mut self) {
189 self.round_status = OracleRoundStatus::Unknown;
190 self.value_status = OracleValueStatus::RequiresRepair;
191 self.source = OracleValueSource::Unknown;
192 self.requires_reconciliation = true;
193 }
194
195 pub(crate) fn set_value_status(&mut self, value_status: OracleValueStatus) {
196 self.value_status = value_status;
197 self.requires_reconciliation = matches!(
198 value_status,
199 OracleValueStatus::EventPending
200 | OracleValueStatus::RequiresRepair
201 | OracleValueStatus::Unknown
202 );
203 }
204
205 pub fn requires_reconciliation(&self) -> bool {
207 self.requires_reconciliation
208 }
209}
210
211pub(crate) struct EventSnapshotInput<'a> {
212 pub(crate) id: FeedId,
213 pub(crate) proxy: Address,
214 pub(crate) aggregator: Option<Address>,
215 pub(crate) metadata: FeedMetadata,
216 pub(crate) round: RoundData,
217 pub(crate) now_timestamp: u64,
218 pub(crate) staleness: &'a StalenessPolicy,
219 pub(crate) block_number: Option<u64>,
220 pub(crate) block_hash: Option<B256>,
221 pub(crate) value_status: OracleValueStatus,
222 pub(crate) source: OracleValueSource,
223}
224
225pub(crate) fn classify_round(
226 round: &RoundData,
227 now_timestamp: u64,
228 staleness: &StalenessPolicy,
229) -> OracleRoundStatus {
230 if round.updated_at == 0 || round.answered_in_round < round.round_id {
231 return OracleRoundStatus::IncompleteRound;
232 }
233
234 if !staleness.allows_zero_or_negative_answer() && round.answer <= I256::ZERO {
235 return OracleRoundStatus::InvalidAnswer;
236 }
237
238 if let Some(max_age_secs) = staleness.max_age_secs() {
239 let age_secs = now_timestamp.saturating_sub(round.updated_at);
240 if age_secs > max_age_secs {
241 return OracleRoundStatus::Stale {
242 age_secs,
243 max_age_secs,
244 };
245 }
246 }
247
248 OracleRoundStatus::Fresh
249}