use std::collections::BTreeSet;
use alloy_primitives::{Address, keccak256};
use alloy_rpc_types_eth::Log;
use crate::{FeedRegistration, FeedSource, OracleAdapterId, decode_ocr2_new_transmission};
use super::{
CHAINLINK_FORWARD_SELECTOR, CHAINLINK_TRANSMIT_SECONDARY_SELECTOR, CHAINLINK_TRANSMIT_SELECTOR,
PendingOracleEvidence, PendingOracleObserveOutcome, PendingOracleOrderingHandle,
PendingOracleSimulationMaterial, PendingOracleTransmission, PendingOracleUpdate,
PendingOracleValue, PendingTransportCandidate, decode_chainlink_ocr2_calldata,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PendingOracleInterest {
pub adapter_id: OracleAdapterId,
pub addresses: BTreeSet<Address>,
pub selectors: BTreeSet<[u8; 4]>,
}
impl PendingOracleInterest {
pub fn new(
adapter_id: OracleAdapterId,
addresses: impl IntoIterator<Item = Address>,
selectors: impl IntoIterator<Item = [u8; 4]>,
) -> Self {
Self {
adapter_id,
addresses: addresses.into_iter().collect(),
selectors: selectors.into_iter().collect(),
}
}
pub fn matches(&self, candidate: &PendingTransportCandidate) -> bool {
let Some(selector) = candidate.calldata.get(..4) else {
return false;
};
(self.addresses.is_empty() || self.addresses.contains(&candidate.to))
&& self
.selectors
.iter()
.any(|expected| selector == expected.as_slice())
}
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum PendingOracleAdapterError {
#[error("pending candidate is for chain {observed}, expected {expected}")]
WrongChain {
expected: u64,
observed: u64,
},
#[error("pending source {0:?} is not enabled")]
SourceDisabled(super::PendingOracleSource),
#[error("pending adapter {adapter_id} failed to decode candidate: {message}")]
Decode {
adapter_id: OracleAdapterId,
message: String,
},
}
impl PendingOracleAdapterError {
pub fn new(adapter_id: OracleAdapterId, message: impl Into<String>) -> Self {
Self::Decode {
adapter_id,
message: message.into(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PendingOracleAdapterObservation {
pub adapter_id: OracleAdapterId,
pub update_id: super::PendingOracleUpdateId,
pub outcome: PendingOracleObserveOutcome,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PendingOracleAdapterFailure {
pub adapter_id: OracleAdapterId,
pub error: PendingOracleAdapterError,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PendingOracleCandidateReport {
pub observations: Vec<PendingOracleAdapterObservation>,
pub failures: Vec<PendingOracleAdapterFailure>,
}
pub trait PendingOracleAdapter: Send + Sync + 'static {
fn adapter_id(&self) -> OracleAdapterId;
fn interests(&self, registrations: &[FeedRegistration]) -> Vec<PendingOracleInterest>;
fn decode(
&self,
candidate: &PendingTransportCandidate,
registrations: &[FeedRegistration],
) -> Result<Vec<PendingOracleUpdate>, PendingOracleAdapterError>;
fn confirms(&self, _update: &PendingOracleUpdate, _log: &Log) -> bool {
false
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct ChainlinkPendingAdapter;
impl ChainlinkPendingAdapter {
pub const ID: &'static str = "chainlink-ocr2";
}
impl PendingOracleAdapter for ChainlinkPendingAdapter {
fn adapter_id(&self) -> OracleAdapterId {
OracleAdapterId::new(Self::ID)
}
fn interests(&self, registrations: &[FeedRegistration]) -> Vec<PendingOracleInterest> {
let aggregators = registrations
.iter()
.filter(|registration| matches!(®istration.source, FeedSource::Chainlink))
.filter_map(|registration| registration.current_aggregator);
let aggregators = aggregators.collect::<BTreeSet<_>>();
if aggregators.is_empty() {
return Vec::new();
}
vec![
PendingOracleInterest::new(
self.adapter_id(),
aggregators,
[
CHAINLINK_TRANSMIT_SELECTOR,
CHAINLINK_TRANSMIT_SECONDARY_SELECTOR,
],
),
PendingOracleInterest::new(self.adapter_id(), [], [CHAINLINK_FORWARD_SELECTOR]),
]
}
fn decode(
&self,
candidate: &PendingTransportCandidate,
registrations: &[FeedRegistration],
) -> Result<Vec<PendingOracleUpdate>, PendingOracleAdapterError> {
let Some(decoded) = decode_chainlink_ocr2_calldata(candidate.to, &candidate.calldata)
.map_err(|error| {
PendingOracleAdapterError::new(self.adapter_id(), error.to_string())
})?
else {
return Ok(Vec::new());
};
let feed_ids = registrations
.iter()
.filter(|registration| {
matches!(®istration.source, FeedSource::Chainlink)
&& registration.current_aggregator == Some(decoded.report.aggregator)
})
.map(|registration| registration.id.clone())
.collect::<Vec<_>>();
if feed_ids.is_empty() {
return Ok(Vec::new());
}
let simulation = match &candidate.ordering {
PendingOracleOrderingHandle::RawEthereumTransaction {
tx_hash,
signed_envelope,
} if keccak256(signed_envelope) == *tx_hash => {
PendingOracleSimulationMaterial::ExactSignedTransaction
}
_ => PendingOracleSimulationMaterial::AnswerOnlyProjection,
};
let transmission = PendingOracleTransmission::new(
candidate.id,
candidate.source_id.clone(),
decoded.route.clone(),
candidate.ordering.clone(),
simulation,
decoded.report.calldata_hash,
candidate.observed_at_head,
);
Ok(vec![
PendingOracleUpdate::new(
decoded.update_id(),
feed_ids,
PendingOracleEvidence::ChainlinkReport(decoded.report.clone()),
)
.with_proposed_value(PendingOracleValue {
raw_answer: decoded.report.median_answer,
observed_at: decoded.report.observations_timestamp,
})
.with_transmission(transmission),
])
}
fn confirms(&self, update: &PendingOracleUpdate, log: &Log) -> bool {
let PendingOracleEvidence::ChainlinkReport(report) = &update.evidence else {
return false;
};
decode_ocr2_new_transmission(log).is_ok_and(|confirmed| {
!confirmed.removed
&& confirmed.aggregator == report.aggregator
&& confirmed.answer == report.median_answer
&& confirmed.observations_timestamp == report.observations_timestamp
})
}
}
pub(crate) fn adapter_owns_update(
adapter_id: &OracleAdapterId,
update: &PendingOracleUpdate,
) -> bool {
match &update.evidence {
PendingOracleEvidence::ChainlinkReport(_) => {
adapter_id.as_str() == ChainlinkPendingAdapter::ID
}
PendingOracleEvidence::Adapter {
adapter_id: owner, ..
} => owner == adapter_id,
}
}