1use core::marker::PhantomData;
37
38use crate::account::AccountView;
39use crate::address::Address;
40
41#[repr(transparent)]
47#[derive(Clone, Copy)]
48pub struct Signer<'info> {
49 inner: &'info AccountView,
50}
51
52impl<'info> Signer<'info> {
53 #[inline(always)]
58 pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
63 Self { inner: view }
64 }
65
66 #[inline]
70 pub fn try_new(view: &'info AccountView) -> Result<Self, crate::error::ProgramError> {
71 view.check_signer()?;
72 Ok(Self { inner: view })
73 }
74
75 #[inline(always)]
77 pub fn as_account(&self) -> &'info AccountView {
78 self.inner
79 }
80
81 #[inline(always)]
83 pub fn key(&self) -> &Address {
84 self.inner.address()
85 }
86}
87
88impl<'info> core::ops::Deref for Signer<'info> {
89 type Target = AccountView;
90 #[inline(always)]
91 fn deref(&self) -> &AccountView {
92 self.inner
93 }
94}
95
96#[repr(transparent)]
104pub struct Account<'info, T: crate::layout::LayoutContract> {
105 inner: &'info AccountView,
106 _ty: PhantomData<T>,
107}
108
109impl<'info, T: crate::layout::LayoutContract> Clone for Account<'info, T> {
110 fn clone(&self) -> Self {
111 *self
112 }
113}
114impl<'info, T: crate::layout::LayoutContract> Copy for Account<'info, T> {}
115
116impl<'info, T: crate::layout::LayoutContract> Account<'info, T> {
117 #[inline(always)]
120 pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
125 Self {
126 inner: view,
127 _ty: PhantomData,
128 }
129 }
130
131 #[inline]
133 pub fn try_new(
134 view: &'info AccountView,
135 owner: &Address,
136 ) -> Result<Self, crate::error::ProgramError> {
137 view.check_owned_by(owner)?;
138 let _ = view.load::<T>()?;
139 Ok(Self {
140 inner: view,
141 _ty: PhantomData,
142 })
143 }
144
145 #[inline(always)]
147 pub fn as_account(&self) -> &'info AccountView {
148 self.inner
149 }
150
151 #[inline(always)]
153 pub fn key(&self) -> &Address {
154 self.inner.address()
155 }
156
157 #[inline(always)]
159 pub fn load(&self) -> Result<crate::borrow::Ref<'_, T>, crate::error::ProgramError> {
160 self.inner.load::<T>()
161 }
162
163 #[inline(always)]
165 pub fn get(&self) -> Result<crate::borrow::Ref<'_, T>, crate::error::ProgramError> {
166 self.load()
167 }
168
169 #[inline(always)]
176 pub fn load_mut(&self) -> Result<crate::borrow::RefMut<'_, T>, crate::error::ProgramError> {
177 self.inner.load_mut::<T>()
178 }
179
180 #[inline(always)]
182 pub fn get_mut(&self) -> Result<crate::borrow::RefMut<'_, T>, crate::error::ProgramError> {
183 self.load_mut()
184 }
185}
186
187impl<'info, T: crate::layout::LayoutContract> core::ops::Deref for Account<'info, T> {
188 type Target = AccountView;
189
190 #[inline(always)]
191 fn deref(&self) -> &AccountView {
192 self.inner
193 }
194}
195
196#[repr(transparent)]
205pub struct InitAccount<'info, T: crate::layout::LayoutContract> {
206 inner: &'info AccountView,
207 _ty: PhantomData<T>,
208}
209
210impl<'info, T: crate::layout::LayoutContract> Clone for InitAccount<'info, T> {
211 fn clone(&self) -> Self {
212 *self
213 }
214}
215impl<'info, T: crate::layout::LayoutContract> Copy for InitAccount<'info, T> {}
216
217impl<'info, T: crate::layout::LayoutContract> InitAccount<'info, T> {
218 #[inline(always)]
222 pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
227 Self {
228 inner: view,
229 _ty: PhantomData,
230 }
231 }
232
233 #[inline(always)]
235 pub fn as_account(&self) -> &'info AccountView {
236 self.inner
237 }
238
239 #[inline(always)]
241 pub fn key(&self) -> &Address {
242 self.inner.address()
243 }
244
245 #[inline(always)]
249 pub fn load_after_init(
250 &self,
251 ) -> Result<crate::borrow::RefMut<'_, T>, crate::error::ProgramError> {
252 self.inner.load_mut::<T>()
253 }
254
255 #[inline(always)]
257 pub fn get_mut_after_init(
258 &self,
259 ) -> Result<crate::borrow::RefMut<'_, T>, crate::error::ProgramError> {
260 self.load_after_init()
261 }
262}
263
264impl<'info, T: crate::layout::LayoutContract> core::ops::Deref for InitAccount<'info, T> {
265 type Target = AccountView;
266
267 #[inline(always)]
268 fn deref(&self) -> &AccountView {
269 self.inner
270 }
271}
272
273#[repr(transparent)]
280#[derive(Clone, Copy)]
281pub struct UncheckedAccount<'info> {
282 inner: &'info AccountView,
283}
284
285impl<'info> UncheckedAccount<'info> {
286 #[inline(always)]
288 pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
293 Self { inner: view }
294 }
295
296 #[inline(always)]
299 pub fn new(view: &'info AccountView) -> Self {
300 Self { inner: view }
301 }
302
303 #[inline(always)]
305 pub fn as_account(&self) -> &'info AccountView {
306 self.inner
307 }
308
309 #[inline(always)]
311 pub fn key(&self) -> &Address {
312 self.inner.address()
313 }
314}
315
316impl<'info> core::ops::Deref for UncheckedAccount<'info> {
317 type Target = AccountView;
318
319 #[inline(always)]
320 fn deref(&self) -> &AccountView {
321 self.inner
322 }
323}
324
325#[repr(transparent)]
327#[derive(Clone, Copy)]
328pub struct SystemAccount<'info> {
329 inner: &'info AccountView,
330}
331
332impl<'info> SystemAccount<'info> {
333 #[inline]
335 pub fn try_new(view: &'info AccountView) -> Result<Self, crate::error::ProgramError> {
336 view.check_owned_by(&SystemId::ID)?;
337 Ok(Self { inner: view })
338 }
339
340 #[inline(always)]
342 pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
347 Self { inner: view }
348 }
349
350 #[inline(always)]
352 pub fn as_account(&self) -> &'info AccountView {
353 self.inner
354 }
355
356 #[inline(always)]
358 pub fn key(&self) -> &Address {
359 self.inner.address()
360 }
361}
362
363impl<'info> core::ops::Deref for SystemAccount<'info> {
364 type Target = AccountView;
365
366 #[inline(always)]
367 fn deref(&self) -> &AccountView {
368 self.inner
369 }
370}
371
372#[repr(transparent)]
379pub struct Program<'info, P: ProgramId> {
380 inner: &'info AccountView,
381 _ty: PhantomData<P>,
382}
383
384impl<'info, P: ProgramId> Clone for Program<'info, P> {
385 fn clone(&self) -> Self {
386 *self
387 }
388}
389impl<'info, P: ProgramId> Copy for Program<'info, P> {}
390
391impl<'info, P: ProgramId> Program<'info, P> {
392 #[inline]
394 pub fn try_new(view: &'info AccountView) -> Result<Self, crate::error::ProgramError> {
395 if view.address() != &P::ID {
396 return Err(crate::error::ProgramError::IncorrectProgramId);
397 }
398 if !view.executable() {
399 return Err(crate::error::ProgramError::InvalidAccountData);
400 }
401 Ok(Self {
402 inner: view,
403 _ty: PhantomData,
404 })
405 }
406
407 #[inline(always)]
408 pub fn as_account(&self) -> &'info AccountView {
409 self.inner
410 }
411
412 #[inline(always)]
414 pub fn key(&self) -> &Address {
415 self.inner.address()
416 }
417}
418
419impl<'info, P: ProgramId> core::ops::Deref for Program<'info, P> {
420 type Target = AccountView;
421
422 #[inline(always)]
423 fn deref(&self) -> &AccountView {
424 self.inner
425 }
426}
427
428pub trait InterfaceSpec: 'static {
436 const IDS: &'static [Address];
438
439 #[inline(always)]
441 fn contains(program_id: &Address) -> bool {
442 Self::IDS.iter().any(|candidate| candidate == program_id)
443 }
444}
445
446pub trait InterfaceAccountLayout: crate::layout::LayoutContract {
462 type Interface: InterfaceSpec;
464
465 #[inline]
473 fn validate_interface_account(view: &AccountView) -> Result<(), crate::error::ProgramError> {
474 let _ = view.load_cross_program::<Self>()?;
475 Ok(())
476 }
477}
478
479pub trait InterfaceAccountResolve: InterfaceAccountLayout {
487 type Resolved<'a>
489 where
490 Self: 'a;
491
492 fn resolve<'a>(view: &'a AccountView)
494 -> Result<Self::Resolved<'a>, crate::error::ProgramError>;
495}
496
497#[repr(transparent)]
499pub struct Interface<'info, I: InterfaceSpec> {
500 inner: &'info AccountView,
501 _ty: PhantomData<I>,
502}
503
504impl<'info, I: InterfaceSpec> Clone for Interface<'info, I> {
505 fn clone(&self) -> Self {
506 *self
507 }
508}
509impl<'info, I: InterfaceSpec> Copy for Interface<'info, I> {}
510
511impl<'info, I: InterfaceSpec> Interface<'info, I> {
512 #[inline(always)]
514 pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
520 Self {
521 inner: view,
522 _ty: PhantomData,
523 }
524 }
525
526 #[inline]
528 pub fn try_new(view: &'info AccountView) -> Result<Self, crate::error::ProgramError> {
529 if !I::contains(view.address()) {
530 return Err(crate::error::ProgramError::IncorrectProgramId);
531 }
532 if !view.executable() {
533 return Err(crate::error::ProgramError::InvalidAccountData);
534 }
535 Ok(Self {
536 inner: view,
537 _ty: PhantomData,
538 })
539 }
540
541 #[inline(always)]
543 pub fn as_account(&self) -> &'info AccountView {
544 self.inner
545 }
546
547 #[inline(always)]
549 pub fn key(&self) -> &Address {
550 self.inner.address()
551 }
552}
553
554impl<'info, I: InterfaceSpec> core::ops::Deref for Interface<'info, I> {
555 type Target = AccountView;
556
557 #[inline(always)]
558 fn deref(&self) -> &AccountView {
559 self.inner
560 }
561}
562
563#[repr(transparent)]
570pub struct InterfaceAccount<'info, T: InterfaceAccountLayout> {
571 inner: &'info AccountView,
572 _ty: PhantomData<T>,
573}
574
575impl<'info, T: InterfaceAccountLayout> Clone for InterfaceAccount<'info, T> {
576 fn clone(&self) -> Self {
577 *self
578 }
579}
580impl<'info, T: InterfaceAccountLayout> Copy for InterfaceAccount<'info, T> {}
581
582impl<'info, T: InterfaceAccountLayout> InterfaceAccount<'info, T> {
583 #[inline(always)]
585 pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
590 Self {
591 inner: view,
592 _ty: PhantomData,
593 }
594 }
595
596 #[inline]
598 pub fn try_new(view: &'info AccountView) -> Result<Self, crate::error::ProgramError> {
599 let owner = view.read_owner();
600 if !<T::Interface as InterfaceSpec>::contains(&owner) {
601 return Err(crate::error::ProgramError::IncorrectProgramId);
602 }
603 T::validate_interface_account(view)?;
604 Ok(Self {
605 inner: view,
606 _ty: PhantomData,
607 })
608 }
609
610 #[inline(always)]
612 pub fn as_account(&self) -> &'info AccountView {
613 self.inner
614 }
615
616 #[inline(always)]
618 pub fn key(&self) -> &Address {
619 self.inner.address()
620 }
621
622 #[inline(always)]
624 pub fn owner(&self) -> Address {
625 self.inner.read_owner()
626 }
627
628 #[inline(always)]
630 pub fn load(&self) -> Result<crate::borrow::Ref<'_, T>, crate::error::ProgramError> {
631 self.inner.load_cross_program::<T>()
632 }
633
634 #[inline(always)]
636 pub fn get(&self) -> Result<crate::borrow::Ref<'_, T>, crate::error::ProgramError> {
637 self.load()
638 }
639
640 #[inline(always)]
647 pub fn load_as<U>(&self) -> Result<crate::borrow::Ref<'_, U>, crate::error::ProgramError>
648 where
649 U: InterfaceAccountLayout<Interface = <T as InterfaceAccountLayout>::Interface>,
650 {
651 self.inner.load_cross_program::<U>()
652 }
653
654 #[inline(always)]
656 pub fn get_as<U>(&self) -> Result<crate::borrow::Ref<'_, U>, crate::error::ProgramError>
657 where
658 U: InterfaceAccountLayout<Interface = <T as InterfaceAccountLayout>::Interface>,
659 {
660 self.load_as::<U>()
661 }
662
663 #[inline(always)]
666 pub fn is<U>(&self) -> bool
667 where
668 U: InterfaceAccountLayout<Interface = <T as InterfaceAccountLayout>::Interface>,
669 {
670 self.inner
671 .layout_info()
672 .is_some_and(|info| info.matches::<U>())
673 }
674
675 #[inline(always)]
677 pub fn resolve(&self) -> Result<T::Resolved<'_>, crate::error::ProgramError>
678 where
679 T: InterfaceAccountResolve,
680 {
681 T::resolve(self.inner)
682 }
683}
684
685impl<'info, T: InterfaceAccountLayout> core::ops::Deref for InterfaceAccount<'info, T> {
686 type Target = AccountView;
687
688 #[inline(always)]
689 fn deref(&self) -> &AccountView {
690 self.inner
691 }
692}
693
694pub trait ProgramId: 'static {
701 const ID: Address;
702}
703
704pub struct SystemId;
706impl ProgramId for SystemId {
707 const ID: Address = Address::new_from_array([0u8; 32]);
708}
709
710#[cfg(test)]
711mod tests {
712 use super::*;
713
714 #[test]
715 fn signer_wrapper_is_pointer_sized_zero_cost() {
716 assert_eq!(
720 core::mem::size_of::<Signer<'static>>(),
721 core::mem::size_of::<&'static AccountView>()
722 );
723 }
724
725 #[test]
726 fn system_program_id_is_all_zero() {
727 let sys = SystemId::ID;
728 assert_eq!(sys.as_array(), &[0u8; 32]);
729 }
730}
731
732#[cfg(all(test, feature = "hopper-native-backend"))]
733mod resolver_tests {
734 use super::*;
735 use crate::layout::{HopperHeader, LayoutContract};
736
737 use hopper_native::{
738 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
739 };
740
741 const PROGRAM_A: Address = Address::new_from_array([0xA1; 32]);
742 const PROGRAM_B: Address = Address::new_from_array([0xB2; 32]);
743 const OTHER_PROGRAM: Address = Address::new_from_array([0xCC; 32]);
744
745 struct VaultPrograms;
746 impl InterfaceSpec for VaultPrograms {
747 const IDS: &'static [Address] = &[PROGRAM_A, PROGRAM_B];
748 }
749
750 #[repr(C)]
751 #[derive(Clone, Copy, Debug, Default)]
752 struct VaultV1 {
753 balance: u64,
754 }
755
756 impl crate::field_map::FieldMap for VaultV1 {
757 const FIELDS: &'static [crate::field_map::FieldInfo] = &[crate::field_map::FieldInfo::new(
758 "balance",
759 HopperHeader::SIZE,
760 8,
761 )];
762 }
763
764 impl LayoutContract for VaultV1 {
765 const DISC: u8 = 11;
766 const VERSION: u8 = 1;
767 const LAYOUT_ID: [u8; 8] = [0x11; 8];
768 const SIZE: usize = HopperHeader::SIZE + core::mem::size_of::<Self>();
769 }
770
771 impl InterfaceAccountLayout for VaultV1 {
772 type Interface = VaultPrograms;
773 }
774
775 #[repr(C)]
776 #[derive(Clone, Copy, Debug, Default)]
777 struct VaultV2 {
778 balance: u64,
779 bump: u64,
780 }
781
782 impl crate::field_map::FieldMap for VaultV2 {
783 const FIELDS: &'static [crate::field_map::FieldInfo] = &[
784 crate::field_map::FieldInfo::new("balance", HopperHeader::SIZE, 8),
785 crate::field_map::FieldInfo::new("bump", HopperHeader::SIZE + 8, 8),
786 ];
787 }
788
789 impl LayoutContract for VaultV2 {
790 const DISC: u8 = 12;
791 const VERSION: u8 = 2;
792 const LAYOUT_ID: [u8; 8] = [0x22; 8];
793 const SIZE: usize = HopperHeader::SIZE + core::mem::size_of::<Self>();
794 }
795
796 impl InterfaceAccountLayout for VaultV2 {
797 type Interface = VaultPrograms;
798 }
799
800 #[derive(Clone, Copy, Debug, Default)]
801 struct AnyVault;
802
803 impl crate::field_map::FieldMap for AnyVault {
804 const FIELDS: &'static [crate::field_map::FieldInfo] = &[];
805 }
806
807 impl LayoutContract for AnyVault {
808 const DISC: u8 = 0;
809 const VERSION: u8 = 0;
810 const LAYOUT_ID: [u8; 8] = [0; 8];
811 const SIZE: usize = HopperHeader::SIZE;
812 }
813
814 impl InterfaceAccountLayout for AnyVault {
815 type Interface = VaultPrograms;
816
817 fn validate_interface_account(
818 view: &AccountView,
819 ) -> Result<(), crate::error::ProgramError> {
820 let data = view.try_borrow()?;
821 if VaultV1::validate_header(&data).is_ok() || VaultV2::validate_header(&data).is_ok() {
822 Ok(())
823 } else {
824 Err(crate::error::ProgramError::InvalidAccountData)
825 }
826 }
827 }
828
829 enum ResolvedVault<'a> {
830 V1(crate::borrow::Ref<'a, VaultV1>),
831 V2(crate::borrow::Ref<'a, VaultV2>),
832 }
833
834 impl InterfaceAccountResolve for AnyVault {
835 type Resolved<'a> = ResolvedVault<'a>;
836
837 fn resolve<'a>(
838 view: &'a AccountView,
839 ) -> Result<Self::Resolved<'a>, crate::error::ProgramError> {
840 let info = view
841 .layout_info()
842 .ok_or(crate::error::ProgramError::AccountDataTooSmall)?;
843 if info.matches::<VaultV1>() {
844 return Ok(ResolvedVault::V1(view.load_cross_program::<VaultV1>()?));
845 }
846 if info.matches::<VaultV2>() {
847 return Ok(ResolvedVault::V2(view.load_cross_program::<VaultV2>()?));
848 }
849 Err(crate::error::ProgramError::InvalidAccountData)
850 }
851 }
852
853 fn make_account(total_data_len: usize, owner: Address) -> (std::vec::Vec<u8>, AccountView) {
854 let mut backing = std::vec![0u8; RuntimeAccount::SIZE + total_data_len];
855 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
856 unsafe {
857 raw.write(RuntimeAccount {
858 borrow_state: NOT_BORROWED,
859 is_signer: 1,
860 is_writable: 1,
861 executable: 0,
862 resize_delta: 0,
863 address: NativeAddress::new_from_array([0x44; 32]),
864 owner: NativeAddress::new_from_array(*owner.as_array()),
865 lamports: 42,
866 data_len: total_data_len as u64,
867 });
868 }
869 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
870 (backing, AccountView::from_backend(backend))
871 }
872
873 #[test]
874 fn interface_account_resolves_bounded_layout_variants() {
875 let (_v1_backing, v1_account) = make_account(VaultV1::SIZE, PROGRAM_B);
876 {
877 let mut data = v1_account.try_borrow_mut().unwrap();
878 crate::layout::init_header::<VaultV1>(&mut data).unwrap();
879 data[HopperHeader::SIZE..HopperHeader::SIZE + 8].copy_from_slice(&300u64.to_le_bytes());
880 }
881
882 let v1_vault = InterfaceAccount::<AnyVault>::try_new(&v1_account).unwrap();
883 match v1_vault.resolve().unwrap() {
884 ResolvedVault::V1(v1) => {
885 assert_eq!(v1.balance, 300);
886 }
887 ResolvedVault::V2(_) => panic!("expected v1"),
888 }
889
890 let (_backing, account) = make_account(VaultV2::SIZE, PROGRAM_A);
891 {
892 let mut data = account.try_borrow_mut().unwrap();
893 crate::layout::init_header::<VaultV2>(&mut data).unwrap();
894 data[HopperHeader::SIZE..HopperHeader::SIZE + 8].copy_from_slice(&700u64.to_le_bytes());
895 data[HopperHeader::SIZE + 8..HopperHeader::SIZE + 16]
896 .copy_from_slice(&9u64.to_le_bytes());
897 }
898
899 let vault = InterfaceAccount::<AnyVault>::try_new(&account).unwrap();
900 assert!(vault.is::<VaultV2>());
901 assert!(!vault.is::<VaultV1>());
902
903 match vault.resolve().unwrap() {
904 ResolvedVault::V2(v2) => {
905 assert_eq!(v2.balance, 700);
906 assert_eq!(v2.bump, 9);
907 }
908 ResolvedVault::V1(_) => panic!("expected v2"),
909 }
910
911 let v2 = vault.load_as::<VaultV2>().unwrap();
912 assert_eq!(v2.balance, 700);
913 assert!(vault.get_as::<VaultV1>().is_err());
914 }
915
916 #[test]
917 fn interface_account_resolver_keeps_owner_and_layout_checks() {
918 let (_wrong_owner_backing, wrong_owner) = make_account(VaultV1::SIZE, OTHER_PROGRAM);
919 {
920 let mut data = wrong_owner.try_borrow_mut().unwrap();
921 crate::layout::init_header::<VaultV1>(&mut data).unwrap();
922 }
923 let wrong_owner_result = InterfaceAccount::<AnyVault>::try_new(&wrong_owner);
924 assert!(matches!(
925 wrong_owner_result,
926 Err(crate::error::ProgramError::IncorrectProgramId)
927 ));
928
929 let (_bad_layout_backing, bad_layout) = make_account(VaultV1::SIZE, PROGRAM_B);
930 {
931 let mut data = bad_layout.try_borrow_mut().unwrap();
932 crate::layout::write_header(&mut data, 99, 1, &[0x99; 8]).unwrap();
933 }
934 let bad_layout_result = InterfaceAccount::<AnyVault>::try_new(&bad_layout);
935 assert!(matches!(
936 bad_layout_result,
937 Err(crate::error::ProgramError::InvalidAccountData)
938 ));
939 }
940}