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