use hopper_runtime::error::ProgramError;
use hopper_runtime::{AccountView, Address, Ref};
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum TrustLevel {
Strict,
Compatible,
Observational,
}
#[derive(Clone, Copy)]
pub struct TrustFlags {
pub reject_closed: bool,
pub require_immutable: bool,
pub min_version: u8,
}
impl TrustFlags {
#[inline(always)]
pub const fn default() -> Self {
Self {
reject_closed: true,
require_immutable: false,
min_version: 0,
}
}
#[inline(always)]
pub const fn paranoid() -> Self {
Self {
reject_closed: true,
require_immutable: true,
min_version: 0,
}
}
}
pub struct TrustProfile<'a> {
pub owner: &'a Address,
pub layout_id: &'a [u8; 8],
pub size: usize,
pub level: TrustLevel,
pub flags: TrustFlags,
}
impl<'a> TrustProfile<'a> {
#[inline(always)]
pub const fn strict(owner: &'a Address, layout_id: &'a [u8; 8], size: usize) -> Self {
Self {
owner,
layout_id,
size,
level: TrustLevel::Strict,
flags: TrustFlags::default(),
}
}
#[inline(always)]
pub const fn compatible(owner: &'a Address, layout_id: &'a [u8; 8], min_size: usize) -> Self {
Self {
owner,
layout_id,
size: min_size,
level: TrustLevel::Compatible,
flags: TrustFlags::default(),
}
}
#[inline(always)]
pub const fn observational(layout_id: &'a [u8; 8]) -> Self {
const ZERO_ADDR: Address = Address::new_from_array([0u8; 32]);
Self {
owner: &ZERO_ADDR,
layout_id,
size: 0,
level: TrustLevel::Observational,
flags: TrustFlags {
reject_closed: false,
require_immutable: false,
min_version: 0,
},
}
}
#[inline(always)]
pub const fn read_only(owner: &'a Address, layout_id: &'a [u8; 8], min_size: usize) -> Self {
Self {
owner,
layout_id,
size: min_size,
level: TrustLevel::Compatible,
flags: TrustFlags {
reject_closed: true,
require_immutable: true,
min_version: 0,
},
}
}
#[inline(always)]
pub const fn with_min_version(mut self, v: u8) -> Self {
self.flags.min_version = v;
self
}
#[inline(always)]
pub const fn require_immutable(mut self) -> Self {
self.flags.require_immutable = true;
self
}
#[inline]
pub fn load(&self, account: &'a AccountView) -> Result<Ref<'a, [u8]>, ProgramError> {
if self.flags.require_immutable && account.is_writable() {
return Err(ProgramError::InvalidAccountData);
}
match self.level {
TrustLevel::Strict => self.load_strict(account),
TrustLevel::Compatible => self.load_compatible(account),
TrustLevel::Observational => self.load_observational(account),
}
}
#[inline]
fn load_strict(&self, account: &'a AccountView) -> Result<Ref<'a, [u8]>, ProgramError> {
if !account.owned_by(self.owner) {
return Err(ProgramError::IncorrectProgramId);
}
let data = account.try_borrow()?;
if data.len() != self.size {
return Err(ProgramError::AccountDataTooSmall);
}
self.check_layout_id(&data)?;
if self.flags.reject_closed {
self.check_not_closed(&data)?;
}
if self.flags.min_version > 0 {
self.check_min_version(&data)?;
}
Ok(data)
}
#[inline]
fn load_compatible(&self, account: &'a AccountView) -> Result<Ref<'a, [u8]>, ProgramError> {
if !account.owned_by(self.owner) {
return Err(ProgramError::IncorrectProgramId);
}
let data = account.try_borrow()?;
if data.len() < self.size {
return Err(ProgramError::AccountDataTooSmall);
}
self.check_layout_id(&data)?;
if self.flags.reject_closed {
self.check_not_closed(&data)?;
}
if self.flags.min_version > 0 {
self.check_min_version(&data)?;
}
Ok(data)
}
#[inline]
fn load_observational(&self, account: &'a AccountView) -> Result<Ref<'a, [u8]>, ProgramError> {
let data = account.try_borrow()?;
if data.len() < crate::account::HEADER_LEN {
return Err(ProgramError::AccountDataTooSmall);
}
self.check_layout_id(&data)?;
Ok(data)
}
#[inline(always)]
fn check_layout_id(&self, data: &[u8]) -> Result<(), ProgramError> {
if data.len() < 12 {
return Err(ProgramError::AccountDataTooSmall);
}
if data[4..12] != *self.layout_id {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
#[inline(always)]
fn check_not_closed(&self, data: &[u8]) -> Result<(), ProgramError> {
if !data.is_empty() && data[0] == crate::account::CLOSE_SENTINEL {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
#[inline(always)]
fn check_min_version(&self, data: &[u8]) -> Result<(), ProgramError> {
if data.len() < 2 {
return Err(ProgramError::AccountDataTooSmall);
}
if data[1] < self.flags.min_version {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
}
#[inline]
pub fn load_foreign_with_profile<'a, T: crate::account::Pod + crate::account::FixedLayout>(
account: &'a AccountView,
profile: &TrustProfile<'a>,
) -> Result<crate::account::VerifiedAccount<'a, T>, ProgramError> {
let data = profile.load(account)?;
crate::account::VerifiedAccount::from_ref(data)
}