Skip to main content

evm_oracle_state/
error.rs

1use thiserror::Error;
2
3use crate::{OracleAdapterId, OracleCodePolicyViolation, OracleFeedSkip, PricePolicyViolation};
4
5/// Errors returned by Chainlink event decoding.
6#[non_exhaustive]
7#[derive(Clone, Debug, Error, PartialEq, Eq)]
8pub enum ChainlinkEventDecodeError {
9    /// The log did not contain the expected event signature topic.
10    #[error("wrong event topic: expected {expected:?}, got {actual:?}")]
11    WrongTopic {
12        /// Expected topic0.
13        expected: alloy_primitives::B256,
14        /// Actual topic0, if present.
15        actual: Option<alloy_primitives::B256>,
16    },
17    /// The log had the wrong number of topics.
18    #[error("wrong topic count: expected {expected}, got {actual}")]
19    WrongTopicCount {
20        /// Expected topic count.
21        expected: usize,
22        /// Actual topic count.
23        actual: usize,
24    },
25    /// The data payload had the wrong byte length.
26    #[error("wrong data length: expected {expected}, got {actual}")]
27    WrongDataLength {
28        /// Expected byte length.
29        expected: usize,
30        /// Actual byte length.
31        actual: usize,
32    },
33    /// A uint256 event field did not fit into the crate's typed Rust field.
34    #[error("event field `{field}` value {value} does not fit in u64")]
35    Uint64Overflow {
36        /// Field name.
37        field: &'static str,
38        /// Encoded value.
39        value: alloy_primitives::U256,
40    },
41    /// ABI decoding failed for a dynamic or typed event payload.
42    #[error("failed to decode event `{event}`: {message}")]
43    AbiDecode {
44        /// Event name.
45        event: &'static str,
46        /// Decoder error message.
47        message: String,
48    },
49}
50
51/// Errors returned by registry, reconciliation, and report application.
52#[non_exhaustive]
53#[derive(Clone, Debug, Error, PartialEq, Eq)]
54pub enum OracleError {
55    /// A chain/provider read failed.
56    ///
57    /// This covers only genuine on-chain read failures: RPC transport errors,
58    /// reverted or undecodable view calls (`call_sol`), and
59    /// [`crate::EvmCacheChainlinkReader`] read failures. Programming errors,
60    /// policy rejections, and unsupported-source classifications use the
61    /// dedicated variants below.
62    #[error("provider read failed: {0}")]
63    Provider(String),
64    /// Event or typed-field decode failed.
65    ///
66    /// Covers Chainlink log decoding and value-range failures where a chain
67    /// value does not fit the crate's typed representation (for example a
68    /// `latestRoundData()` timestamp that overflows `u64`, reported as
69    /// [`ChainlinkEventDecodeError::Uint64Overflow`]).
70    #[error(transparent)]
71    Decode(#[from] ChainlinkEventDecodeError),
72    /// Hook signal decode failed.
73    #[error(transparent)]
74    SignalDecode(#[from] OracleSignalDecodeError),
75    /// Feed id already exists.
76    #[error("feed id `{0}` is already registered")]
77    DuplicateFeedId(String),
78    /// Proxy already exists.
79    #[error("proxy `{0:?}` is already registered")]
80    DuplicateProxy(alloy_primitives::Address),
81    /// A feed was not found.
82    #[error("feed was not found")]
83    FeedNotFound,
84    /// A configured price policy rejected a value or result.
85    ///
86    /// Covers [`crate::PricePolicy`] rejections (stale, pending, invalid, or
87    /// wrong-denomination prices) and valuation failures while producing a
88    /// [`crate::ValuedAmount`]. The payload classifies the exact violation.
89    /// Code warmup policy failures use [`OracleError::CodePolicy`].
90    #[error("policy rejected: {0}")]
91    Policy(#[from] PricePolicyViolation),
92    /// The configured [`crate::OracleCodeWarmupPolicy`] rejected a code
93    /// warmup report.
94    ///
95    /// The boxed payload lists exactly the [`crate::OracleCodeWarmupReport`]
96    /// entries that violated the enforced policy toggles.
97    #[error("{0}")]
98    CodePolicy(Box<OracleCodePolicyViolation>),
99    /// Caller or programming error.
100    ///
101    /// The payload classifies the failure: a request that references an
102    /// adapter that is not installed
103    /// ([`OracleConfigError::AdapterNotInstalled`]), a system clock before
104    /// the UNIX epoch ([`OracleConfigError::Clock`]), a malformed or
105    /// mismatched overlay/reconciliation request
106    /// ([`OracleConfigError::InvalidRequest`]), or another invalid builder
107    /// configuration or internal invariant violation
108    /// ([`OracleConfigError::Other`]).
109    #[error("configuration error: {0}")]
110    Config(#[from] OracleConfigError),
111    /// A source family, layout, or value shape was recognized but is not
112    /// supported by this crate.
113    ///
114    /// Covers unsupported adapter variants discovered at runtime (for example
115    /// an unknown Euler adapter name, a nested `CrossAdapter`, or a
116    /// base/quote direction mismatch) and chain values that cannot be
117    /// represented in the requested ABI or fixed-point shape.
118    #[error("unsupported source or value: {0}")]
119    Unsupported(String),
120    /// An adapter feed skip was surfaced as a fail-fast build error.
121    ///
122    /// The boxed payload carries the complete skip description (feed
123    /// id/label, proxy, and classified [`crate::OracleAdapterSkipReason`]).
124    /// Use the `build_report` methods to receive skips as data instead of an
125    /// error.
126    #[error("{0}")]
127    FeedSkipped(Box<OracleFeedSkip>),
128    /// A reactive-engine operation failed.
129    ///
130    /// Covers handler install/uninstall, subscriber interest registration,
131    /// and batch ingestion errors surfaced through the
132    /// [`evm_fork_cache::reactive`] runtime.
133    #[error("reactive engine error: {0}")]
134    Reactive(String),
135}
136
137/// Errors returned when decoding oracle hook signals.
138#[non_exhaustive]
139#[derive(Clone, Debug, Error, PartialEq, Eq)]
140pub enum OracleSignalDecodeError {
141    /// The signal kind is known but the payload is absent.
142    #[error("oracle signal `{kind}` did not include a typed payload")]
143    MissingPayload {
144        /// Signal kind.
145        kind: String,
146    },
147    /// The signal kind is known but the payload type does not match.
148    #[error("oracle signal `{kind}` had an unexpected payload type")]
149    WrongPayloadType {
150        /// Signal kind.
151        kind: String,
152    },
153}
154
155/// Caller or programming errors carried by [`OracleError::Config`].
156#[non_exhaustive]
157#[derive(Clone, Debug, Error, PartialEq, Eq)]
158pub enum OracleConfigError {
159    /// A request referenced an oracle adapter that is not installed in the
160    /// runtime.
161    #[error("oracle adapter `{adapter}` is not installed in this runtime")]
162    AdapterNotInstalled {
163        /// Adapter id the request referenced.
164        adapter: OracleAdapterId,
165    },
166    /// The system clock could not produce a UNIX timestamp.
167    #[error("system clock is before the UNIX epoch: {detail}")]
168    Clock {
169        /// Stringified [`std::time::SystemTimeError`]. Kept as a string
170        /// because the source error is neither `Clone` nor `PartialEq`.
171        detail: String,
172    },
173    /// A malformed or mismatched overlay or reconciliation request, for
174    /// example calldata that does not decode or a quote request that does
175    /// not match any registered pair.
176    #[error("{0}")]
177    InvalidRequest(String),
178    /// Any other invalid builder configuration or internal invariant
179    /// violation.
180    #[error("{0}")]
181    Other(String),
182}
183
184/// Classify a `SystemTime::duration_since(UNIX_EPOCH)` failure.
185pub(crate) fn clock_error(error: std::time::SystemTimeError) -> OracleError {
186    OracleError::Config(OracleConfigError::Clock {
187        detail: error.to_string(),
188    })
189}