use crate::prelude::*;
#[cfg(feature = "anchor")]
use anchor_lang::solana_program;
use anyhow::{bail, Error as AnyError};
use solana_program::account_info::AccountInfo;
use solana_program::sysvar::instructions::get_instruction_relative;
#[allow(unused_imports)]
use crate::{ON_DEMAND_MAINNET_PID, ON_DEMAND_DEVNET_PID};
const SYSVAR_SLOT_LEN: u64 = 512;
#[cfg(not(feature = "devnet"))]
const EXPECTED_PREFIX: [u8; 64] = {
let mut prefix = [0u8; 64];
let pid_bytes = ON_DEMAND_MAINNET_PID.to_bytes();
let sysvar_bytes = solana_program::sysvar::slot_hashes::ID.to_bytes();
let mut i = 0;
while i < 32 {
prefix[i] = pid_bytes[i];
i += 1;
}
i = 0;
while i < 32 {
prefix[32 + i] = sysvar_bytes[i];
i += 1;
}
prefix
};
#[cfg(feature = "devnet")]
const EXPECTED_PREFIX: [u8; 64] = {
let mut prefix = [0u8; 64];
let pid_bytes = ON_DEMAND_DEVNET_PID.to_bytes();
let sysvar_bytes = solana_program::sysvar::slot_hashes::ID.to_bytes();
let mut i = 0;
while i < 32 {
prefix[i] = pid_bytes[i];
i += 1;
}
i = 0;
while i < 32 {
prefix[32 + i] = sysvar_bytes[i];
i += 1;
}
prefix
};
pub struct BundleVerifier<'info, 'a> {
pub queue: &'a AccountInfo<'info>,
pub slothash_sysvar: &'a AccountInfo<'info>,
pub ix_sysvar: &'a AccountInfo<'info>,
pub max_age: u64,
}
#[derive(Copy, Clone)]
pub struct BundleVerifierBuilder<'info, 'a> {
queue: *const AccountInfo<'info>,
slothash_sysvar: *const AccountInfo<'info>,
ix_sysvar: *const AccountInfo<'info>,
max_age: u64,
_phantom: core::marker::PhantomData<(&'a (), &'info ())>,
}
impl<'info, 'a> Default for BundleVerifierBuilder<'info, 'a> {
fn default() -> Self {
Self::new()
}
}
impl<'info, 'a> BundleVerifierBuilder<'info, 'a> {
#[inline(always)]
pub fn new() -> Self {
unsafe { core::mem::zeroed() }
}
#[inline(always)]
pub fn queue<T>(&mut self, account: &'a T) -> &mut Self
where
T: AsRef<AccountInfo<'info>>,
{
self.queue = account.as_ref() as *const _;
self
}
#[inline(always)]
pub fn slothash_sysvar<T>(&mut self, sysvar: &'a T) -> &mut Self
where
T: AsRef<AccountInfo<'info>>,
{
self.slothash_sysvar = sysvar.as_ref() as *const _;
self
}
#[inline(always)]
pub fn ix_sysvar<T>(&mut self, sysvar: &'a T) -> &mut Self
where
T: AsRef<AccountInfo<'info>>,
{
self.ix_sysvar = sysvar.as_ref() as *const _;
self
}
#[inline(always)]
pub fn max_age(&mut self, max_age: u64) -> &mut Self {
self.max_age = max_age;
self
}
pub fn verify<'instr>(
&self,
instruction_data: &'instr [u8],
) -> Result<VerifiedBundle<'instr>, AnyError> {
let queue = unsafe { &*self.queue };
let slothash_sysvar = unsafe { &*self.slothash_sysvar };
use crate::sysvar::ed25519_sysvar::Ed25519Sysvar;
let (parsed_sigs, sig_count, oracle_idxs, recent_slot, version) =
Ed25519Sysvar::parse_instruction_zero_copy(instruction_data)?;
if sig_count == 0 {
bail!("No signatures provided");
}
let queue_buf = queue.data.borrow();
let queue_data: &QueueAccountData = bytemuck::from_bytes(&queue_buf[8..]);
let reference_sig = &parsed_sigs[0];
let header = unsafe { reference_sig.bundle_header() };
let target_slothash = header.signed_slothash;
let found_slothash = Self::find_slothash_in_sysvar(recent_slot, slothash_sysvar)?;
let mut validation_data = [0u8; 352];
let mut expected_data = [0u8; 352];
let mut offset = 0;
validation_data[0..32].copy_from_slice(&queue.owner.to_bytes());
validation_data[32..64].copy_from_slice(&slothash_sysvar.key.to_bytes());
expected_data[0..64].copy_from_slice(&EXPECTED_PREFIX);
offset += 64;
validation_data[offset..offset+32].copy_from_slice(&found_slothash);
expected_data[offset..offset+32].copy_from_slice(&target_slothash);
offset += 32;
for i in 0..sig_count {
let oracle_idx = (oracle_idxs[i as usize] as usize) % 30; let expected_oracle_key = &queue_data.ed25519_oracle_signing_keys[oracle_idx];
let actual_oracle_key = unsafe { *parsed_sigs[i as usize].pubkey() };
validation_data[offset..offset+32].copy_from_slice(&actual_oracle_key);
expected_data[offset..offset+32].copy_from_slice(&expected_oracle_key.to_bytes());
offset += 32;
}
assert!(solana_program::program_memory::sol_memcmp(
&validation_data,
&expected_data,
offset,
) == 0);
let reference_feed_infos = unsafe { reference_sig.feed_infos() };
let feed_count = reference_feed_infos.len();
Ok(VerifiedBundle::new(
unsafe { reference_sig.bundle_header() },
sig_count,
reference_feed_infos,
feed_count as u8,
oracle_idxs,
recent_slot,
version,
))
}
fn find_slothash_in_sysvar(
target_slot: u64,
slothash_sysvar: &AccountInfo,
) -> Result<[u8; 32], AnyError> {
let slothash_data = slothash_sysvar.data.borrow();
let slot_data: &[SlotHash] = unsafe { std::mem::transmute(&slothash_data[8..]) };
let mut estimated_idx = ((slot_data[0].slot - target_slot) % SYSVAR_SLOT_LEN) as usize;
loop {
let slot_entry = &slot_data[estimated_idx];
if slot_entry.slot == target_slot {
return Ok(slot_entry.hash);
}
if estimated_idx == 0 {
break;
}
estimated_idx -= 1;
}
bail!("Slot not found in slothash sysvar");
}
}
#[inline(always)]
pub fn get_ed25519_instruction<'a, T>(
ix_sysvar: &T,
) -> Result<solana_program::instruction::Instruction, solana_program::program_error::ProgramError>
where
T: AsRef<AccountInfo<'a>>,
{
get_instruction_relative(-1, ix_sysvar.as_ref())
}