switchboard-on-demand 0.4.5

A Rust library to interact with the Switchboard Solana program.
Documentation
#[cfg(feature = "anchor")]
use anchor_lang::solana_program;
use anyhow::{bail, Context, Error as AnyError};
use solana_program::account_info::AccountInfo;
use solana_program::sysvar::clock::Clock;

use crate::prelude::*;
use crate::slothash_sysvar::SlotHashes;

pub struct BundleVerifier<'info, 'a> {
    pub queue: &'a AccountInfo<'info>,
    pub slothash_sysvar: &'a AccountInfo<'info>,
    pub ix_sysvar: &'a AccountInfo<'info>,
    pub clock: &'a Clock,
    pub max_age: i32,
    pub bundle: &'a [u8],
}

pub struct BundleVerifierBuilder<'info, 'a> {
    queue: Option<&'a AccountInfo<'info>>,
    slothash_sysvar: Option<&'a AccountInfo<'info>>,
    ix_sysvar: Option<&'a AccountInfo<'info>>,
    clock: Option<&'a Clock>,
    max_age: Option<i32>,
    bundle: Option<&'a [u8]>,
}

impl<'info, 'a> Default for BundleVerifierBuilder<'info, 'a> {
    fn default() -> Self {
        Self {
            queue: None,
            slothash_sysvar: None,
            ix_sysvar: None,
            clock: None,
            max_age: None,
            bundle: None,
        }
    }
}

impl<'info, 'a> BundleVerifierBuilder<'info, 'a> {
    pub fn new() -> Self {
        BundleVerifierBuilder::default()
    }

    pub fn queue<T>(mut self, account: &'a T) -> Self
    where
        T: AsRef<AccountInfo<'info>>,
    {
        self.queue = Some(account.as_ref());
        self
    }

    pub fn slothash_sysvar<T>(mut self, sysvar: &'a T) -> Self
    where
        T: AsRef<AccountInfo<'info>>,
    {
        self.slothash_sysvar = Some(sysvar.as_ref());
        self
    }

    pub fn ix_sysvar<T>(mut self, sysvar: &'a T) -> Self
    where
        T: AsRef<AccountInfo<'info>>,
    {
        self.ix_sysvar = Some(sysvar.as_ref());
        self
    }

    pub fn clock(mut self, clock: &'a Clock) -> Self {
        self.clock = Some(clock);
        self
    }

    pub fn max_age(mut self, max_age: i32) -> Self {
        self.max_age = Some(max_age);
        self
    }

    pub fn bundle(mut self, bundle: &'a [u8]) -> Self {
        self.bundle = Some(bundle);
        self
    }

    pub fn verify(&self) -> Result<VerifiedBundle, AnyError> {
        if self.bundle.is_none() {
            bail!("Switchboard On-Demand MissingBuffer");
        }
        if self.queue.is_none() {
            bail!("Switchboard On-Demand MissingQueue");
        }
        if self.slothash_sysvar.is_none() {
            bail!("Switchboard On-Demand MissingSlothashSysvar");
        }
        if self.ix_sysvar.is_none() {
            bail!("Switchboard On-Demand MissingIxSysvar");
        }
        if self.clock.is_none() {
            bail!("Switchboard On-Demand MissingClock");
        }
        let buf = self.bundle.unwrap();
        if buf.len() < 1 + FeedInfo::PACKED_SIZE {
            bail!("Switchboard On-Demand InvalidBundleLength");
        }
        let slot_lower: u8 = buf[0];
        let slothash =
            SlotHashes::get_slothash_from_lower_byte(self.slothash_sysvar.unwrap(), slot_lower)
                .context("Failed to get slot from slothash sysvar")?;
        let max_age = self.max_age.unwrap_or_default();
        if max_age < 0 {
            bail!("Switchboard On-Demand InvalidMaxAge");
        }
        let slot = slothash.slot;
        if self.clock.unwrap().slot - slot > self.max_age.unwrap_or_default() as u64 {
            bail!("Switchboard On-Demand BundleTooOld");
        }
        let hash = slothash.hash;
        let buf = &buf[1..];
        if buf.len() % FeedInfo::PACKED_SIZE != 0 {
            bail!("Switchboard On-Demand InvalidFeedInfoLength");
        }
        let feed_infos = buf
            .chunks(FeedInfo::PACKED_SIZE)
            .map(|buf| FeedInfo::from_packed(buf))
            .flatten()
            .collect::<Vec<FeedInfo>>();
        let mut bundle = VerifiedBundle {
            slot_lower,
            feed_infos,
            verified_slot: slot,
            verified_slothash: hash,
            verification_count: 0,
        };
        bundle.verify(
            self.queue.unwrap(),
            self.slothash_sysvar.unwrap(),
            self.ix_sysvar.unwrap(),
        )?;
        Ok(bundle)
    }
}