use anyhow::{Context, Error as AnyError};
use core::ptr::read_unaligned;
use solana_define_syscall::definitions::sol_memcpy_;
use solana_program::sysvar::clock::Clock;
use solana_program::account_info::AccountInfo;
use solana_program::ed25519_program::ID as ED25519_PROGRAM_ID;
use solana_program::sysvar::instructions::ID as INSTRUCTIONS_SYSVAR_ID;
use crate::Instructions;
use crate::check_pubkey_eq;
use crate::QuoteVerifier;
#[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)]
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);
Self::validate_slot_progression(clock, source, dst);
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(
ix_sysvar: &AccountInfo<'a>,
oracle_account: &AccountInfo,
clock: &Clock,
instruction_index: usize,
) {
let (program_id, data) = Instructions::extract_ix_data(ix_sysvar, instruction_index)
.expect("Failed to extract instruction data");
assert!(
check_pubkey_eq(ix_sysvar.key, &INSTRUCTIONS_SYSVAR_ID) &&
check_pubkey_eq(program_id, &ED25519_PROGRAM_ID));
Self::write(clock, data, oracle_account);
}
#[inline(always)]
pub fn load_verified_from_ix<'b>(
ix_sysvar: &'b AccountInfo<'b>,
queue: &'b AccountInfo<'b>,
slothash_sysvar: &'b AccountInfo<'b>,
max_age: u64,
instruction_index: usize,
) -> Result<OracleQuote<'b>, AnyError> {
let (program_id, data) = Instructions::extract_ix_data(ix_sysvar, instruction_index)
.map_err(|e| anyhow::anyhow!("Failed to extract instruction data: {:?}", e))?;
assert!(
check_pubkey_eq(ix_sysvar.key, &INSTRUCTIONS_SYSVAR_ID) &&
check_pubkey_eq(program_id, &ED25519_PROGRAM_ID));
let mut verifier = QuoteVerifier::new();
verifier
.queue(queue)
.slothash_sysvar(slothash_sysvar)
.ix_sysvar(ix_sysvar)
.max_age(max_age);
verifier.verify(data)
}
}