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}