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]
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> core::ops::Deref for Account<'info, T> {
212 type Target = AccountView;
213
214 #[inline(always)]
215 fn deref(&self) -> &AccountView {
216 self.inner
217 }
218}
219
220#[repr(transparent)]
229pub struct InitAccount<'info, T: crate::layout::LayoutContract> {
230 inner: &'info AccountView,
231 _ty: PhantomData<T>,
232}
233
234impl<'info, T: crate::layout::LayoutContract> Clone for InitAccount<'info, T> {
235 fn clone(&self) -> Self {
236 *self
237 }
238}
239impl<'info, T: crate::layout::LayoutContract> Copy for InitAccount<'info, T> {}
240
241impl<'info, T: crate::layout::LayoutContract> InitAccount<'info, T> {
242 #[inline(always)]
246 pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
251 Self {
252 inner: view,
253 _ty: PhantomData,
254 }
255 }
256
257 #[inline(always)]
259 pub fn as_account(&self) -> &'info AccountView {
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]
289 pub fn with_mut_after_init<R, F>(&self, f: F) -> Result<R, crate::error::ProgramError>
290 where
291 F: FnOnce(&mut T) -> Result<R, crate::error::ProgramError>,
292 {
293 self.inner.with_mut::<T, R, F>(f)
294 }
295}
296
297impl<'info, T: crate::layout::LayoutContract> core::ops::Deref for InitAccount<'info, T> {
298 type Target = AccountView;
299
300 #[inline(always)]
301 fn deref(&self) -> &AccountView {
302 self.inner
303 }
304}
305
306#[repr(transparent)]
313#[derive(Clone, Copy)]
314pub struct UncheckedAccount<'info> {
315 inner: &'info AccountView,
316}
317
318impl<'info> UncheckedAccount<'info> {
319 #[inline(always)]
321 pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
326 Self { inner: view }
327 }
328
329 #[inline(always)]
332 pub fn new(view: &'info AccountView) -> Self {
333 Self { inner: view }
334 }
335
336 #[inline(always)]
338 pub fn as_account(&self) -> &'info AccountView {
339 self.inner
340 }
341
342 #[inline(always)]
344 pub fn key(&self) -> &Address {
345 self.inner.address()
346 }
347}
348
349impl<'info> core::ops::Deref for UncheckedAccount<'info> {
350 type Target = AccountView;
351
352 #[inline(always)]
353 fn deref(&self) -> &AccountView {
354 self.inner
355 }
356}
357
358#[repr(transparent)]
360#[derive(Clone, Copy)]
361pub struct SystemAccount<'info> {
362 inner: &'info AccountView,
363}
364
365impl<'info> SystemAccount<'info> {
366 #[inline]
368 pub fn try_new(view: &'info AccountView) -> Result<Self, crate::error::ProgramError> {
369 view.check_owned_by(&SystemId::ID)?;
370 Ok(Self { inner: view })
371 }
372
373 #[inline(always)]
375 pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
380 Self { inner: view }
381 }
382
383 #[inline(always)]
385 pub fn as_account(&self) -> &'info AccountView {
386 self.inner
387 }
388
389 #[inline(always)]
391 pub fn key(&self) -> &Address {
392 self.inner.address()
393 }
394}
395
396impl<'info> core::ops::Deref for SystemAccount<'info> {
397 type Target = AccountView;
398
399 #[inline(always)]
400 fn deref(&self) -> &AccountView {
401 self.inner
402 }
403}
404
405#[repr(transparent)]
412pub struct Program<'info, P: ProgramId> {
413 inner: &'info AccountView,
414 _ty: PhantomData<P>,
415}
416
417impl<'info, P: ProgramId> Clone for Program<'info, P> {
418 fn clone(&self) -> Self {
419 *self
420 }
421}
422impl<'info, P: ProgramId> Copy for Program<'info, P> {}
423
424impl<'info, P: ProgramId> Program<'info, P> {
425 #[inline]
427 pub fn try_new(view: &'info AccountView) -> Result<Self, crate::error::ProgramError> {
428 if view.address() != &P::ID {
429 return Err(crate::error::ProgramError::IncorrectProgramId);
430 }
431 if !view.executable() {
432 return Err(crate::error::ProgramError::InvalidAccountData);
433 }
434 Ok(Self {
435 inner: view,
436 _ty: PhantomData,
437 })
438 }
439
440 #[inline(always)]
441 pub fn as_account(&self) -> &'info AccountView {
442 self.inner
443 }
444
445 #[inline(always)]
447 pub fn key(&self) -> &Address {
448 self.inner.address()
449 }
450}
451
452impl<'info, P: ProgramId> core::ops::Deref for Program<'info, P> {
453 type Target = AccountView;
454
455 #[inline(always)]
456 fn deref(&self) -> &AccountView {
457 self.inner
458 }
459}
460
461pub trait InterfaceSpec: 'static {
469 const IDS: &'static [Address];
471
472 #[inline(always)]
474 fn contains(program_id: &Address) -> bool {
475 Self::IDS.iter().any(|candidate| candidate == program_id)
476 }
477}
478
479pub trait InterfaceAccountLayout: crate::layout::LayoutContract {
495 type Interface: InterfaceSpec;
497
498 #[inline]
506 fn validate_interface_account(view: &AccountView) -> Result<(), crate::error::ProgramError> {
507 let _ = view.load_cross_program::<Self>()?;
508 Ok(())
509 }
510}
511
512pub trait InterfaceAccountResolve: InterfaceAccountLayout {
520 type Resolved<'a>
522 where
523 Self: 'a;
524
525 fn resolve<'a>(view: &'a AccountView)
527 -> Result<Self::Resolved<'a>, crate::error::ProgramError>;
528}
529
530#[repr(transparent)]
532pub struct Interface<'info, I: InterfaceSpec> {
533 inner: &'info AccountView,
534 _ty: PhantomData<I>,
535}
536
537impl<'info, I: InterfaceSpec> Clone for Interface<'info, I> {
538 fn clone(&self) -> Self {
539 *self
540 }
541}
542impl<'info, I: InterfaceSpec> Copy for Interface<'info, I> {}
543
544impl<'info, I: InterfaceSpec> Interface<'info, I> {
545 #[inline(always)]
547 pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
553 Self {
554 inner: view,
555 _ty: PhantomData,
556 }
557 }
558
559 #[inline]
561 pub fn try_new(view: &'info AccountView) -> Result<Self, crate::error::ProgramError> {
562 if !I::contains(view.address()) {
563 return Err(crate::error::ProgramError::IncorrectProgramId);
564 }
565 if !view.executable() {
566 return Err(crate::error::ProgramError::InvalidAccountData);
567 }
568 Ok(Self {
569 inner: view,
570 _ty: PhantomData,
571 })
572 }
573
574 #[inline(always)]
576 pub fn as_account(&self) -> &'info AccountView {
577 self.inner
578 }
579
580 #[inline(always)]
582 pub fn key(&self) -> &Address {
583 self.inner.address()
584 }
585}
586
587impl<'info, I: InterfaceSpec> core::ops::Deref for Interface<'info, I> {
588 type Target = AccountView;
589
590 #[inline(always)]
591 fn deref(&self) -> &AccountView {
592 self.inner
593 }
594}
595
596#[repr(transparent)]
603pub struct InterfaceAccount<'info, T: InterfaceAccountLayout> {
604 inner: &'info AccountView,
605 _ty: PhantomData<T>,
606}
607
608impl<'info, T: InterfaceAccountLayout> Clone for InterfaceAccount<'info, T> {
609 fn clone(&self) -> Self {
610 *self
611 }
612}
613impl<'info, T: InterfaceAccountLayout> Copy for InterfaceAccount<'info, T> {}
614
615impl<'info, T: InterfaceAccountLayout> InterfaceAccount<'info, T> {
616 #[inline(always)]
618 pub unsafe fn new_unchecked(view: &'info AccountView) -> Self {
623 Self {
624 inner: view,
625 _ty: PhantomData,
626 }
627 }
628
629 #[inline]
631 pub fn try_new(view: &'info AccountView) -> Result<Self, crate::error::ProgramError> {
632 let owner = view.read_owner();
633 if !<T::Interface as InterfaceSpec>::contains(&owner) {
634 return Err(crate::error::ProgramError::IncorrectProgramId);
635 }
636 T::validate_interface_account(view)?;
637 Ok(Self {
638 inner: view,
639 _ty: PhantomData,
640 })
641 }
642
643 #[inline(always)]
645 pub fn as_account(&self) -> &'info AccountView {
646 self.inner
647 }
648
649 #[inline(always)]
651 pub fn key(&self) -> &Address {
652 self.inner.address()
653 }
654
655 #[inline(always)]
657 pub fn owner(&self) -> Address {
658 self.inner.read_owner()
659 }
660
661 #[inline(always)]
663 pub fn load(&self) -> Result<crate::borrow::Ref<'_, T>, crate::error::ProgramError> {
664 self.inner.load_cross_program::<T>()
665 }
666
667 #[inline(always)]
669 pub fn get(&self) -> Result<crate::borrow::Ref<'_, T>, crate::error::ProgramError> {
670 self.load()
671 }
672
673 #[inline]
675 pub fn with<R, F>(&self, f: F) -> Result<R, crate::error::ProgramError>
676 where
677 F: FnOnce(&T) -> Result<R, crate::error::ProgramError>,
678 {
679 let account = self.load()?;
680 f(&*account)
681 }
682
683 #[inline(always)]
690 pub fn load_as<U>(&self) -> Result<crate::borrow::Ref<'_, U>, crate::error::ProgramError>
691 where
692 U: InterfaceAccountLayout<Interface = <T as InterfaceAccountLayout>::Interface>,
693 {
694 self.inner.load_cross_program::<U>()
695 }
696
697 #[inline(always)]
699 pub fn get_as<U>(&self) -> Result<crate::borrow::Ref<'_, U>, crate::error::ProgramError>
700 where
701 U: InterfaceAccountLayout<Interface = <T as InterfaceAccountLayout>::Interface>,
702 {
703 self.load_as::<U>()
704 }
705
706 #[inline]
708 pub fn with_as<U, R, F>(&self, f: F) -> Result<R, crate::error::ProgramError>
709 where
710 U: InterfaceAccountLayout<Interface = <T as InterfaceAccountLayout>::Interface>,
711 F: FnOnce(&U) -> Result<R, crate::error::ProgramError>,
712 {
713 let account = self.load_as::<U>()?;
714 f(&*account)
715 }
716
717 #[inline(always)]
720 pub fn is<U>(&self) -> bool
721 where
722 U: InterfaceAccountLayout<Interface = <T as InterfaceAccountLayout>::Interface>,
723 {
724 self.inner
725 .layout_info()
726 .is_some_and(|info| info.matches::<U>())
727 }
728
729 #[inline(always)]
731 pub fn resolve(&self) -> Result<T::Resolved<'_>, crate::error::ProgramError>
732 where
733 T: InterfaceAccountResolve,
734 {
735 T::resolve(self.inner)
736 }
737}
738
739impl<'info, T: InterfaceAccountLayout> core::ops::Deref for InterfaceAccount<'info, T> {
740 type Target = AccountView;
741
742 #[inline(always)]
743 fn deref(&self) -> &AccountView {
744 self.inner
745 }
746}
747
748pub trait ProgramId: 'static {
755 const ID: Address;
756}
757
758pub struct SystemId;
760impl ProgramId for SystemId {
761 const ID: Address = Address::new_from_array([0u8; 32]);
762}
763
764#[cfg(test)]
765mod tests {
766 use super::*;
767
768 #[test]
769 fn signer_wrapper_is_pointer_sized_zero_cost() {
770 assert_eq!(
774 core::mem::size_of::<Signer<'static>>(),
775 core::mem::size_of::<&'static AccountView>()
776 );
777 }
778
779 #[test]
780 fn system_program_id_is_all_zero() {
781 let sys = SystemId::ID;
782 assert_eq!(sys.as_array(), &[0u8; 32]);
783 }
784}
785
786#[cfg(all(test, feature = "hopper-native-backend"))]
787mod resolver_tests {
788 use super::*;
789 use crate::layout::{HopperHeader, LayoutContract};
790
791 use hopper_native::{
792 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
793 };
794
795 const PROGRAM_A: Address = Address::new_from_array([0xA1; 32]);
796 const PROGRAM_B: Address = Address::new_from_array([0xB2; 32]);
797 const OTHER_PROGRAM: Address = Address::new_from_array([0xCC; 32]);
798
799 struct VaultPrograms;
800 impl InterfaceSpec for VaultPrograms {
801 const IDS: &'static [Address] = &[PROGRAM_A, PROGRAM_B];
802 }
803
804 #[repr(C)]
805 #[derive(Clone, Copy, Debug, Default)]
806 struct VaultV1 {
807 balance: u64,
808 }
809
810 impl crate::field_map::FieldMap for VaultV1 {
811 const FIELDS: &'static [crate::field_map::FieldInfo] = &[crate::field_map::FieldInfo::new(
812 "balance",
813 HopperHeader::SIZE,
814 8,
815 )];
816 }
817
818 impl LayoutContract for VaultV1 {
819 const DISC: u8 = 11;
820 const VERSION: u8 = 1;
821 const LAYOUT_ID: [u8; 8] = [0x11; 8];
822 const SIZE: usize = HopperHeader::SIZE + core::mem::size_of::<Self>();
823 }
824
825 impl InterfaceAccountLayout for VaultV1 {
826 type Interface = VaultPrograms;
827 }
828
829 #[repr(C)]
830 #[derive(Clone, Copy, Debug, Default)]
831 struct VaultV2 {
832 balance: u64,
833 bump: u64,
834 }
835
836 impl crate::field_map::FieldMap for VaultV2 {
837 const FIELDS: &'static [crate::field_map::FieldInfo] = &[
838 crate::field_map::FieldInfo::new("balance", HopperHeader::SIZE, 8),
839 crate::field_map::FieldInfo::new("bump", HopperHeader::SIZE + 8, 8),
840 ];
841 }
842
843 impl LayoutContract for VaultV2 {
844 const DISC: u8 = 12;
845 const VERSION: u8 = 2;
846 const LAYOUT_ID: [u8; 8] = [0x22; 8];
847 const SIZE: usize = HopperHeader::SIZE + core::mem::size_of::<Self>();
848 }
849
850 impl InterfaceAccountLayout for VaultV2 {
851 type Interface = VaultPrograms;
852 }
853
854 #[derive(Clone, Copy, Debug, Default)]
855 struct AnyVault;
856
857 impl crate::field_map::FieldMap for AnyVault {
858 const FIELDS: &'static [crate::field_map::FieldInfo] = &[];
859 }
860
861 impl LayoutContract for AnyVault {
862 const DISC: u8 = 0;
863 const VERSION: u8 = 0;
864 const LAYOUT_ID: [u8; 8] = [0; 8];
865 const SIZE: usize = HopperHeader::SIZE;
866 }
867
868 impl InterfaceAccountLayout for AnyVault {
869 type Interface = VaultPrograms;
870
871 fn validate_interface_account(
872 view: &AccountView,
873 ) -> Result<(), crate::error::ProgramError> {
874 let data = view.try_borrow()?;
875 if VaultV1::validate_header(&data).is_ok() || VaultV2::validate_header(&data).is_ok() {
876 Ok(())
877 } else {
878 Err(crate::error::ProgramError::InvalidAccountData)
879 }
880 }
881 }
882
883 enum ResolvedVault<'a> {
884 V1(crate::borrow::Ref<'a, VaultV1>),
885 V2(crate::borrow::Ref<'a, VaultV2>),
886 }
887
888 impl InterfaceAccountResolve for AnyVault {
889 type Resolved<'a> = ResolvedVault<'a>;
890
891 fn resolve<'a>(
892 view: &'a AccountView,
893 ) -> Result<Self::Resolved<'a>, crate::error::ProgramError> {
894 let info = view
895 .layout_info()
896 .ok_or(crate::error::ProgramError::AccountDataTooSmall)?;
897 if info.matches::<VaultV1>() {
898 return Ok(ResolvedVault::V1(view.load_cross_program::<VaultV1>()?));
899 }
900 if info.matches::<VaultV2>() {
901 return Ok(ResolvedVault::V2(view.load_cross_program::<VaultV2>()?));
902 }
903 Err(crate::error::ProgramError::InvalidAccountData)
904 }
905 }
906
907 fn make_account(total_data_len: usize, owner: Address) -> (std::vec::Vec<u8>, AccountView) {
908 let mut backing = std::vec![0u8; RuntimeAccount::SIZE + total_data_len];
909 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
910 unsafe {
913 raw.write(RuntimeAccount {
914 borrow_state: NOT_BORROWED,
915 is_signer: 1,
916 is_writable: 1,
917 executable: 0,
918 resize_delta: 0,
919 address: NativeAddress::new_from_array([0x44; 32]),
920 owner: NativeAddress::new_from_array(*owner.as_array()),
921 lamports: 42,
922 data_len: total_data_len as u64,
923 });
924 }
925 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
927 (backing, AccountView::from_backend(backend))
928 }
929
930 #[test]
931 fn interface_account_resolves_bounded_layout_variants() {
932 let (_v1_backing, v1_account) = make_account(VaultV1::SIZE, PROGRAM_B);
933 {
934 let mut data = v1_account.try_borrow_mut().unwrap();
935 crate::layout::init_header::<VaultV1>(&mut data).unwrap();
936 data[HopperHeader::SIZE..HopperHeader::SIZE + 8].copy_from_slice(&300u64.to_le_bytes());
937 }
938
939 let v1_vault = InterfaceAccount::<AnyVault>::try_new(&v1_account).unwrap();
940 match v1_vault.resolve().unwrap() {
941 ResolvedVault::V1(v1) => {
942 assert_eq!(v1.balance, 300);
943 }
944 ResolvedVault::V2(_) => panic!("expected v1"),
945 }
946
947 let (_backing, account) = make_account(VaultV2::SIZE, PROGRAM_A);
948 {
949 let mut data = account.try_borrow_mut().unwrap();
950 crate::layout::init_header::<VaultV2>(&mut data).unwrap();
951 data[HopperHeader::SIZE..HopperHeader::SIZE + 8].copy_from_slice(&700u64.to_le_bytes());
952 data[HopperHeader::SIZE + 8..HopperHeader::SIZE + 16]
953 .copy_from_slice(&9u64.to_le_bytes());
954 }
955
956 let vault = InterfaceAccount::<AnyVault>::try_new(&account).unwrap();
957 assert!(vault.is::<VaultV2>());
958 assert!(!vault.is::<VaultV1>());
959
960 match vault.resolve().unwrap() {
961 ResolvedVault::V2(v2) => {
962 assert_eq!(v2.balance, 700);
963 assert_eq!(v2.bump, 9);
964 }
965 ResolvedVault::V1(_) => panic!("expected v2"),
966 }
967
968 let v2 = vault.load_as::<VaultV2>().unwrap();
969 assert_eq!(v2.balance, 700);
970 assert!(vault.get_as::<VaultV1>().is_err());
971 }
972
973 #[test]
974 fn interface_account_resolver_keeps_owner_and_layout_checks() {
975 let (_wrong_owner_backing, wrong_owner) = make_account(VaultV1::SIZE, OTHER_PROGRAM);
976 {
977 let mut data = wrong_owner.try_borrow_mut().unwrap();
978 crate::layout::init_header::<VaultV1>(&mut data).unwrap();
979 }
980 let wrong_owner_result = InterfaceAccount::<AnyVault>::try_new(&wrong_owner);
981 assert!(matches!(
982 wrong_owner_result,
983 Err(crate::error::ProgramError::IncorrectProgramId)
984 ));
985
986 let (_bad_layout_backing, bad_layout) = make_account(VaultV1::SIZE, PROGRAM_B);
987 {
988 let mut data = bad_layout.try_borrow_mut().unwrap();
989 crate::layout::write_header(&mut data, 99, 1, &[0x99; 8]).unwrap();
990 }
991 let bad_layout_result = InterfaceAccount::<AnyVault>::try_new(&bad_layout);
992 assert!(matches!(
993 bad_layout_result,
994 Err(crate::error::ProgramError::InvalidAccountData)
995 ));
996 }
997}