use core::marker::PhantomData;
use crate::account::AccountView;
use crate::address::Address;
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct Signer<'info> {
inner: &'info AccountView,
}
impl<'info> Signer<'info> {
#[inline(always)]
pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
Self { inner: view }
}
#[inline]
pub fn try_new(view: &'info AccountView) -> Result<Self, crate::error::ProgramError> {
view.check_signer()?;
Ok(Self { inner: view })
}
#[inline(always)]
pub fn as_account(&self) -> &'info AccountView {
self.inner
}
#[inline(always)]
pub fn key(&self) -> &Address {
self.inner.address()
}
}
impl<'info> core::ops::Deref for Signer<'info> {
type Target = AccountView;
#[inline(always)]
fn deref(&self) -> &AccountView {
self.inner
}
}
#[repr(transparent)]
pub struct Account<'info, T: crate::layout::LayoutContract> {
inner: &'info AccountView,
_ty: PhantomData<T>,
}
impl<'info, T: crate::layout::LayoutContract> Clone for Account<'info, T> {
fn clone(&self) -> Self {
*self
}
}
impl<'info, T: crate::layout::LayoutContract> Copy for Account<'info, T> {}
impl<'info, T: crate::layout::LayoutContract> Account<'info, T> {
#[inline(always)]
pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
Self {
inner: view,
_ty: PhantomData,
}
}
#[inline]
pub fn try_new(
view: &'info AccountView,
owner: &Address,
) -> Result<Self, crate::error::ProgramError> {
view.check_owned_by(owner)?;
let _ = view.load::<T>()?;
Ok(Self {
inner: view,
_ty: PhantomData,
})
}
#[inline(always)]
pub fn as_account(&self) -> &'info AccountView {
self.inner
}
#[inline(always)]
pub fn key(&self) -> &Address {
self.inner.address()
}
#[inline(always)]
pub fn load(&self) -> Result<crate::borrow::Ref<'_, T>, crate::error::ProgramError> {
self.inner.load::<T>()
}
#[inline(always)]
pub fn get(&self) -> Result<crate::borrow::Ref<'_, T>, crate::error::ProgramError> {
self.load()
}
#[inline(always)]
pub fn load_mut(&self) -> Result<crate::borrow::RefMut<'_, T>, crate::error::ProgramError> {
self.inner.load_mut::<T>()
}
#[inline(always)]
pub fn get_mut(&self) -> Result<crate::borrow::RefMut<'_, T>, crate::error::ProgramError> {
self.load_mut()
}
}
impl<'info, T: crate::layout::LayoutContract> core::ops::Deref for Account<'info, T> {
type Target = AccountView;
#[inline(always)]
fn deref(&self) -> &AccountView {
self.inner
}
}
#[repr(transparent)]
pub struct InitAccount<'info, T: crate::layout::LayoutContract> {
inner: &'info AccountView,
_ty: PhantomData<T>,
}
impl<'info, T: crate::layout::LayoutContract> Clone for InitAccount<'info, T> {
fn clone(&self) -> Self {
*self
}
}
impl<'info, T: crate::layout::LayoutContract> Copy for InitAccount<'info, T> {}
impl<'info, T: crate::layout::LayoutContract> InitAccount<'info, T> {
#[inline(always)]
pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
Self {
inner: view,
_ty: PhantomData,
}
}
#[inline(always)]
pub fn as_account(&self) -> &'info AccountView {
self.inner
}
#[inline(always)]
pub fn key(&self) -> &Address {
self.inner.address()
}
#[inline(always)]
pub fn load_after_init(
&self,
) -> Result<crate::borrow::RefMut<'_, T>, crate::error::ProgramError> {
self.inner.load_mut::<T>()
}
#[inline(always)]
pub fn get_mut_after_init(
&self,
) -> Result<crate::borrow::RefMut<'_, T>, crate::error::ProgramError> {
self.load_after_init()
}
}
impl<'info, T: crate::layout::LayoutContract> core::ops::Deref for InitAccount<'info, T> {
type Target = AccountView;
#[inline(always)]
fn deref(&self) -> &AccountView {
self.inner
}
}
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct UncheckedAccount<'info> {
inner: &'info AccountView,
}
impl<'info> UncheckedAccount<'info> {
#[inline(always)]
pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
Self { inner: view }
}
#[inline(always)]
pub fn new(view: &'info AccountView) -> Self {
Self { inner: view }
}
#[inline(always)]
pub fn as_account(&self) -> &'info AccountView {
self.inner
}
#[inline(always)]
pub fn key(&self) -> &Address {
self.inner.address()
}
}
impl<'info> core::ops::Deref for UncheckedAccount<'info> {
type Target = AccountView;
#[inline(always)]
fn deref(&self) -> &AccountView {
self.inner
}
}
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct SystemAccount<'info> {
inner: &'info AccountView,
}
impl<'info> SystemAccount<'info> {
#[inline]
pub fn try_new(view: &'info AccountView) -> Result<Self, crate::error::ProgramError> {
view.check_owned_by(&SystemId::ID)?;
Ok(Self { inner: view })
}
#[inline(always)]
pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
Self { inner: view }
}
#[inline(always)]
pub fn as_account(&self) -> &'info AccountView {
self.inner
}
#[inline(always)]
pub fn key(&self) -> &Address {
self.inner.address()
}
}
impl<'info> core::ops::Deref for SystemAccount<'info> {
type Target = AccountView;
#[inline(always)]
fn deref(&self) -> &AccountView {
self.inner
}
}
#[repr(transparent)]
pub struct Program<'info, P: ProgramId> {
inner: &'info AccountView,
_ty: PhantomData<P>,
}
impl<'info, P: ProgramId> Clone for Program<'info, P> {
fn clone(&self) -> Self {
*self
}
}
impl<'info, P: ProgramId> Copy for Program<'info, P> {}
impl<'info, P: ProgramId> Program<'info, P> {
#[inline]
pub fn try_new(view: &'info AccountView) -> Result<Self, crate::error::ProgramError> {
if view.address() != &P::ID {
return Err(crate::error::ProgramError::IncorrectProgramId);
}
if !view.executable() {
return Err(crate::error::ProgramError::InvalidAccountData);
}
Ok(Self {
inner: view,
_ty: PhantomData,
})
}
#[inline(always)]
pub fn as_account(&self) -> &'info AccountView {
self.inner
}
#[inline(always)]
pub fn key(&self) -> &Address {
self.inner.address()
}
}
impl<'info, P: ProgramId> core::ops::Deref for Program<'info, P> {
type Target = AccountView;
#[inline(always)]
fn deref(&self) -> &AccountView {
self.inner
}
}
pub trait InterfaceSpec: 'static {
const IDS: &'static [Address];
#[inline(always)]
fn contains(program_id: &Address) -> bool {
Self::IDS.iter().any(|candidate| candidate == program_id)
}
}
pub trait InterfaceAccountLayout: crate::layout::LayoutContract {
type Interface: InterfaceSpec;
#[inline]
fn validate_interface_account(view: &AccountView) -> Result<(), crate::error::ProgramError> {
let _ = view.load_cross_program::<Self>()?;
Ok(())
}
}
pub trait InterfaceAccountResolve: InterfaceAccountLayout {
type Resolved<'a>
where
Self: 'a;
fn resolve<'a>(view: &'a AccountView)
-> Result<Self::Resolved<'a>, crate::error::ProgramError>;
}
#[repr(transparent)]
pub struct Interface<'info, I: InterfaceSpec> {
inner: &'info AccountView,
_ty: PhantomData<I>,
}
impl<'info, I: InterfaceSpec> Clone for Interface<'info, I> {
fn clone(&self) -> Self {
*self
}
}
impl<'info, I: InterfaceSpec> Copy for Interface<'info, I> {}
impl<'info, I: InterfaceSpec> Interface<'info, I> {
#[inline(always)]
pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
Self {
inner: view,
_ty: PhantomData,
}
}
#[inline]
pub fn try_new(view: &'info AccountView) -> Result<Self, crate::error::ProgramError> {
if !I::contains(view.address()) {
return Err(crate::error::ProgramError::IncorrectProgramId);
}
if !view.executable() {
return Err(crate::error::ProgramError::InvalidAccountData);
}
Ok(Self {
inner: view,
_ty: PhantomData,
})
}
#[inline(always)]
pub fn as_account(&self) -> &'info AccountView {
self.inner
}
#[inline(always)]
pub fn key(&self) -> &Address {
self.inner.address()
}
}
impl<'info, I: InterfaceSpec> core::ops::Deref for Interface<'info, I> {
type Target = AccountView;
#[inline(always)]
fn deref(&self) -> &AccountView {
self.inner
}
}
#[repr(transparent)]
pub struct InterfaceAccount<'info, T: InterfaceAccountLayout> {
inner: &'info AccountView,
_ty: PhantomData<T>,
}
impl<'info, T: InterfaceAccountLayout> Clone for InterfaceAccount<'info, T> {
fn clone(&self) -> Self {
*self
}
}
impl<'info, T: InterfaceAccountLayout> Copy for InterfaceAccount<'info, T> {}
impl<'info, T: InterfaceAccountLayout> InterfaceAccount<'info, T> {
#[inline(always)]
pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
Self {
inner: view,
_ty: PhantomData,
}
}
#[inline]
pub fn try_new(view: &'info AccountView) -> Result<Self, crate::error::ProgramError> {
let owner = view.read_owner();
if !<T::Interface as InterfaceSpec>::contains(&owner) {
return Err(crate::error::ProgramError::IncorrectProgramId);
}
T::validate_interface_account(view)?;
Ok(Self {
inner: view,
_ty: PhantomData,
})
}
#[inline(always)]
pub fn as_account(&self) -> &'info AccountView {
self.inner
}
#[inline(always)]
pub fn key(&self) -> &Address {
self.inner.address()
}
#[inline(always)]
pub fn owner(&self) -> Address {
self.inner.read_owner()
}
#[inline(always)]
pub fn load(&self) -> Result<crate::borrow::Ref<'_, T>, crate::error::ProgramError> {
self.inner.load_cross_program::<T>()
}
#[inline(always)]
pub fn get(&self) -> Result<crate::borrow::Ref<'_, T>, crate::error::ProgramError> {
self.load()
}
#[inline(always)]
pub fn load_as<U>(&self) -> Result<crate::borrow::Ref<'_, U>, crate::error::ProgramError>
where
U: InterfaceAccountLayout<Interface = <T as InterfaceAccountLayout>::Interface>,
{
self.inner.load_cross_program::<U>()
}
#[inline(always)]
pub fn get_as<U>(&self) -> Result<crate::borrow::Ref<'_, U>, crate::error::ProgramError>
where
U: InterfaceAccountLayout<Interface = <T as InterfaceAccountLayout>::Interface>,
{
self.load_as::<U>()
}
#[inline(always)]
pub fn is<U>(&self) -> bool
where
U: InterfaceAccountLayout<Interface = <T as InterfaceAccountLayout>::Interface>,
{
self.inner
.layout_info()
.is_some_and(|info| info.matches::<U>())
}
#[inline(always)]
pub fn resolve(&self) -> Result<T::Resolved<'_>, crate::error::ProgramError>
where
T: InterfaceAccountResolve,
{
T::resolve(self.inner)
}
}
impl<'info, T: InterfaceAccountLayout> core::ops::Deref for InterfaceAccount<'info, T> {
type Target = AccountView;
#[inline(always)]
fn deref(&self) -> &AccountView {
self.inner
}
}
pub trait ProgramId: 'static {
const ID: Address;
}
pub struct SystemId;
impl ProgramId for SystemId {
const ID: Address = Address::new_from_array([0u8; 32]);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn signer_wrapper_is_pointer_sized_zero_cost() {
assert_eq!(
core::mem::size_of::<Signer<'static>>(),
core::mem::size_of::<&'static AccountView>()
);
}
#[test]
fn system_program_id_is_all_zero() {
let sys = SystemId::ID;
assert_eq!(sys.as_array(), &[0u8; 32]);
}
}
#[cfg(all(test, feature = "hopper-native-backend"))]
mod resolver_tests {
use super::*;
use crate::layout::{HopperHeader, LayoutContract};
use hopper_native::{
AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
};
const PROGRAM_A: Address = Address::new_from_array([0xA1; 32]);
const PROGRAM_B: Address = Address::new_from_array([0xB2; 32]);
const OTHER_PROGRAM: Address = Address::new_from_array([0xCC; 32]);
struct VaultPrograms;
impl InterfaceSpec for VaultPrograms {
const IDS: &'static [Address] = &[PROGRAM_A, PROGRAM_B];
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
struct VaultV1 {
balance: u64,
}
impl crate::field_map::FieldMap for VaultV1 {
const FIELDS: &'static [crate::field_map::FieldInfo] = &[crate::field_map::FieldInfo::new(
"balance",
HopperHeader::SIZE,
8,
)];
}
impl LayoutContract for VaultV1 {
const DISC: u8 = 11;
const VERSION: u8 = 1;
const LAYOUT_ID: [u8; 8] = [0x11; 8];
const SIZE: usize = HopperHeader::SIZE + core::mem::size_of::<Self>();
}
impl InterfaceAccountLayout for VaultV1 {
type Interface = VaultPrograms;
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
struct VaultV2 {
balance: u64,
bump: u64,
}
impl crate::field_map::FieldMap for VaultV2 {
const FIELDS: &'static [crate::field_map::FieldInfo] = &[
crate::field_map::FieldInfo::new("balance", HopperHeader::SIZE, 8),
crate::field_map::FieldInfo::new("bump", HopperHeader::SIZE + 8, 8),
];
}
impl LayoutContract for VaultV2 {
const DISC: u8 = 12;
const VERSION: u8 = 2;
const LAYOUT_ID: [u8; 8] = [0x22; 8];
const SIZE: usize = HopperHeader::SIZE + core::mem::size_of::<Self>();
}
impl InterfaceAccountLayout for VaultV2 {
type Interface = VaultPrograms;
}
#[derive(Clone, Copy, Debug, Default)]
struct AnyVault;
impl crate::field_map::FieldMap for AnyVault {
const FIELDS: &'static [crate::field_map::FieldInfo] = &[];
}
impl LayoutContract for AnyVault {
const DISC: u8 = 0;
const VERSION: u8 = 0;
const LAYOUT_ID: [u8; 8] = [0; 8];
const SIZE: usize = HopperHeader::SIZE;
}
impl InterfaceAccountLayout for AnyVault {
type Interface = VaultPrograms;
fn validate_interface_account(
view: &AccountView,
) -> Result<(), crate::error::ProgramError> {
let data = view.try_borrow()?;
if VaultV1::validate_header(&data).is_ok() || VaultV2::validate_header(&data).is_ok() {
Ok(())
} else {
Err(crate::error::ProgramError::InvalidAccountData)
}
}
}
enum ResolvedVault<'a> {
V1(crate::borrow::Ref<'a, VaultV1>),
V2(crate::borrow::Ref<'a, VaultV2>),
}
impl InterfaceAccountResolve for AnyVault {
type Resolved<'a> = ResolvedVault<'a>;
fn resolve<'a>(
view: &'a AccountView,
) -> Result<Self::Resolved<'a>, crate::error::ProgramError> {
let info = view
.layout_info()
.ok_or(crate::error::ProgramError::AccountDataTooSmall)?;
if info.matches::<VaultV1>() {
return Ok(ResolvedVault::V1(view.load_cross_program::<VaultV1>()?));
}
if info.matches::<VaultV2>() {
return Ok(ResolvedVault::V2(view.load_cross_program::<VaultV2>()?));
}
Err(crate::error::ProgramError::InvalidAccountData)
}
}
fn make_account(total_data_len: usize, owner: Address) -> (std::vec::Vec<u8>, AccountView) {
let mut backing = std::vec![0u8; RuntimeAccount::SIZE + total_data_len];
let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
unsafe {
raw.write(RuntimeAccount {
borrow_state: NOT_BORROWED,
is_signer: 1,
is_writable: 1,
executable: 0,
resize_delta: 0,
address: NativeAddress::new_from_array([0x44; 32]),
owner: NativeAddress::new_from_array(*owner.as_array()),
lamports: 42,
data_len: total_data_len as u64,
});
}
let backend = unsafe { NativeAccountView::new_unchecked(raw) };
(backing, AccountView::from_backend(backend))
}
#[test]
fn interface_account_resolves_bounded_layout_variants() {
let (_v1_backing, v1_account) = make_account(VaultV1::SIZE, PROGRAM_B);
{
let mut data = v1_account.try_borrow_mut().unwrap();
crate::layout::init_header::<VaultV1>(&mut data).unwrap();
data[HopperHeader::SIZE..HopperHeader::SIZE + 8].copy_from_slice(&300u64.to_le_bytes());
}
let v1_vault = InterfaceAccount::<AnyVault>::try_new(&v1_account).unwrap();
match v1_vault.resolve().unwrap() {
ResolvedVault::V1(v1) => {
assert_eq!(v1.balance, 300);
}
ResolvedVault::V2(_) => panic!("expected v1"),
}
let (_backing, account) = make_account(VaultV2::SIZE, PROGRAM_A);
{
let mut data = account.try_borrow_mut().unwrap();
crate::layout::init_header::<VaultV2>(&mut data).unwrap();
data[HopperHeader::SIZE..HopperHeader::SIZE + 8].copy_from_slice(&700u64.to_le_bytes());
data[HopperHeader::SIZE + 8..HopperHeader::SIZE + 16]
.copy_from_slice(&9u64.to_le_bytes());
}
let vault = InterfaceAccount::<AnyVault>::try_new(&account).unwrap();
assert!(vault.is::<VaultV2>());
assert!(!vault.is::<VaultV1>());
match vault.resolve().unwrap() {
ResolvedVault::V2(v2) => {
assert_eq!(v2.balance, 700);
assert_eq!(v2.bump, 9);
}
ResolvedVault::V1(_) => panic!("expected v2"),
}
let v2 = vault.load_as::<VaultV2>().unwrap();
assert_eq!(v2.balance, 700);
assert!(vault.get_as::<VaultV1>().is_err());
}
#[test]
fn interface_account_resolver_keeps_owner_and_layout_checks() {
let (_wrong_owner_backing, wrong_owner) = make_account(VaultV1::SIZE, OTHER_PROGRAM);
{
let mut data = wrong_owner.try_borrow_mut().unwrap();
crate::layout::init_header::<VaultV1>(&mut data).unwrap();
}
let wrong_owner_result = InterfaceAccount::<AnyVault>::try_new(&wrong_owner);
assert!(matches!(
wrong_owner_result,
Err(crate::error::ProgramError::IncorrectProgramId)
));
let (_bad_layout_backing, bad_layout) = make_account(VaultV1::SIZE, PROGRAM_B);
{
let mut data = bad_layout.try_borrow_mut().unwrap();
crate::layout::write_header(&mut data, 99, 1, &[0x99; 8]).unwrap();
}
let bad_layout_result = InterfaceAccount::<AnyVault>::try_new(&bad_layout);
assert!(matches!(
bad_layout_result,
Err(crate::error::ProgramError::InvalidAccountData)
));
}
}