evm_oracle_state/pending/
adapter.rs1use std::collections::BTreeSet;
4
5use alloy_primitives::{Address, keccak256};
6use alloy_rpc_types_eth::Log;
7
8use crate::{FeedRegistration, FeedSource, OracleAdapterId, decode_ocr2_new_transmission};
9
10use super::{
11 CHAINLINK_FORWARD_SELECTOR, CHAINLINK_TRANSMIT_SECONDARY_SELECTOR, CHAINLINK_TRANSMIT_SELECTOR,
12 PendingOracleEvidence, PendingOracleObserveOutcome, PendingOracleOrderingHandle,
13 PendingOracleSimulationMaterial, PendingOracleTransmission, PendingOracleUpdate,
14 PendingOracleValue, PendingTransportCandidate, decode_chainlink_ocr2_calldata,
15};
16
17#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct PendingOracleInterest {
23 pub adapter_id: OracleAdapterId,
25 pub addresses: BTreeSet<Address>,
27 pub selectors: BTreeSet<[u8; 4]>,
29}
30
31impl PendingOracleInterest {
32 pub fn new(
34 adapter_id: OracleAdapterId,
35 addresses: impl IntoIterator<Item = Address>,
36 selectors: impl IntoIterator<Item = [u8; 4]>,
37 ) -> Self {
38 Self {
39 adapter_id,
40 addresses: addresses.into_iter().collect(),
41 selectors: selectors.into_iter().collect(),
42 }
43 }
44
45 pub fn matches(&self, candidate: &PendingTransportCandidate) -> bool {
47 let Some(selector) = candidate.calldata.get(..4) else {
48 return false;
49 };
50 (self.addresses.is_empty() || self.addresses.contains(&candidate.to))
51 && self
52 .selectors
53 .iter()
54 .any(|expected| selector == expected.as_slice())
55 }
56}
57
58#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
60pub enum PendingOracleAdapterError {
61 #[error("pending candidate is for chain {observed}, expected {expected}")]
63 WrongChain {
64 expected: u64,
66 observed: u64,
68 },
69 #[error("pending source {0:?} is not enabled")]
71 SourceDisabled(super::PendingOracleSource),
72 #[error("pending adapter {adapter_id} failed to decode candidate: {message}")]
74 Decode {
75 adapter_id: OracleAdapterId,
77 message: String,
79 },
80}
81
82impl PendingOracleAdapterError {
83 pub fn new(adapter_id: OracleAdapterId, message: impl Into<String>) -> Self {
85 Self::Decode {
86 adapter_id,
87 message: message.into(),
88 }
89 }
90}
91
92#[derive(Clone, Debug, PartialEq, Eq)]
94pub struct PendingOracleAdapterObservation {
95 pub adapter_id: OracleAdapterId,
97 pub update_id: super::PendingOracleUpdateId,
99 pub outcome: PendingOracleObserveOutcome,
101}
102
103#[non_exhaustive]
105#[derive(Clone, Debug, PartialEq, Eq)]
106pub struct PendingOracleAdapterFailure {
107 pub adapter_id: OracleAdapterId,
109 pub error: PendingOracleAdapterError,
111}
112
113#[non_exhaustive]
115#[derive(Clone, Debug, Default, PartialEq, Eq)]
116pub struct PendingOracleCandidateReport {
117 pub observations: Vec<PendingOracleAdapterObservation>,
119 pub failures: Vec<PendingOracleAdapterFailure>,
121}
122
123pub trait PendingOracleAdapter: Send + Sync + 'static {
125 fn adapter_id(&self) -> OracleAdapterId;
127
128 fn interests(&self, registrations: &[FeedRegistration]) -> Vec<PendingOracleInterest>;
130
131 fn decode(
133 &self,
134 candidate: &PendingTransportCandidate,
135 registrations: &[FeedRegistration],
136 ) -> Result<Vec<PendingOracleUpdate>, PendingOracleAdapterError>;
137
138 fn confirms(&self, _update: &PendingOracleUpdate, _log: &Log) -> bool {
140 false
141 }
142}
143
144#[derive(Clone, Copy, Debug, Default)]
146pub struct ChainlinkPendingAdapter;
147
148impl ChainlinkPendingAdapter {
149 pub const ID: &'static str = "chainlink-ocr2";
151}
152
153impl PendingOracleAdapter for ChainlinkPendingAdapter {
154 fn adapter_id(&self) -> OracleAdapterId {
155 OracleAdapterId::new(Self::ID)
156 }
157
158 fn interests(&self, registrations: &[FeedRegistration]) -> Vec<PendingOracleInterest> {
159 let aggregators = registrations
160 .iter()
161 .filter(|registration| matches!(®istration.source, FeedSource::Chainlink))
162 .filter_map(|registration| registration.current_aggregator);
163 let aggregators = aggregators.collect::<BTreeSet<_>>();
164 if aggregators.is_empty() {
165 return Vec::new();
166 }
167 vec![
168 PendingOracleInterest::new(
169 self.adapter_id(),
170 aggregators,
171 [
172 CHAINLINK_TRANSMIT_SELECTOR,
173 CHAINLINK_TRANSMIT_SECONDARY_SELECTOR,
174 ],
175 ),
176 PendingOracleInterest::new(self.adapter_id(), [], [CHAINLINK_FORWARD_SELECTOR]),
177 ]
178 }
179
180 fn decode(
181 &self,
182 candidate: &PendingTransportCandidate,
183 registrations: &[FeedRegistration],
184 ) -> Result<Vec<PendingOracleUpdate>, PendingOracleAdapterError> {
185 let Some(decoded) = decode_chainlink_ocr2_calldata(candidate.to, &candidate.calldata)
186 .map_err(|error| {
187 PendingOracleAdapterError::new(self.adapter_id(), error.to_string())
188 })?
189 else {
190 return Ok(Vec::new());
191 };
192 let feed_ids = registrations
193 .iter()
194 .filter(|registration| {
195 matches!(®istration.source, FeedSource::Chainlink)
196 && registration.current_aggregator == Some(decoded.report.aggregator)
197 })
198 .map(|registration| registration.id.clone())
199 .collect::<Vec<_>>();
200 if feed_ids.is_empty() {
201 return Ok(Vec::new());
202 }
203
204 let simulation = match &candidate.ordering {
205 PendingOracleOrderingHandle::RawEthereumTransaction {
206 tx_hash,
207 signed_envelope,
208 } if keccak256(signed_envelope) == *tx_hash => {
209 PendingOracleSimulationMaterial::ExactSignedTransaction
210 }
211 _ => PendingOracleSimulationMaterial::AnswerOnlyProjection,
212 };
213 let transmission = PendingOracleTransmission::new(
214 candidate.id,
215 candidate.source_id.clone(),
216 decoded.route.clone(),
217 candidate.ordering.clone(),
218 simulation,
219 decoded.report.calldata_hash,
220 candidate.observed_at_head,
221 );
222 Ok(vec![
223 PendingOracleUpdate::new(
224 decoded.update_id(),
225 feed_ids,
226 PendingOracleEvidence::ChainlinkReport(decoded.report.clone()),
227 )
228 .with_proposed_value(PendingOracleValue {
229 raw_answer: decoded.report.median_answer,
230 observed_at: decoded.report.observations_timestamp,
231 })
232 .with_transmission(transmission),
233 ])
234 }
235
236 fn confirms(&self, update: &PendingOracleUpdate, log: &Log) -> bool {
237 let PendingOracleEvidence::ChainlinkReport(report) = &update.evidence else {
238 return false;
239 };
240 decode_ocr2_new_transmission(log).is_ok_and(|confirmed| {
241 !confirmed.removed
242 && confirmed.aggregator == report.aggregator
243 && confirmed.answer == report.median_answer
244 && confirmed.observations_timestamp == report.observations_timestamp
245 })
246 }
247}
248
249pub(crate) fn adapter_owns_update(
250 adapter_id: &OracleAdapterId,
251 update: &PendingOracleUpdate,
252) -> bool {
253 match &update.evidence {
254 PendingOracleEvidence::ChainlinkReport(_) => {
255 adapter_id.as_str() == ChainlinkPendingAdapter::ID
256 }
257 PendingOracleEvidence::Adapter {
258 adapter_id: owner, ..
259 } => owner == adapter_id,
260 }
261}