1use crate::account::AccountView;
47use crate::address::Address;
48use crate::borrow::Ref;
49use crate::crypto::{sha256_single, Sha256Hash};
50use crate::error::ProgramError;
51use crate::layout::{HopperHeader, LayoutContract};
52use crate::zerocopy::{AccountLayout, ZeroCopy};
53use crate::ProgramResult;
54use core::marker::PhantomData;
55
56pub trait ExternalZeroCopy {
64 type View<'a>;
70
71 const OWNER: Option<Address> = None;
73 const DISCRIMINATOR: Option<&'static [u8]> = None;
75 const MIN_LEN: usize = 0;
77
78 #[inline]
81 fn validate(view: &AccountView<'_>) -> ProgramResult {
82 if let Some(owner) = Self::OWNER {
83 view.check_owned_by(&owner)?;
84 }
85 if view.data_len() < Self::MIN_LEN {
86 return Err(ProgramError::AccountDataTooSmall);
87 }
88 if let Some(discriminator) = Self::DISCRIMINATOR {
89 let data = view.try_borrow()?;
90 if data.len() < discriminator.len() {
91 return Err(ProgramError::AccountDataTooSmall);
92 }
93 if !data.starts_with(discriminator) {
94 return Err(ProgramError::InvalidAccountData);
95 }
96 }
97 Ok(())
98 }
99
100 fn view<'a>(data: Ref<'a, [u8]>) -> Result<Self::View<'a>, ProgramError>;
103}
104
105pub struct ExternalBytes<'a> {
110 data: Ref<'a, [u8]>,
111}
112
113impl<'a> ExternalBytes<'a> {
114 #[inline(always)]
116 pub const fn new(data: Ref<'a, [u8]>) -> Self {
117 Self { data }
118 }
119
120 #[inline(always)]
122 pub fn as_bytes(&self) -> &[u8] {
123 &self.data
124 }
125}
126
127impl core::ops::Deref for ExternalBytes<'_> {
128 type Target = [u8];
129
130 #[inline(always)]
131 fn deref(&self) -> &[u8] {
132 self.as_bytes()
133 }
134}
135
136pub trait ExternalResolve {
142 type Resolved<'a>;
144
145 fn resolve<'a>(view: &'a AccountView<'a>) -> Result<Self::Resolved<'a>, ProgramError>;
147}
148
149pub trait ExternalProof<T: ExternalZeroCopy> {
160 type Proof<'a>;
162
163 fn verify<'a>(account: ExternalAccount<'a, T>) -> Result<Self::Proof<'a>, ProgramError>;
165}
166
167pub struct ExternalChecked<'info, T, P>
169where
170 T: ExternalZeroCopy,
171 P: ExternalProof<T>,
172{
173 account: ExternalAccount<'info, T>,
174 proof: P::Proof<'info>,
175 _marker: PhantomData<P>,
176}
177
178impl<'info, T, P> ExternalChecked<'info, T, P>
179where
180 T: ExternalZeroCopy,
181 P: ExternalProof<T>,
182{
183 #[inline(always)]
185 pub const fn account(&self) -> ExternalAccount<'info, T> {
186 self.account
187 }
188
189 #[inline(always)]
191 pub const fn proof(&self) -> &P::Proof<'info> {
192 &self.proof
193 }
194
195 #[inline]
197 pub fn view(&self) -> Result<T::View<'info>, ProgramError> {
198 self.account.view()
199 }
200}
201
202pub trait ExternalExplainSink {
208 fn field_str(&mut self, name: &'static str, value: &str) -> ProgramResult {
209 let _ = (name, value);
210 Ok(())
211 }
212
213 fn field_bytes(&mut self, name: &'static str, value: &[u8]) -> ProgramResult {
214 let _ = (name, value);
215 Ok(())
216 }
217
218 fn field_address(&mut self, name: &'static str, value: &Address) -> ProgramResult {
219 let _ = (name, value);
220 Ok(())
221 }
222
223 fn field_u64(&mut self, name: &'static str, value: u64) -> ProgramResult {
224 let _ = (name, value);
225 Ok(())
226 }
227
228 fn field_i64(&mut self, name: &'static str, value: i64) -> ProgramResult {
229 let _ = (name, value);
230 Ok(())
231 }
232
233 fn field_bool(&mut self, name: &'static str, value: bool) -> ProgramResult {
234 let _ = (name, value);
235 Ok(())
236 }
237}
238
239pub trait ExplainExternal: ExternalZeroCopy {
241 fn explain<S: ExternalExplainSink>(account: &AccountView<'_>, sink: &mut S) -> ProgramResult;
243}
244
245pub trait ExternalLensValue: Sized {
247 const SIZE: usize;
249
250 fn read(bytes: &[u8]) -> Self;
252}
253
254macro_rules! impl_external_lens_value_le {
255 ($ty:ty, $size:expr) => {
256 impl ExternalLensValue for $ty {
257 const SIZE: usize = $size;
258
259 #[inline(always)]
260 fn read(bytes: &[u8]) -> Self {
261 let mut raw = [0u8; $size];
262 raw.copy_from_slice(bytes);
263 <$ty>::from_le_bytes(raw)
264 }
265 }
266 };
267}
268
269impl ExternalLensValue for u8 {
270 const SIZE: usize = 1;
271
272 #[inline(always)]
273 fn read(bytes: &[u8]) -> Self {
274 bytes[0]
275 }
276}
277
278impl ExternalLensValue for i8 {
279 const SIZE: usize = 1;
280
281 #[inline(always)]
282 fn read(bytes: &[u8]) -> Self {
283 bytes[0] as i8
284 }
285}
286
287impl_external_lens_value_le!(u16, 2);
288impl_external_lens_value_le!(u32, 4);
289impl_external_lens_value_le!(u64, 8);
290impl_external_lens_value_le!(u128, 16);
291impl_external_lens_value_le!(i16, 2);
292impl_external_lens_value_le!(i32, 4);
293impl_external_lens_value_le!(i64, 8);
294impl_external_lens_value_le!(i128, 16);
295
296impl<const N: usize> ExternalLensValue for [u8; N] {
297 const SIZE: usize = N;
298
299 #[inline(always)]
300 fn read(bytes: &[u8]) -> Self {
301 let mut raw = [0u8; N];
302 raw.copy_from_slice(bytes);
303 raw
304 }
305}
306
307impl ExternalLensValue for Address {
308 const SIZE: usize = 32;
309
310 #[inline(always)]
311 fn read(bytes: &[u8]) -> Self {
312 let mut raw = [0u8; 32];
313 raw.copy_from_slice(bytes);
314 Address::new_from_array(raw)
315 }
316}
317
318pub struct ExternalLens<'a, V: ExternalLensValue, const OFFSET: usize> {
320 data: Ref<'a, [u8]>,
321 _value: PhantomData<V>,
322}
323
324impl<'a, V: ExternalLensValue, const OFFSET: usize> ExternalLens<'a, V, OFFSET> {
325 #[inline]
326 fn new(data: Ref<'a, [u8]>) -> Result<Self, ProgramError> {
327 let end = OFFSET
328 .checked_add(V::SIZE)
329 .ok_or(ProgramError::ArithmeticOverflow)?;
330 if end > data.len() {
331 return Err(ProgramError::AccountDataTooSmall);
332 }
333 Ok(Self {
334 data,
335 _value: PhantomData,
336 })
337 }
338
339 #[inline(always)]
341 pub fn as_bytes(&self) -> &[u8] {
342 &self.data[OFFSET..OFFSET + V::SIZE]
343 }
344
345 #[inline(always)]
347 pub fn get(&self) -> V {
348 V::read(self.as_bytes())
349 }
350}
351
352#[repr(transparent)]
360pub struct ExternalAccount<'info, T: ExternalZeroCopy> {
361 inner: &'info AccountView<'info>,
362 _ty: PhantomData<T>,
363}
364
365impl<'info, T: ExternalZeroCopy> Clone for ExternalAccount<'info, T> {
366 fn clone(&self) -> Self {
367 *self
368 }
369}
370impl<'info, T: ExternalZeroCopy> Copy for ExternalAccount<'info, T> {}
371
372impl<T: ExternalZeroCopy> core::fmt::Debug for ExternalAccount<'_, T> {
373 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
374 f.debug_struct("ExternalAccount")
375 .field("key", self.key())
376 .field("owner", &self.owner())
377 .field("data_len", &self.data_len())
378 .finish()
379 }
380}
381
382impl<'info, T: ExternalZeroCopy> ExternalAccount<'info, T> {
383 #[inline(always)]
384 fn revalidate(&self) -> ProgramResult {
385 T::validate(self.inner)
386 }
387
388 #[inline(always)]
390 pub unsafe fn new_unchecked(view: &'info AccountView<'info>) -> Self {
395 Self {
396 inner: view,
397 _ty: PhantomData,
398 }
399 }
400
401 #[inline]
403 pub fn try_new(view: &'info AccountView<'info>) -> Result<Self, ProgramError> {
404 T::validate(view)?;
405 Ok(Self {
406 inner: view,
407 _ty: PhantomData,
408 })
409 }
410
411 #[inline(always)]
413 pub fn as_account(&self) -> &'info AccountView<'info> {
414 self.inner
415 }
416
417 #[inline(always)]
419 pub fn key(&self) -> &Address {
420 self.inner.address()
421 }
422
423 #[inline(always)]
425 pub fn owner(&self) -> Address {
426 self.inner.read_owner()
427 }
428
429 #[inline(always)]
431 pub fn data_len(&self) -> usize {
432 self.inner.data_len()
433 }
434
435 #[inline(always)]
437 pub fn data(&self) -> Result<Ref<'info, [u8]>, ProgramError> {
438 self.revalidate()?;
439 self.inner.try_borrow()
440 }
441
442 #[inline]
444 pub fn with_data<R, F>(&self, f: F) -> Result<R, ProgramError>
445 where
446 F: FnOnce(&[u8]) -> Result<R, ProgramError>,
447 {
448 let data = self.data()?;
449 f(&data)
450 }
451
452 #[inline]
454 pub fn view(&self) -> Result<T::View<'info>, ProgramError> {
455 T::view(self.data()?)
456 }
457
458 #[inline]
460 pub fn with_view<R, F>(&self, f: F) -> Result<R, ProgramError>
461 where
462 F: FnOnce(T::View<'info>) -> Result<R, ProgramError>,
463 {
464 f(self.view()?)
465 }
466
467 #[inline]
469 pub fn checked<P>(self) -> Result<ExternalChecked<'info, T, P>, ProgramError>
470 where
471 P: ExternalProof<T>,
472 {
473 self.revalidate()?;
478 let proof = P::verify(self)?;
479 Ok(ExternalChecked {
480 account: self,
481 proof,
482 _marker: PhantomData,
483 })
484 }
485
486 #[inline]
488 pub fn require_owner(&self, owner: &Address) -> Result<&Self, ProgramError> {
489 self.inner.check_owned_by(owner)?;
490 Ok(self)
491 }
492
493 #[inline]
495 pub fn lens<V: ExternalLensValue, const OFFSET: usize>(
496 &self,
497 ) -> Result<ExternalLens<'info, V, OFFSET>, ProgramError> {
498 ExternalLens::new(self.data()?)
499 }
500
501 #[inline]
503 pub fn snapshot_hash(&self) -> Result<Sha256Hash, ProgramError> {
504 let data = self.data()?;
505 sha256_single(&data)
506 }
507
508 #[inline]
510 pub fn assert_snapshot(&self, expected: &Sha256Hash) -> ProgramResult {
511 if &self.snapshot_hash()? == expected {
512 Ok(())
513 } else {
514 Err(ProgramError::InvalidAccountData)
515 }
516 }
517
518 #[inline]
520 pub fn assert_unchanged_after<R, F>(&self, f: F) -> Result<R, ProgramError>
521 where
522 F: FnOnce() -> Result<R, ProgramError>,
523 {
524 let before = self.snapshot_hash()?;
525 let result = f()?;
526 self.assert_snapshot(&before)?;
527 Ok(result)
528 }
529}
530
531impl<'info, T> ExternalAccount<'info, T>
532where
533 T: ExplainExternal,
534{
535 #[inline]
537 pub fn explain<S: ExternalExplainSink>(&self, sink: &mut S) -> ProgramResult {
538 self.revalidate()?;
539 T::explain(self.inner, sink)
540 }
541}
542
543impl<'info, T> ExternalAccount<'info, T>
544where
545 T: ExternalZeroCopy + ExternalResolve,
546{
547 #[inline]
549 pub fn resolve(&self) -> Result<T::Resolved<'info>, ProgramError> {
550 self.revalidate()?;
551 T::resolve(self.inner)
552 }
553}
554
555impl<'info, T: ExternalZeroCopy> core::ops::Deref for ExternalAccount<'info, T> {
556 type Target = AccountView<'info>;
557
558 #[inline(always)]
559 fn deref(&self) -> &AccountView<'info> {
560 self.inner
561 }
562}
563
564#[derive(Clone, Debug, PartialEq, Eq)]
570pub struct ForeignManifest {
571 pub program_id: Address,
574 pub expected_disc: u8,
576 pub expected_wire_fp: u64,
580 pub supported_epochs: core::ops::RangeInclusive<u32>,
584}
585
586impl ForeignManifest {
587 pub const fn single_epoch(
590 program_id: Address,
591 expected_disc: u8,
592 expected_wire_fp: u64,
593 epoch: u32,
594 ) -> Self {
595 Self {
596 program_id,
597 expected_disc,
598 expected_wire_fp,
599 supported_epochs: epoch..=epoch,
600 }
601 }
602}
603
604pub struct ForeignLens<'a, T: AccountLayout + LayoutContract> {
611 inner: Ref<'a, T>,
612}
613
614impl<'a, T: AccountLayout + LayoutContract> ForeignLens<'a, T> {
615 #[inline]
626 pub fn open(
627 account: &'a AccountView<'a>,
628 manifest: &ForeignManifest,
629 ) -> Result<Self, ProgramError> {
630 account.check_owned_by(&manifest.program_id)?;
632
633 let loaded: Ref<'a, T> = account.load::<T>()?;
638 if <T as AccountLayout>::DISC != manifest.expected_disc {
639 return Err(ProgramError::InvalidAccountData);
640 }
641
642 let data = account.try_borrow()?;
650 let header = HopperHeader::from_bytes(&data).ok_or(ProgramError::AccountDataTooSmall)?;
651 let layout_id = header.layout_id;
653 let schema_epoch = header.schema_epoch;
654 let actual_wire_fp = u64::from_le_bytes(layout_id);
655 if actual_wire_fp != manifest.expected_wire_fp {
656 return Err(ProgramError::InvalidAccountData);
657 }
658 if actual_wire_fp != <T as AccountLayout>::WIRE_FINGERPRINT {
659 return Err(ProgramError::InvalidAccountData);
660 }
661 if !manifest.supported_epochs.contains(&schema_epoch) {
662 return Err(ProgramError::InvalidAccountData);
663 }
664
665 drop(data);
668
669 Ok(Self { inner: loaded })
670 }
671
672 #[inline(always)]
675 pub fn get(&self) -> &T {
676 &self.inner
677 }
678
679 #[inline(always)]
687 pub fn field<F: ZeroCopy, const OFFSET: usize>(&self) -> Result<&F, ProgramError> {
688 let body_size = core::mem::size_of::<T>();
689 let field_size = core::mem::size_of::<F>();
690 if OFFSET
691 .checked_add(field_size)
692 .map(|end| end > body_size)
693 .unwrap_or(true)
694 {
695 return Err(ProgramError::AccountDataTooSmall);
696 }
697 let layout_ref: &T = &self.inner;
704 unsafe {
708 let base = layout_ref as *const T as *const u8;
709 let field_ptr = base.add(OFFSET) as *const F;
710 Ok(&*field_ptr)
711 }
712 }
713}
714
715#[cfg(test)]
716mod tests {
717 use super::*;
718 use hopper_native::{
719 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
720 };
721 const EXTERNAL_OWNER: Address = Address::new_from_array([7; 32]);
722 struct SampleExternal;
723 impl ExternalZeroCopy for SampleExternal {
724 type View<'a> = SampleExternalView<'a>;
725
726 const OWNER: Option<Address> = Some(EXTERNAL_OWNER);
727 const DISCRIMINATOR: Option<&'static [u8]> = Some(b"PX");
728 const MIN_LEN: usize = 4;
729
730 fn view<'a>(data: Ref<'a, [u8]>) -> Result<Self::View<'a>, ProgramError> {
731 Ok(SampleExternalView { data })
732 }
733 }
734 struct SampleExternalView<'a> {
735 data: Ref<'a, [u8]>,
736 }
737 impl SampleExternalView<'_> {
738 fn tag(&self) -> &[u8] {
739 &self.data[..2]
740 }
741
742 fn value(&self) -> u16 {
743 u16::from_le_bytes([self.data[2], self.data[3]])
744 }
745 }
746 enum SampleResolved<'a> {
747 Price(SampleExternalView<'a>),
748 }
749 impl ExternalResolve for SampleExternal {
750 type Resolved<'a> = SampleResolved<'a>;
751
752 fn resolve<'a>(view: &'a AccountView<'a>) -> Result<Self::Resolved<'a>, ProgramError> {
753 Ok(SampleResolved::Price(
754 ExternalAccount::<SampleExternal>::try_new(view)?.view()?,
755 ))
756 }
757 }
758 struct SampleValueProof;
759 struct BlindProof;
760 struct SampleValueChecked {
761 value: u16,
762 }
763 impl ExternalProof<SampleExternal> for BlindProof {
764 type Proof<'a> = ();
765
766 fn verify<'a>(
767 _account: ExternalAccount<'a, SampleExternal>,
768 ) -> Result<Self::Proof<'a>, ProgramError> {
769 Ok(())
770 }
771 }
772 impl ExternalProof<SampleExternal> for SampleValueProof {
773 type Proof<'a> = SampleValueChecked;
774
775 fn verify<'a>(
776 account: ExternalAccount<'a, SampleExternal>,
777 ) -> Result<Self::Proof<'a>, ProgramError> {
778 let value = account.view()?.value();
779 if value == view_u16(b"12") {
780 Ok(SampleValueChecked { value })
781 } else {
782 Err(ProgramError::InvalidAccountData)
783 }
784 }
785 }
786 impl ExplainExternal for SampleExternal {
787 fn explain<S: ExternalExplainSink>(
788 account: &AccountView<'_>,
789 sink: &mut S,
790 ) -> ProgramResult {
791 let external = ExternalAccount::<SampleExternal>::try_new(account)?;
792 external.with_view(|view| {
793 sink.field_str("adapter", "SampleExternal")?;
794 sink.field_u64("value", view.value() as u64)
795 })
796 }
797 }
798 #[derive(Default)]
799 struct CountingExplainSink {
800 fields: usize,
801 }
802 impl ExternalExplainSink for CountingExplainSink {
803 fn field_str(&mut self, _name: &'static str, _value: &str) -> ProgramResult {
804 self.fields += 1;
805 Ok(())
806 }
807
808 fn field_u64(&mut self, _name: &'static str, _value: u64) -> ProgramResult {
809 self.fields += 1;
810 Ok(())
811 }
812 }
813 fn make_external_account(
814 owner: Address,
815 data: &[u8],
816 ) -> (std::vec::Vec<u64>, AccountView<'static>) {
817 let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + data.len()).div_ceil(8)];
818 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
819 unsafe {
822 raw.write(RuntimeAccount {
823 borrow_state: NOT_BORROWED,
824 is_signer: 0,
825 is_writable: 0,
826 executable: 0,
827 resize_delta: 0,
828 address: NativeAddress::new_from_array([3; 32]),
829 owner: NativeAddress::new_from_array(owner.to_bytes()),
830 lamports: 1,
831 data_len: data.len() as u64,
832 });
833 let data_ptr = (backing.as_mut_ptr() as *mut u8).add(RuntimeAccount::SIZE);
834 core::ptr::copy_nonoverlapping(data.as_ptr(), data_ptr, data.len());
835 }
836 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
838 (backing, AccountView::from_backend(backend))
839 }
840
841 #[test]
842 fn manifest_single_epoch_is_inclusive_single_value() {
843 let program = Address::new_from_array([7u8; 32]);
844 let m = ForeignManifest::single_epoch(program, 42, 0xDEAD_BEEF_1234_5678, 3);
845 assert!(m.supported_epochs.contains(&3));
846 assert!(!m.supported_epochs.contains(&2));
847 assert!(!m.supported_epochs.contains(&4));
848 assert_eq!(m.expected_disc, 42);
849 assert_eq!(m.expected_wire_fp, 0xDEAD_BEEF_1234_5678);
850 }
851
852 #[test]
853 fn manifest_range_spans_inclusive() {
854 let program = Address::new_from_array([0u8; 32]);
855 let m = ForeignManifest {
856 program_id: program,
857 expected_disc: 1,
858 expected_wire_fp: 0,
859 supported_epochs: 2..=5,
860 };
861 for ok in [2u32, 3, 4, 5] {
862 assert!(m.supported_epochs.contains(&ok), "{ok}");
863 }
864 for fail in [0u32, 1, 6, 100] {
865 assert!(!m.supported_epochs.contains(&fail), "{fail}");
866 }
867 }
868 #[test]
869 fn external_account_validates_owner_discriminator_and_length() {
870 let (_backing, account) = make_external_account(EXTERNAL_OWNER, b"PX12");
871 let external = ExternalAccount::<SampleExternal>::try_new(&account).unwrap();
872 assert_eq!(external.owner(), EXTERNAL_OWNER);
873 assert_eq!(external.data_len(), 4);
874 external
875 .with_data(|data| {
876 assert_eq!(data, b"PX12");
877 Ok(())
878 })
879 .unwrap();
880 external
881 .with_view(|view| {
882 assert_eq!(view.tag(), b"PX");
883 assert_eq!(view.value(), u16::from_le_bytes(*b"12"));
884 Ok(())
885 })
886 .unwrap();
887 assert_eq!(external.lens::<u16, 2>().unwrap().get(), view_u16(b"12"));
888 let snapshot = external.snapshot_hash().unwrap();
889 external.assert_snapshot(&snapshot).unwrap();
890 let resolved = external.resolve().unwrap();
891 match resolved {
892 SampleResolved::Price(view) => assert_eq!(view.value(), view_u16(b"12")),
893 }
894 let checked = external.checked::<SampleValueProof>().unwrap();
895 assert_eq!(checked.proof().value, view_u16(b"12"));
896 let mut sink = CountingExplainSink::default();
897 external.explain(&mut sink).unwrap();
898 assert_eq!(sink.fields, 2);
899 }
900 fn view_u16(bytes: &[u8; 2]) -> u16 {
901 u16::from_le_bytes(*bytes)
902 }
903 #[test]
904 fn external_account_rejects_wrong_owner_or_prefix() {
905 let (_wrong_owner_backing, wrong_owner) =
906 make_external_account(Address::new_from_array([8; 32]), b"PX12");
907 assert_eq!(
908 ExternalAccount::<SampleExternal>::try_new(&wrong_owner).unwrap_err(),
909 ProgramError::IncorrectProgramId
910 );
911
912 let (_wrong_prefix_backing, wrong_prefix) = make_external_account(EXTERNAL_OWNER, b"NO12");
913 assert_eq!(
914 ExternalAccount::<SampleExternal>::try_new(&wrong_prefix).unwrap_err(),
915 ProgramError::InvalidAccountData
916 );
917
918 let (_short_backing, short) = make_external_account(EXTERNAL_OWNER, b"PX");
919 assert_eq!(
920 ExternalAccount::<SampleExternal>::try_new(&short).unwrap_err(),
921 ProgramError::AccountDataTooSmall
922 );
923 }
924
925 #[test]
926 fn external_account_revalidates_owner_and_discriminator_after_binding() {
927 let (_owner_backing, owner_changed) = make_external_account(EXTERNAL_OWNER, b"PX12");
928 let external = ExternalAccount::<SampleExternal>::try_new(&owner_changed).unwrap();
929 let checked = external.checked::<BlindProof>().unwrap();
930 unsafe {
933 owner_changed.assign(&Address::new_from_array([8; 32]));
934 }
935 assert!(matches!(
936 external.view(),
937 Err(ProgramError::IncorrectProgramId)
938 ));
939 assert!(matches!(
940 checked.view(),
941 Err(ProgramError::IncorrectProgramId)
942 ));
943 assert!(matches!(
944 external.checked::<BlindProof>(),
945 Err(ProgramError::IncorrectProgramId)
946 ));
947
948 let (_disc_backing, discriminator_changed) = make_external_account(EXTERNAL_OWNER, b"PX12");
949 let external = ExternalAccount::<SampleExternal>::try_new(&discriminator_changed).unwrap();
950 let checked = external.checked::<BlindProof>().unwrap();
951 {
952 let mut data = discriminator_changed.try_borrow_mut().unwrap();
953 data[..2].copy_from_slice(b"NO");
954 }
955 assert!(matches!(
956 external.view(),
957 Err(ProgramError::InvalidAccountData)
958 ));
959 assert!(matches!(
960 checked.view(),
961 Err(ProgramError::InvalidAccountData)
962 ));
963 assert!(matches!(
964 external.checked::<BlindProof>(),
965 Err(ProgramError::InvalidAccountData)
966 ));
967 }
968}