#[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)
}
}