use core::ptr::read_unaligned;
use anyhow::{Context, Error as AnyError};
use solana_define_syscall::definitions::sol_memcpy_;
use solana_program::account_info::AccountInfo;
use solana_program::ed25519_program::ID as ED25519_PROGRAM_ID;
use solana_program::sysvar::clock::Clock;
use crate::{check_pubkey_eq, Instructions};
#[allow(unused)]
const SLOTS_PER_EPOCH: u64 = 432_000;
pub const QUOTE_DISCRIMINATOR: [u8; 8] = *b"SBOracle";
pub const QUOTE_DISCRIMINATOR_U64_LE: u64 = u64::from_le_bytes(QUOTE_DISCRIMINATOR);
#[derive(Clone, Copy)]
pub struct OracleQuote<'a> {
quote_header_refs: &'a crate::on_demand::oracle_quote::feed_info::PackedQuoteHeader,
pub oracle_count: u8,
pub packed_feed_infos: &'a [crate::on_demand::oracle_quote::feed_info::PackedFeedInfo],
feed_count: u8,
pub oracle_idxs: &'a [u8],
pub recent_slot: u64,
pub version: u8,
pub raw_buffer: Option<&'a [u8]>,
}
impl<'a> OracleQuote<'a> {
#[inline(always)]
pub(crate) fn new(
quote_header_ref: &'a crate::on_demand::oracle_quote::feed_info::PackedQuoteHeader,
oracle_count: u8,
packed_feed_infos: &'a [crate::on_demand::oracle_quote::feed_info::PackedFeedInfo],
feed_count: u8,
oracle_idxs: &'a [u8],
recent_slot: u64,
version: u8,
raw_buffer: Option<&'a [u8]>,
) -> Self {
Self {
quote_header_refs: quote_header_ref,
oracle_count,
packed_feed_infos,
feed_count,
oracle_idxs,
recent_slot,
version,
raw_buffer,
}
}
#[inline(always)]
pub fn slot(&self) -> u64 {
self.recent_slot
}
#[inline(always)]
pub fn version(&self) -> u8 {
self.version
}
#[inline(always)]
pub fn raw_data(&self) -> Option<&[u8]> {
self.raw_buffer
}
#[inline(always)]
pub fn feeds(&self) -> &[crate::on_demand::oracle_quote::feed_info::PackedFeedInfo] {
&self.packed_feed_infos[..self.feed_count as usize]
}
#[inline(always)]
pub fn len(&self) -> usize {
self.feed_count as usize
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.feed_count == 0
}
#[inline(always)]
pub fn oracle_index(&self, signature_index: usize) -> Result<u8, AnyError> {
if signature_index < self.oracle_count as usize {
Ok(self.oracle_idxs[signature_index])
} else {
anyhow::bail!(
"Invalid signature index {} for quote with {} oracles",
signature_index,
self.oracle_count
)
}
}
#[inline(always)]
pub fn header(&self) -> &'a crate::on_demand::oracle_quote::feed_info::PackedQuoteHeader {
self.quote_header_refs
}
#[inline(always)]
pub fn feed(
&self,
feed_id: &[u8; 32],
) -> std::result::Result<&crate::on_demand::oracle_quote::feed_info::PackedFeedInfo, AnyError>
{
let info = self.packed_feed_infos[..self.feed_count as usize]
.iter()
.find(|info| info.feed_id() == feed_id)
.context("Switchboard On-Demand FeedNotFound")?;
Ok(info)
}
#[inline(always)]
pub fn store_delimited(clock: &Clock, source: &[u8], dst: &mut [u8]) {
Self::validate_slot_progression(clock, source, dst);
unsafe {
let dst_ptr = dst.as_mut_ptr();
let data_len = source.len();
assert!(data_len + 2 <= dst.len()); *(dst_ptr as *mut u16) = data_len as u16;
sol_memcpy_(dst_ptr.add(2), source.as_ptr(), data_len as u64);
}
}
#[inline(always)]
fn validate_slot_progression(clock: &Clock, source: &[u8], existing_data: &[u8]) {
let source_len = source.len();
if source_len < 13 {
panic!("Invalid source data length: {}", source_len);
}
unsafe {
let slot_offset = source_len - 13;
let new_slot = read_unaligned(source.as_ptr().add(slot_offset) as *const u64);
assert!(
new_slot < clock.slot,
"SB oracle slot is stale new_slot: {}, clock.slot: {}",
new_slot,
clock.slot
);
if existing_data.len() >= 13 {
let existing_slot_offset = existing_data.len() - 13;
let existing_slot =
read_unaligned(existing_data.as_ptr().add(existing_slot_offset) as *const u64);
assert!(
new_slot >= existing_slot,
"SB oracle slot regression new_slot: {}, existing_slot: {}",
new_slot,
existing_slot
);
}
}
}
#[inline(always)]
pub fn write(clock: &Clock, source: &[u8], oracle_account: &AccountInfo) {
unsafe {
let dst: &mut [u8] = *oracle_account.data.as_ptr();
assert!(dst.len() >= 23); let dst_ptr = dst.as_mut_ptr();
*(dst_ptr as *mut u64) = QUOTE_DISCRIMINATOR_U64_LE;
Self::store_delimited(clock, source, &mut dst[8..]);
}
}
#[inline(always)]
pub fn write_from_ix<I, O>(
ix_sysvar: I,
oracle_account: O,
clock: &Clock,
instruction_index: usize,
) where
I: AsRef<AccountInfo<'a>>,
O: AsRef<AccountInfo<'a>>,
{
let ix_sysvar = ix_sysvar.as_ref();
let oracle_account = oracle_account.as_ref();
let (program_id, data) = Instructions::extract_ix_data(ix_sysvar, instruction_index);
assert!(check_pubkey_eq(program_id, &ED25519_PROGRAM_ID));
Self::write(clock, data, oracle_account);
}
}