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<'info>,
50}
51
52impl<'info> Signer<'info> {
53 #[inline(always)]
58 pub unsafe fn new_unchecked(view: &'info AccountView<'info>) -> Self {
63 Self { inner: view }
64 }
65
66 #[inline]
70 pub fn try_new(view: &'info AccountView<'info>) -> 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<'info> {
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<'info>;
90 #[inline(always)]
91 fn deref(&self) -> &AccountView<'info> {
92 self.inner
93 }
94}
95
96#[repr(transparent)]
104pub struct Account<'info, T: crate::layout::LayoutContract + crate::Pod> {
105 inner: &'info AccountView<'info>,
106 _ty: PhantomData<T>,
107}
108
109impl<'info, T: crate::layout::LayoutContract + crate::Pod> Clone for Account<'info, T> {
110 fn clone(&self) -> Self {
111 *self
112 }
113}
114impl<'info, T: crate::layout::LayoutContract + crate::Pod> Copy for Account<'info, T> {}
115
116impl<'info, T: crate::layout::LayoutContract + crate::Pod> Account<'info, T> {
117 #[inline(always)]
120 pub unsafe fn new_unchecked(view: &'info AccountView<'info>) -> Self {
125 Self {
126 inner: view,
127 _ty: PhantomData,
128 }
129 }
130
131 #[inline]
133 pub fn try_new(
134 view: &'info AccountView<'info>,
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<'info> {
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]
174 pub fn with<R, F>(&self, f: F) -> Result<R, crate::error::ProgramError>
175 where
176 F: FnOnce(&T) -> Result<R, crate::error::ProgramError>,
177 {
178 self.inner.with::<T, R, F>(f)
179 }
180
181 #[inline(always)]
188 pub fn load_mut(&self) -> Result<crate::borrow::RefMut<'_, T>, crate::error::ProgramError> {
189 self.inner.load_mut::<T>()
190 }
191
192 #[inline(always)]
194 pub fn get_mut(&self) -> Result<crate::borrow::RefMut<'_, T>, crate::error::ProgramError> {
195 self.load_mut()
196 }
197
198 #[inline]
203 pub fn with_mut<R, F>(&self, f: F) -> Result<R, crate::error::ProgramError>
204 where
205 F: FnOnce(&mut T) -> Result<R, crate::error::ProgramError>,
206 {
207 self.inner.with_mut::<T, R, F>(f)
208 }
209}
210
211impl<'info, T: crate::layout::LayoutContract + crate::Pod> core::ops::Deref for Account<'info, T> {
212 type Target = AccountView<'info>;
213
214 #[inline(always)]
215 fn deref(&self) -> &AccountView<'info> {
216 self.inner
217 }
218}
219
220#[repr(transparent)]
229pub struct InitAccount<'info, T: crate::layout::LayoutContract + crate::Pod> {
230 inner: &'info AccountView<'info>,
231 _ty: PhantomData<T>,
232}
233
234impl<'info, T: crate::layout::LayoutContract + crate::Pod> Clone for InitAccount<'info, T> {
235 fn clone(&self) -> Self {
236 *self
237 }
238}
239impl<'info, T: crate::layout::LayoutContract + crate::Pod> Copy for InitAccount<'info, T> {}
240
241impl<'info, T: crate::layout::LayoutContract + crate::Pod> InitAccount<'info, T> {
242 #[inline(always)]
246 pub unsafe fn new_unchecked(view: &'info AccountView<'info>) -> Self {
251 Self {
252 inner: view,
253 _ty: PhantomData,
254 }
255 }
256
257 #[inline(always)]
259 pub fn as_account(&self) -> &'info AccountView<'info> {
260 self.inner
261 }
262
263 #[inline(always)]
265 pub fn key(&self) -> &Address {
266 self.inner.address()
267 }
268
269 #[inline(always)]
273 pub fn load_after_init(
274 &self,
275 ) -> Result<crate::borrow::RefMut<'_, T>, crate::error::ProgramError> {
276 self.inner.load_mut::<T>()
277 }
278
279 #[inline(always)]
281 pub fn get_mut_after_init(
282 &self,
283 ) -> Result<crate::borrow::RefMut<'_, T>, crate::error::ProgramError> {
284 self.load_after_init()
285 }
286
287 #[inline(always)]
289 pub fn load_init(&self) -> Result<crate::borrow::RefMut<'_, T>, crate::error::ProgramError> {
290 self.load_after_init()
291 }
292
293 #[inline]
295 pub fn with_mut_after_init<R, F>(&self, f: F) -> Result<R, crate::error::ProgramError>
296 where
297 F: FnOnce(&mut T) -> Result<R, crate::error::ProgramError>,
298 {
299 self.inner.with_mut::<T, R, F>(f)
300 }
301}
302
303impl<'info, T: crate::layout::LayoutContract + crate::Pod> core::ops::Deref
304 for InitAccount<'info, T>
305{
306 type Target = AccountView<'info>;
307
308 #[inline(always)]
309 fn deref(&self) -> &AccountView<'info> {
310 self.inner
311 }
312}
313
314#[repr(transparent)]
321#[derive(Clone, Copy)]
322pub struct UncheckedAccount<'info> {
323 inner: &'info AccountView<'info>,
324}
325
326impl<'info> UncheckedAccount<'info> {
327 #[inline(always)]
329 pub unsafe fn new_unchecked(view: &'info AccountView<'info>) -> Self {
334 Self { inner: view }
335 }
336
337 #[inline(always)]
340 pub fn new(view: &'info AccountView<'info>) -> Self {
341 Self { inner: view }
342 }
343
344 #[inline(always)]
346 pub fn as_account(&self) -> &'info AccountView<'info> {
347 self.inner
348 }
349
350 #[inline(always)]
352 pub fn key(&self) -> &Address {
353 self.inner.address()
354 }
355}
356
357impl<'info> core::ops::Deref for UncheckedAccount<'info> {
358 type Target = AccountView<'info>;
359
360 #[inline(always)]
361 fn deref(&self) -> &AccountView<'info> {
362 self.inner
363 }
364}
365
366#[repr(transparent)]
368#[derive(Clone, Copy)]
369pub struct SystemAccount<'info> {
370 inner: &'info AccountView<'info>,
371}
372
373impl<'info> SystemAccount<'info> {
374 #[inline]
376 pub fn try_new(view: &'info AccountView<'info>) -> Result<Self, crate::error::ProgramError> {
377 view.check_owned_by(&SystemId::ID)?;
378 Ok(Self { inner: view })
379 }
380
381 #[inline(always)]
383 pub unsafe fn new_unchecked(view: &'info AccountView<'info>) -> Self {
388 Self { inner: view }
389 }
390
391 #[inline(always)]
393 pub fn as_account(&self) -> &'info AccountView<'info> {
394 self.inner
395 }
396
397 #[inline(always)]
399 pub fn key(&self) -> &Address {
400 self.inner.address()
401 }
402}
403
404impl<'info> core::ops::Deref for SystemAccount<'info> {
405 type Target = AccountView<'info>;
406
407 #[inline(always)]
408 fn deref(&self) -> &AccountView<'info> {
409 self.inner
410 }
411}
412
413#[repr(transparent)]
420pub struct Program<'info, P: ProgramId> {
421 inner: &'info AccountView<'info>,
422 _ty: PhantomData<P>,
423}
424
425impl<'info, P: ProgramId> Clone for Program<'info, P> {
426 fn clone(&self) -> Self {
427 *self
428 }
429}
430impl<'info, P: ProgramId> Copy for Program<'info, P> {}
431
432impl<'info, P: ProgramId> Program<'info, P> {
433 #[inline]
435 pub fn try_new(view: &'info AccountView<'info>) -> Result<Self, crate::error::ProgramError> {
436 if view.address() != &P::ID {
437 return Err(crate::error::ProgramError::IncorrectProgramId);
438 }
439 if !view.executable() {
440 return Err(crate::error::ProgramError::InvalidAccountData);
441 }
442 Ok(Self {
443 inner: view,
444 _ty: PhantomData,
445 })
446 }
447
448 #[inline(always)]
449 pub fn as_account(&self) -> &'info AccountView<'info> {
450 self.inner
451 }
452
453 #[inline(always)]
455 pub fn key(&self) -> &Address {
456 self.inner.address()
457 }
458}
459
460impl<'info, P: ProgramId> core::ops::Deref for Program<'info, P> {
461 type Target = AccountView<'info>;
462
463 #[inline(always)]
464 fn deref(&self) -> &AccountView<'info> {
465 self.inner
466 }
467}
468
469pub trait InterfaceSpec: 'static {
477 const IDS: &'static [Address];
479
480 #[inline(always)]
482 fn contains(program_id: &Address) -> bool {
483 Self::IDS.iter().any(|candidate| candidate == program_id)
484 }
485}
486
487pub trait InterfaceAccountLayout: crate::layout::LayoutContract {
503 type Interface: InterfaceSpec;
505
506 #[inline]
514 fn validate_interface_account(
515 view: &AccountView<'_>,
516 ) -> Result<(), crate::error::ProgramError> {
517 let info = view
518 .layout_info()
519 .ok_or(crate::error::ProgramError::InvalidAccountData)?;
520 if !info.matches::<Self>() {
521 return Err(crate::error::ProgramError::InvalidAccountData);
522 }
523 if view.data_len() < Self::required_len() {
524 return Err(crate::error::ProgramError::AccountDataTooSmall);
525 }
526 Ok(())
527 }
528}
529
530pub trait InterfaceAccountResolve: InterfaceAccountLayout {
538 type Resolved<'a>
540 where
541 Self: 'a;
542
543 fn resolve<'a>(
545 view: &'a AccountView<'a>,
546 ) -> Result<Self::Resolved<'a>, crate::error::ProgramError>;
547}
548
549#[repr(transparent)]
551pub struct Interface<'info, I: InterfaceSpec> {
552 inner: &'info AccountView<'info>,
553 _ty: PhantomData<I>,
554}
555
556impl<'info, I: InterfaceSpec> Clone for Interface<'info, I> {
557 fn clone(&self) -> Self {
558 *self
559 }
560}
561impl<'info, I: InterfaceSpec> Copy for Interface<'info, I> {}
562
563impl<'info, I: InterfaceSpec> Interface<'info, I> {
564 #[inline(always)]
566 pub unsafe fn new_unchecked(view: &'info AccountView<'info>) -> Self {
572 Self {
573 inner: view,
574 _ty: PhantomData,
575 }
576 }
577
578 #[inline]
580 pub fn try_new(view: &'info AccountView<'info>) -> Result<Self, crate::error::ProgramError> {
581 if !I::contains(view.address()) {
582 return Err(crate::error::ProgramError::IncorrectProgramId);
583 }
584 if !view.executable() {
585 return Err(crate::error::ProgramError::InvalidAccountData);
586 }
587 Ok(Self {
588 inner: view,
589 _ty: PhantomData,
590 })
591 }
592
593 #[inline(always)]
595 pub fn as_account(&self) -> &'info AccountView<'info> {
596 self.inner
597 }
598
599 #[inline(always)]
601 pub fn key(&self) -> &Address {
602 self.inner.address()
603 }
604}
605
606impl<'info, I: InterfaceSpec> core::ops::Deref for Interface<'info, I> {
607 type Target = AccountView<'info>;
608
609 #[inline(always)]
610 fn deref(&self) -> &AccountView<'info> {
611 self.inner
612 }
613}
614
615#[repr(transparent)]
622pub struct InterfaceAccount<'info, T: InterfaceAccountLayout> {
623 inner: &'info AccountView<'info>,
624 _ty: PhantomData<T>,
625}
626
627impl<'info, T: InterfaceAccountLayout> Clone for InterfaceAccount<'info, T> {
628 fn clone(&self) -> Self {
629 *self
630 }
631}
632impl<'info, T: InterfaceAccountLayout> Copy for InterfaceAccount<'info, T> {}
633
634impl<'info, T: InterfaceAccountLayout> InterfaceAccount<'info, T> {
635 #[inline(always)]
636 fn revalidate(&self) -> Result<(), crate::error::ProgramError> {
637 let owner = self.inner.read_owner();
638 if !<T::Interface as InterfaceSpec>::contains(&owner) {
639 return Err(crate::error::ProgramError::IncorrectProgramId);
640 }
641 T::validate_interface_account(self.inner)
642 }
643
644 #[inline(always)]
646 pub unsafe fn new_unchecked(view: &'info AccountView<'info>) -> Self {
651 Self {
652 inner: view,
653 _ty: PhantomData,
654 }
655 }
656
657 #[inline]
659 pub fn try_new(view: &'info AccountView<'info>) -> Result<Self, crate::error::ProgramError> {
660 let owner = view.read_owner();
661 if !<T::Interface as InterfaceSpec>::contains(&owner) {
662 return Err(crate::error::ProgramError::IncorrectProgramId);
663 }
664 T::validate_interface_account(view)?;
665 Ok(Self {
666 inner: view,
667 _ty: PhantomData,
668 })
669 }
670
671 #[inline(always)]
673 pub fn as_account(&self) -> &'info AccountView<'info> {
674 self.inner
675 }
676
677 #[inline(always)]
679 pub fn key(&self) -> &Address {
680 self.inner.address()
681 }
682
683 #[inline(always)]
685 pub fn owner(&self) -> Address {
686 self.inner.read_owner()
687 }
688
689 #[inline(always)]
691 pub fn load(&self) -> Result<crate::borrow::Ref<'_, T>, crate::error::ProgramError>
692 where
693 T: crate::Pod,
694 {
695 self.revalidate()?;
696 self.inner.load_cross_program::<T>()
697 }
698
699 #[inline(always)]
701 pub fn get(&self) -> Result<crate::borrow::Ref<'_, T>, crate::error::ProgramError>
702 where
703 T: crate::Pod,
704 {
705 self.load()
706 }
707
708 #[inline]
710 pub fn with<R, F>(&self, f: F) -> Result<R, crate::error::ProgramError>
711 where
712 T: crate::Pod,
713 F: FnOnce(&T) -> Result<R, crate::error::ProgramError>,
714 {
715 let account = self.load()?;
716 f(&*account)
717 }
718
719 #[inline(always)]
726 pub fn load_as<U>(&self) -> Result<crate::borrow::Ref<'_, U>, crate::error::ProgramError>
727 where
728 U: InterfaceAccountLayout<Interface = <T as InterfaceAccountLayout>::Interface>
729 + crate::Pod,
730 {
731 self.revalidate()?;
732 U::validate_interface_account(self.inner)?;
733 self.inner.load_cross_program::<U>()
734 }
735
736 #[inline(always)]
738 pub fn get_as<U>(&self) -> Result<crate::borrow::Ref<'_, U>, crate::error::ProgramError>
739 where
740 U: InterfaceAccountLayout<Interface = <T as InterfaceAccountLayout>::Interface>
741 + crate::Pod,
742 {
743 self.load_as::<U>()
744 }
745
746 #[inline]
748 pub fn with_as<U, R, F>(&self, f: F) -> Result<R, crate::error::ProgramError>
749 where
750 U: InterfaceAccountLayout<Interface = <T as InterfaceAccountLayout>::Interface>
751 + crate::Pod,
752 F: FnOnce(&U) -> Result<R, crate::error::ProgramError>,
753 {
754 let account = self.load_as::<U>()?;
755 f(&*account)
756 }
757
758 #[inline(always)]
761 pub fn is<U>(&self) -> bool
762 where
763 U: InterfaceAccountLayout<Interface = <T as InterfaceAccountLayout>::Interface>
764 + crate::Pod,
765 {
766 self.revalidate().is_ok()
767 && self
768 .inner
769 .layout_info()
770 .is_some_and(|info| info.matches::<U>())
771 }
772
773 #[inline(always)]
775 pub fn resolve(&self) -> Result<T::Resolved<'_>, crate::error::ProgramError>
776 where
777 T: InterfaceAccountResolve,
778 {
779 self.revalidate()?;
780 T::resolve(self.inner)
781 }
782}
783
784impl<'info, T: InterfaceAccountLayout> core::ops::Deref for InterfaceAccount<'info, T> {
785 type Target = AccountView<'info>;
786
787 #[inline(always)]
788 fn deref(&self) -> &AccountView<'info> {
789 self.inner
790 }
791}
792
793pub trait ProgramId: 'static {
800 const ID: Address;
801}
802
803pub struct SystemId;
805impl ProgramId for SystemId {
806 const ID: Address = Address::new_from_array([0u8; 32]);
807}
808
809#[cfg(test)]
810mod tests {
811 use super::*;
812
813 #[test]
814 fn signer_wrapper_is_pointer_sized_zero_cost() {
815 assert_eq!(
819 core::mem::size_of::<Signer<'static>>(),
820 core::mem::size_of::<&'static AccountView<'static>>()
821 );
822 }
823
824 #[test]
825 fn system_program_id_is_all_zero() {
826 let sys = SystemId::ID;
827 assert_eq!(sys.as_array(), &[0u8; 32]);
828 }
829}
830
831#[cfg(test)]
832mod resolver_tests {
833 use super::*;
834 use crate::layout::{HopperHeader, LayoutContract};
835
836 use hopper_native::{
837 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
838 };
839
840 const PROGRAM_A: Address = Address::new_from_array([0xA1; 32]);
841 const PROGRAM_B: Address = Address::new_from_array([0xB2; 32]);
842 const OTHER_PROGRAM: Address = Address::new_from_array([0xCC; 32]);
843
844 struct VaultPrograms;
845 impl InterfaceSpec for VaultPrograms {
846 const IDS: &'static [Address] = &[PROGRAM_A, PROGRAM_B];
847 }
848
849 #[repr(C)]
850 #[derive(Clone, Copy, Debug, Default)]
851 struct VaultV1 {
852 balance: [u8; 8],
853 }
854
855 unsafe impl crate::Zeroable for VaultV1 {}
856 unsafe impl crate::Pod for VaultV1 {}
857
858 impl crate::field_map::FieldMap for VaultV1 {
859 const FIELDS: &'static [crate::field_map::FieldInfo] = &[crate::field_map::FieldInfo::new(
860 "balance",
861 HopperHeader::SIZE,
862 8,
863 )];
864 }
865
866 impl LayoutContract for VaultV1 {
867 const DISC: u8 = 11;
868 const VERSION: u8 = 1;
869 const LAYOUT_ID: [u8; 8] = [0x11; 8];
870 const SIZE: usize = HopperHeader::SIZE + core::mem::size_of::<Self>();
871 }
872
873 impl InterfaceAccountLayout for VaultV1 {
874 type Interface = VaultPrograms;
875 }
876
877 #[repr(C)]
878 #[derive(Clone, Copy, Debug, Default)]
879 struct VaultV2 {
880 balance: [u8; 8],
881 bump: [u8; 8],
882 }
883
884 unsafe impl crate::Zeroable for VaultV2 {}
885 unsafe impl crate::Pod for VaultV2 {}
886
887 impl crate::field_map::FieldMap for VaultV2 {
888 const FIELDS: &'static [crate::field_map::FieldInfo] = &[
889 crate::field_map::FieldInfo::new("balance", HopperHeader::SIZE, 8),
890 crate::field_map::FieldInfo::new("bump", HopperHeader::SIZE + 8, 8),
891 ];
892 }
893
894 impl LayoutContract for VaultV2 {
895 const DISC: u8 = 12;
896 const VERSION: u8 = 2;
897 const LAYOUT_ID: [u8; 8] = [0x22; 8];
898 const SIZE: usize = HopperHeader::SIZE + core::mem::size_of::<Self>();
899 }
900
901 impl InterfaceAccountLayout for VaultV2 {
902 type Interface = VaultPrograms;
903 }
904
905 #[repr(C)]
906 #[derive(Clone, Copy, Debug, Default)]
907 struct AnyVault {
908 _reserved: [u8; 1],
909 }
910
911 unsafe impl crate::Zeroable for AnyVault {}
912 unsafe impl crate::Pod for AnyVault {}
913
914 impl crate::field_map::FieldMap for AnyVault {
915 const FIELDS: &'static [crate::field_map::FieldInfo] = &[];
916 }
917
918 impl LayoutContract for AnyVault {
919 const DISC: u8 = 0;
920 const VERSION: u8 = 0;
921 const LAYOUT_ID: [u8; 8] = [0; 8];
922 const SIZE: usize = HopperHeader::SIZE;
923 }
924
925 impl InterfaceAccountLayout for AnyVault {
926 type Interface = VaultPrograms;
927
928 fn validate_interface_account(
929 view: &AccountView<'_>,
930 ) -> Result<(), crate::error::ProgramError> {
931 let data = view.try_borrow()?;
932 if VaultV1::validate_header(&data).is_ok() || VaultV2::validate_header(&data).is_ok() {
933 Ok(())
934 } else {
935 Err(crate::error::ProgramError::InvalidAccountData)
936 }
937 }
938 }
939
940 enum ResolvedVault<'a> {
941 V1(crate::borrow::Ref<'a, VaultV1>),
942 V2(crate::borrow::Ref<'a, VaultV2>),
943 }
944
945 impl InterfaceAccountResolve for AnyVault {
946 type Resolved<'a> = ResolvedVault<'a>;
947
948 fn resolve<'a>(
949 view: &'a AccountView<'a>,
950 ) -> Result<Self::Resolved<'a>, crate::error::ProgramError> {
951 let info = view
952 .layout_info()
953 .ok_or(crate::error::ProgramError::AccountDataTooSmall)?;
954 if info.matches::<VaultV1>() {
955 return Ok(ResolvedVault::V1(view.load_cross_program::<VaultV1>()?));
956 }
957 if info.matches::<VaultV2>() {
958 return Ok(ResolvedVault::V2(view.load_cross_program::<VaultV2>()?));
959 }
960 Err(crate::error::ProgramError::InvalidAccountData)
961 }
962 }
963
964 fn make_account(
965 total_data_len: usize,
966 owner: Address,
967 ) -> (std::vec::Vec<u64>, AccountView<'static>) {
968 let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + total_data_len).div_ceil(8)];
969 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
970 unsafe {
973 raw.write(RuntimeAccount {
974 borrow_state: NOT_BORROWED,
975 is_signer: 1,
976 is_writable: 1,
977 executable: 0,
978 resize_delta: 0,
979 address: NativeAddress::new_from_array([0x44; 32]),
980 owner: NativeAddress::new_from_array(*owner.as_array()),
981 lamports: 42,
982 data_len: total_data_len as u64,
983 });
984 }
985 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
987 (backing, AccountView::from_backend(backend))
988 }
989
990 #[test]
991 fn interface_account_resolves_bounded_layout_variants() {
992 let (_v1_backing, v1_account) = make_account(VaultV1::SIZE, PROGRAM_B);
993 {
994 let mut data = v1_account.try_borrow_mut().unwrap();
995 crate::layout::init_header::<VaultV1>(&mut data).unwrap();
996 data[HopperHeader::SIZE..HopperHeader::SIZE + 8].copy_from_slice(&300u64.to_le_bytes());
997 }
998
999 let v1_vault = InterfaceAccount::<AnyVault>::try_new(&v1_account).unwrap();
1000 match v1_vault.resolve().unwrap() {
1001 ResolvedVault::V1(v1) => {
1002 assert_eq!(u64::from_le_bytes(v1.balance), 300);
1003 }
1004 ResolvedVault::V2(_) => panic!("expected v1"),
1005 }
1006
1007 let (_backing, account) = make_account(VaultV2::SIZE, PROGRAM_A);
1008 {
1009 let mut data = account.try_borrow_mut().unwrap();
1010 crate::layout::init_header::<VaultV2>(&mut data).unwrap();
1011 data[HopperHeader::SIZE..HopperHeader::SIZE + 8].copy_from_slice(&700u64.to_le_bytes());
1012 data[HopperHeader::SIZE + 8..HopperHeader::SIZE + 16]
1013 .copy_from_slice(&9u64.to_le_bytes());
1014 }
1015
1016 let vault = InterfaceAccount::<AnyVault>::try_new(&account).unwrap();
1017 assert!(vault.is::<VaultV2>());
1018 assert!(!vault.is::<VaultV1>());
1019
1020 match vault.resolve().unwrap() {
1021 ResolvedVault::V2(v2) => {
1022 assert_eq!(u64::from_le_bytes(v2.balance), 700);
1023 assert_eq!(u64::from_le_bytes(v2.bump), 9);
1024 }
1025 ResolvedVault::V1(_) => panic!("expected v2"),
1026 }
1027
1028 let v2 = vault.load_as::<VaultV2>().unwrap();
1029 assert_eq!(u64::from_le_bytes(v2.balance), 700);
1030 assert!(vault.get_as::<VaultV1>().is_err());
1031 }
1032
1033 #[test]
1034 fn interface_account_resolver_keeps_owner_and_layout_checks() {
1035 let (_wrong_owner_backing, wrong_owner) = make_account(VaultV1::SIZE, OTHER_PROGRAM);
1036 {
1037 let mut data = wrong_owner.try_borrow_mut().unwrap();
1038 crate::layout::init_header::<VaultV1>(&mut data).unwrap();
1039 }
1040 let wrong_owner_result = InterfaceAccount::<AnyVault>::try_new(&wrong_owner);
1041 assert!(matches!(
1042 wrong_owner_result,
1043 Err(crate::error::ProgramError::IncorrectProgramId)
1044 ));
1045
1046 let (_bad_layout_backing, bad_layout) = make_account(VaultV1::SIZE, PROGRAM_B);
1047 {
1048 let mut data = bad_layout.try_borrow_mut().unwrap();
1049 crate::layout::write_header(&mut data, 99, 1, &[0x99; 8]).unwrap();
1050 }
1051 let bad_layout_result = InterfaceAccount::<AnyVault>::try_new(&bad_layout);
1052 assert!(matches!(
1053 bad_layout_result,
1054 Err(crate::error::ProgramError::InvalidAccountData)
1055 ));
1056 }
1057
1058 #[test]
1059 fn interface_account_revalidates_owner_after_binding() {
1060 let (_backing, account) = make_account(VaultV1::SIZE, PROGRAM_A);
1061 {
1062 let mut data = account.try_borrow_mut().unwrap();
1063 crate::layout::init_header::<VaultV1>(&mut data).unwrap();
1064 }
1065 let vault = InterfaceAccount::<AnyVault>::try_new(&account).unwrap();
1066
1067 unsafe {
1071 account.assign(&OTHER_PROGRAM);
1072 }
1073
1074 assert!(matches!(
1075 vault.load_as::<VaultV1>(),
1076 Err(crate::error::ProgramError::IncorrectProgramId)
1077 ));
1078 assert!(matches!(
1079 vault.resolve(),
1080 Err(crate::error::ProgramError::IncorrectProgramId)
1081 ));
1082 assert!(!vault.is::<VaultV1>());
1083 }
1084
1085 #[test]
1086 fn interface_account_revalidates_layout_after_binding() {
1087 let (_backing, account) = make_account(VaultV2::SIZE, PROGRAM_A);
1090 {
1091 let mut data = account.try_borrow_mut().unwrap();
1092 crate::layout::init_header::<VaultV1>(&mut data).unwrap();
1093 }
1094 let vault = InterfaceAccount::<VaultV1>::try_new(&account).unwrap();
1095
1096 {
1098 let mut data = account.try_borrow_mut().unwrap();
1099 crate::layout::init_header::<VaultV2>(&mut data).unwrap();
1100 }
1101
1102 assert!(matches!(
1103 vault.load(),
1104 Err(crate::error::ProgramError::InvalidAccountData)
1105 ));
1106 assert!(!vault.is::<VaultV1>());
1107 }
1108}