use crate::on_demand::oracle_quote::feed_info::{PackedFeedInfo, PackedQuoteHeader};
#[cfg(feature = "anchor")]
use crate::on_demand::oracle_quote::instruction_parser::ParsedQuotePayload;
use crate::on_demand::oracle_quote::authority_quote::QuoteSourceScheme;
use crate::smallvec::{SmallVec, U16Prefix, U8Prefix};
use crate::sysvar::ed25519_sysvar::Ed25519SignatureOffsets;
use crate::Pubkey;
pub const QUOTE_DISCRIMINATOR: &[u8; 8] = b"SBOracle";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct OracleSignature {
pub offsets: Ed25519SignatureOffsets,
pub pubkey: Pubkey,
pub signature: [u8; 64],
}
impl borsh::BorshSerialize for OracleSignature {
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
self.offsets.serialize(writer)?;
writer.write_all(self.pubkey.as_ref())?;
writer.write_all(&self.signature)?;
Ok(())
}
}
impl borsh::BorshDeserialize for OracleSignature {
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
let offsets = Ed25519SignatureOffsets::deserialize_reader(reader)?;
let mut pubkey_bytes = [0u8; 32];
reader.read_exact(&mut pubkey_bytes)?;
let pubkey = Pubkey::new_from_array(pubkey_bytes);
let mut signature = [0u8; 64];
reader.read_exact(&mut signature)?;
Ok(Self {
offsets,
pubkey,
signature,
})
}
}
#[cfg(feature = "anchor")]
impl anchor_lang::AnchorDeserialize for OracleSignature {
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
<Self as borsh::BorshDeserialize>::deserialize_reader(reader)
}
}
#[cfg(feature = "anchor")]
impl anchor_lang::AnchorSerialize for OracleSignature {
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
<Self as borsh::BorshSerialize>::serialize(self, writer)
}
}
#[cfg(feature = "idl-build")]
impl anchor_lang::IdlBuild for OracleSignature {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SwitchboardQuote {
pub queue: Pubkey,
pub signatures: SmallVec<OracleSignature, U16Prefix>,
pub quote_header: PackedQuoteHeader,
pub feeds: SmallVec<PackedFeedInfo, U8Prefix>,
pub oracle_idxs: SmallVec<u8, U8Prefix>,
pub slot: u64,
pub version: u8,
pub tail_discriminator: [u8; 4],
}
impl borsh::BorshSerialize for SwitchboardQuote {
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
writer.write_all(self.queue.as_ref())?;
self.signatures.serialize(writer)?;
self.quote_header.serialize(writer)?;
self.feeds.serialize(writer)?;
self.oracle_idxs.serialize(writer)?;
self.slot.serialize(writer)?;
self.version.serialize(writer)?;
self.tail_discriminator.serialize(writer)?;
Ok(())
}
}
impl borsh::BorshDeserialize for SwitchboardQuote {
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
let mut queue_bytes = [0u8; 32];
reader.read_exact(&mut queue_bytes)?;
Ok(Self {
queue: Pubkey::new_from_array(queue_bytes),
signatures: borsh::BorshDeserialize::deserialize_reader(reader)?,
quote_header: borsh::BorshDeserialize::deserialize_reader(reader)?,
feeds: borsh::BorshDeserialize::deserialize_reader(reader)?,
oracle_idxs: borsh::BorshDeserialize::deserialize_reader(reader)?,
slot: borsh::BorshDeserialize::deserialize_reader(reader)?,
version: borsh::BorshDeserialize::deserialize_reader(reader)?,
tail_discriminator: borsh::BorshDeserialize::deserialize_reader(reader)?,
})
}
}
#[cfg(feature = "anchor")]
impl anchor_lang::AnchorDeserialize for SwitchboardQuote {
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
<Self as borsh::BorshDeserialize>::deserialize_reader(reader)
}
}
#[cfg(feature = "anchor")]
impl anchor_lang::AnchorSerialize for SwitchboardQuote {
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
<Self as borsh::BorshSerialize>::serialize(self, writer)
}
}
#[cfg(feature = "anchor")]
impl anchor_lang::Discriminator for SwitchboardQuote {
const DISCRIMINATOR: &'static [u8] = QUOTE_DISCRIMINATOR;
}
#[cfg(feature = "anchor")]
impl anchor_lang::AccountSerialize for SwitchboardQuote {
fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> anchor_lang::Result<()> {
use anchor_lang::Discriminator;
writer.write_all(Self::DISCRIMINATOR)?;
writer.write_all(self.queue.as_ref())?;
let mut delimited_buf = Vec::new();
borsh::BorshSerialize::serialize(&self.signatures, &mut delimited_buf)
.map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotSerialize)?;
borsh::BorshSerialize::serialize(&self.quote_header, &mut delimited_buf)
.map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotSerialize)?;
borsh::BorshSerialize::serialize(&self.feeds, &mut delimited_buf)
.map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotSerialize)?;
borsh::BorshSerialize::serialize(&self.oracle_idxs, &mut delimited_buf)
.map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotSerialize)?;
borsh::BorshSerialize::serialize(&self.slot, &mut delimited_buf)
.map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotSerialize)?;
borsh::BorshSerialize::serialize(&self.version, &mut delimited_buf)
.map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotSerialize)?;
borsh::BorshSerialize::serialize(&self.tail_discriminator, &mut delimited_buf)
.map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotSerialize)?;
let len = delimited_buf.len() as u16;
writer.write_all(&len.to_le_bytes())?;
writer.write_all(&delimited_buf)?;
Ok(())
}
}
#[cfg(feature = "anchor")]
impl anchor_lang::AccountDeserialize for SwitchboardQuote {
fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
use anchor_lang::Discriminator;
if buf.len() < 40 {
return Err(anchor_lang::error::ErrorCode::AccountDiscriminatorNotFound.into());
}
let given_disc = &buf[..Self::DISCRIMINATOR.len()];
if given_disc != Self::DISCRIMINATOR {
return Err(anchor_lang::error::ErrorCode::AccountDiscriminatorMismatch.into());
}
let namespace = Pubkey::new_from_array(buf[8..40].try_into().unwrap());
let data = &buf[40..];
if data.len() < 2 {
return Err(anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into());
}
let len = u16::from_le_bytes([data[0], data[1]]) as usize;
if data.len() < len + 2 {
return Err(anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into());
}
let ed25519_data = &data[2..len + 2];
parse_switchboard_quote(namespace, ed25519_data)
.map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into())
}
fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
if buf.len() < 40 {
return Err(anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into());
}
let full_buf = *buf;
*buf = &[];
let namespace = Pubkey::new_from_array(full_buf[8..40].try_into().unwrap());
let data = &full_buf[40..];
if data.len() < 2 {
return Err(anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into());
}
let len = u16::from_le_bytes([data[0], data[1]]) as usize;
if data.len() < len + 2 {
return Err(anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into());
}
let ed25519_data = &data[2..len + 2];
parse_switchboard_quote(namespace, ed25519_data)
.map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into())
}
}
#[cfg(feature = "idl-build")]
impl anchor_lang::IdlBuild for SwitchboardQuote {}
impl SwitchboardQuote {
pub const MAX_LEN: usize = 32 + (2 + 8 * 110) + 32 + (1 + 8 * 49) + (1 + 8) + 8 + 1 + 4;
pub const MIN_LEN: usize = 32 + 2 + 32 + 1 + 1 + 8 + 1 + 4;
pub fn feeds_slice(&self) -> &[PackedFeedInfo] {
self.feeds.as_slice()
}
pub fn source_scheme(&self) -> QuoteSourceScheme {
let signed_slothash = self.quote_header.signed_slothash;
if self.signatures.is_empty() && self.oracle_idxs.is_empty() && signed_slothash == [0u8; 32]
{
QuoteSourceScheme::Authority
} else {
QuoteSourceScheme::Oracle
}
}
#[cfg(feature = "anchor")]
pub fn get_canonical_key(
queue_key: &anchor_lang::solana_program::pubkey::Pubkey,
feed_ids: &[&[u8; 32]],
program_id: &anchor_lang::solana_program::pubkey::Pubkey,
) -> anchor_lang::solana_program::pubkey::Pubkey {
let mut seeds: Vec<&[u8]> = Vec::with_capacity(feed_ids.len() + 1);
seeds.push(queue_key.as_ref());
for id in feed_ids {
seeds.push(id.as_slice());
}
let (oracle_account, _) =
anchor_lang::solana_program::pubkey::Pubkey::find_program_address(&seeds, program_id);
oracle_account
}
#[cfg(feature = "anchor")]
pub fn canonical_key(
&self,
queue_key: &anchor_lang::solana_program::pubkey::Pubkey,
owner: &anchor_lang::solana_program::pubkey::Pubkey,
) -> anchor_lang::solana_program::pubkey::Pubkey {
let feed_ids: Vec<&[u8; 32]> = self.feeds.iter().map(|feed| &feed.feed_id).collect();
Self::get_canonical_key(queue_key, &feed_ids, owner)
}
}
#[cfg(feature = "anchor")]
impl anchor_lang::Owner for SwitchboardQuote {
fn owner() -> anchor_lang::solana_program::pubkey::Pubkey {
crate::QUOTE_PROGRAM_ID
}
}
#[cfg(feature = "anchor")]
fn parse_switchboard_quote(
namespace: Pubkey,
quote_data: &[u8],
) -> anyhow::Result<SwitchboardQuote> {
match ParsedQuotePayload::parse(quote_data)? {
ParsedQuotePayload::Oracle(parsed) => {
let signatures = parsed
.signatures
.into_iter()
.map(|sig| OracleSignature {
offsets: sig.offsets,
pubkey: Pubkey::new_from_array(sig.pubkey),
signature: sig.signature,
})
.collect::<Vec<_>>()
.try_into()
.map_err(|_| anyhow::anyhow!("Too many quote signatures"))?;
let feeds = parsed
.feeds
.into_iter()
.collect::<Vec<_>>()
.try_into()
.map_err(|_| anyhow::anyhow!("Too many quote feeds"))?;
let oracle_idxs = parsed
.oracle_idxs
.into_iter()
.collect::<Vec<_>>()
.try_into()
.map_err(|_| anyhow::anyhow!("Too many oracle indexes"))?;
Ok(SwitchboardQuote {
queue: namespace,
signatures,
quote_header: parsed.quote_header,
feeds,
oracle_idxs,
slot: parsed.slot,
version: parsed.version,
tail_discriminator: parsed.discriminator,
})
}
ParsedQuotePayload::Authority(parsed) => {
let quote_header = parsed.quote_header();
let feeds = parsed
.feeds
.into_iter()
.collect::<Vec<_>>()
.try_into()
.map_err(|_| anyhow::anyhow!("Too many authority quote feeds"))?;
Ok(SwitchboardQuote {
queue: namespace,
signatures: Vec::<OracleSignature>::new()
.try_into()
.map_err(|_| anyhow::anyhow!("Failed to build empty signature set"))?,
quote_header,
feeds,
oracle_idxs: Vec::<u8>::new()
.try_into()
.map_err(|_| anyhow::anyhow!("Failed to build empty oracle index set"))?,
slot: parsed.slot,
version: parsed.version,
tail_discriminator: parsed.tail_discriminator,
})
}
}
}