use crate::account::AccountView;
use crate::address::Address;
use crate::borrow::Ref;
use crate::error::ProgramError;
use crate::layout::{HopperHeader, LayoutContract};
use crate::zerocopy::{AccountLayout, ZeroCopy};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ForeignManifest {
pub program_id: Address,
pub expected_disc: u8,
pub expected_wire_fp: u64,
pub supported_epochs: core::ops::RangeInclusive<u32>,
}
impl ForeignManifest {
pub const fn single_epoch(
program_id: Address,
expected_disc: u8,
expected_wire_fp: u64,
epoch: u32,
) -> Self {
Self {
program_id,
expected_disc,
expected_wire_fp,
supported_epochs: epoch..=epoch,
}
}
}
pub struct ForeignLens<'a, T: AccountLayout + LayoutContract> {
inner: Ref<'a, T>,
}
impl<'a, T: AccountLayout + LayoutContract> ForeignLens<'a, T> {
#[inline]
pub fn open(
account: &'a AccountView,
manifest: &ForeignManifest,
) -> Result<Self, ProgramError> {
account.check_owned_by(&manifest.program_id)?;
let loaded: Ref<'a, T> = account.load::<T>()?;
if <T as AccountLayout>::DISC != manifest.expected_disc {
return Err(ProgramError::InvalidAccountData);
}
let data = account.try_borrow()?;
let header = HopperHeader::from_bytes(&data)
.ok_or(ProgramError::AccountDataTooSmall)?;
let layout_id = header.layout_id;
let schema_epoch = header.schema_epoch;
let actual_wire_fp = u64::from_le_bytes(layout_id);
if actual_wire_fp != manifest.expected_wire_fp {
return Err(ProgramError::InvalidAccountData);
}
if actual_wire_fp != <T as AccountLayout>::WIRE_FINGERPRINT {
return Err(ProgramError::InvalidAccountData);
}
if !manifest.supported_epochs.contains(&schema_epoch) {
return Err(ProgramError::InvalidAccountData);
}
drop(data);
Ok(Self { inner: loaded })
}
#[inline(always)]
pub fn get(&self) -> &T {
&self.inner
}
#[inline(always)]
pub fn field<F: ZeroCopy, const OFFSET: usize>(&self) -> Result<&F, ProgramError> {
let body_size = core::mem::size_of::<T>();
let field_size = core::mem::size_of::<F>();
if OFFSET.checked_add(field_size).map(|end| end > body_size).unwrap_or(true) {
return Err(ProgramError::AccountDataTooSmall);
}
let layout_ref: &T = &*self.inner;
unsafe {
let base = layout_ref as *const T as *const u8;
let field_ptr = base.add(OFFSET) as *const F;
Ok(&*field_ptr)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn manifest_single_epoch_is_inclusive_single_value() {
let program = Address::new_from_array([7u8; 32]);
let m = ForeignManifest::single_epoch(program, 42, 0xDEAD_BEEF_1234_5678, 3);
assert!(m.supported_epochs.contains(&3));
assert!(!m.supported_epochs.contains(&2));
assert!(!m.supported_epochs.contains(&4));
assert_eq!(m.expected_disc, 42);
assert_eq!(m.expected_wire_fp, 0xDEAD_BEEF_1234_5678);
}
#[test]
fn manifest_range_spans_inclusive() {
let program = Address::new_from_array([0u8; 32]);
let m = ForeignManifest {
program_id: program,
expected_disc: 1,
expected_wire_fp: 0,
supported_epochs: 2..=5,
};
for ok in [2u32, 3, 4, 5] {
assert!(m.supported_epochs.contains(&ok), "{ok}");
}
for fail in [0u32, 1, 6, 100] {
assert!(!m.supported_epochs.contains(&fail), "{fail}");
}
}
}