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 {
706 let base = layout_ref as *const T as *const u8;
707 let field_ptr = base.add(OFFSET) as *const F;
708 Ok(&*field_ptr)
709 }
710 }
711}
712
713#[cfg(test)]
714mod tests {
715 use super::*;
716 use hopper_native::{
717 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
718 };
719 const EXTERNAL_OWNER: Address = Address::new_from_array([7; 32]);
720 struct SampleExternal;
721 impl ExternalZeroCopy for SampleExternal {
722 type View<'a> = SampleExternalView<'a>;
723
724 const OWNER: Option<Address> = Some(EXTERNAL_OWNER);
725 const DISCRIMINATOR: Option<&'static [u8]> = Some(b"PX");
726 const MIN_LEN: usize = 4;
727
728 fn view<'a>(data: Ref<'a, [u8]>) -> Result<Self::View<'a>, ProgramError> {
729 Ok(SampleExternalView { data })
730 }
731 }
732 struct SampleExternalView<'a> {
733 data: Ref<'a, [u8]>,
734 }
735 impl SampleExternalView<'_> {
736 fn tag(&self) -> &[u8] {
737 &self.data[..2]
738 }
739
740 fn value(&self) -> u16 {
741 u16::from_le_bytes([self.data[2], self.data[3]])
742 }
743 }
744 enum SampleResolved<'a> {
745 Price(SampleExternalView<'a>),
746 }
747 impl ExternalResolve for SampleExternal {
748 type Resolved<'a> = SampleResolved<'a>;
749
750 fn resolve<'a>(view: &'a AccountView<'a>) -> Result<Self::Resolved<'a>, ProgramError> {
751 Ok(SampleResolved::Price(
752 ExternalAccount::<SampleExternal>::try_new(view)?.view()?,
753 ))
754 }
755 }
756 struct SampleValueProof;
757 struct BlindProof;
758 struct SampleValueChecked {
759 value: u16,
760 }
761 impl ExternalProof<SampleExternal> for BlindProof {
762 type Proof<'a> = ();
763
764 fn verify<'a>(
765 _account: ExternalAccount<'a, SampleExternal>,
766 ) -> Result<Self::Proof<'a>, ProgramError> {
767 Ok(())
768 }
769 }
770 impl ExternalProof<SampleExternal> for SampleValueProof {
771 type Proof<'a> = SampleValueChecked;
772
773 fn verify<'a>(
774 account: ExternalAccount<'a, SampleExternal>,
775 ) -> Result<Self::Proof<'a>, ProgramError> {
776 let value = account.view()?.value();
777 if value == view_u16(b"12") {
778 Ok(SampleValueChecked { value })
779 } else {
780 Err(ProgramError::InvalidAccountData)
781 }
782 }
783 }
784 impl ExplainExternal for SampleExternal {
785 fn explain<S: ExternalExplainSink>(
786 account: &AccountView<'_>,
787 sink: &mut S,
788 ) -> ProgramResult {
789 let external = ExternalAccount::<SampleExternal>::try_new(account)?;
790 external.with_view(|view| {
791 sink.field_str("adapter", "SampleExternal")?;
792 sink.field_u64("value", view.value() as u64)
793 })
794 }
795 }
796 #[derive(Default)]
797 struct CountingExplainSink {
798 fields: usize,
799 }
800 impl ExternalExplainSink for CountingExplainSink {
801 fn field_str(&mut self, _name: &'static str, _value: &str) -> ProgramResult {
802 self.fields += 1;
803 Ok(())
804 }
805
806 fn field_u64(&mut self, _name: &'static str, _value: u64) -> ProgramResult {
807 self.fields += 1;
808 Ok(())
809 }
810 }
811 fn make_external_account(
812 owner: Address,
813 data: &[u8],
814 ) -> (std::vec::Vec<u64>, AccountView<'static>) {
815 let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + data.len()).div_ceil(8)];
816 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
817 unsafe {
820 raw.write(RuntimeAccount {
821 borrow_state: NOT_BORROWED,
822 is_signer: 0,
823 is_writable: 0,
824 executable: 0,
825 resize_delta: 0,
826 address: NativeAddress::new_from_array([3; 32]),
827 owner: NativeAddress::new_from_array(owner.to_bytes()),
828 lamports: 1,
829 data_len: data.len() as u64,
830 });
831 let data_ptr = (backing.as_mut_ptr() as *mut u8).add(RuntimeAccount::SIZE);
832 core::ptr::copy_nonoverlapping(data.as_ptr(), data_ptr, data.len());
833 }
834 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
836 (backing, AccountView::from_backend(backend))
837 }
838
839 #[test]
840 fn manifest_single_epoch_is_inclusive_single_value() {
841 let program = Address::new_from_array([7u8; 32]);
842 let m = ForeignManifest::single_epoch(program, 42, 0xDEAD_BEEF_1234_5678, 3);
843 assert!(m.supported_epochs.contains(&3));
844 assert!(!m.supported_epochs.contains(&2));
845 assert!(!m.supported_epochs.contains(&4));
846 assert_eq!(m.expected_disc, 42);
847 assert_eq!(m.expected_wire_fp, 0xDEAD_BEEF_1234_5678);
848 }
849
850 #[test]
851 fn manifest_range_spans_inclusive() {
852 let program = Address::new_from_array([0u8; 32]);
853 let m = ForeignManifest {
854 program_id: program,
855 expected_disc: 1,
856 expected_wire_fp: 0,
857 supported_epochs: 2..=5,
858 };
859 for ok in [2u32, 3, 4, 5] {
860 assert!(m.supported_epochs.contains(&ok), "{ok}");
861 }
862 for fail in [0u32, 1, 6, 100] {
863 assert!(!m.supported_epochs.contains(&fail), "{fail}");
864 }
865 }
866 #[test]
867 fn external_account_validates_owner_discriminator_and_length() {
868 let (_backing, account) = make_external_account(EXTERNAL_OWNER, b"PX12");
869 let external = ExternalAccount::<SampleExternal>::try_new(&account).unwrap();
870 assert_eq!(external.owner(), EXTERNAL_OWNER);
871 assert_eq!(external.data_len(), 4);
872 external
873 .with_data(|data| {
874 assert_eq!(data, b"PX12");
875 Ok(())
876 })
877 .unwrap();
878 external
879 .with_view(|view| {
880 assert_eq!(view.tag(), b"PX");
881 assert_eq!(view.value(), u16::from_le_bytes(*b"12"));
882 Ok(())
883 })
884 .unwrap();
885 assert_eq!(external.lens::<u16, 2>().unwrap().get(), view_u16(b"12"));
886 let snapshot = external.snapshot_hash().unwrap();
887 external.assert_snapshot(&snapshot).unwrap();
888 let resolved = external.resolve().unwrap();
889 match resolved {
890 SampleResolved::Price(view) => assert_eq!(view.value(), view_u16(b"12")),
891 }
892 let checked = external.checked::<SampleValueProof>().unwrap();
893 assert_eq!(checked.proof().value, view_u16(b"12"));
894 let mut sink = CountingExplainSink::default();
895 external.explain(&mut sink).unwrap();
896 assert_eq!(sink.fields, 2);
897 }
898 fn view_u16(bytes: &[u8; 2]) -> u16 {
899 u16::from_le_bytes(*bytes)
900 }
901 #[test]
902 fn external_account_rejects_wrong_owner_or_prefix() {
903 let (_wrong_owner_backing, wrong_owner) =
904 make_external_account(Address::new_from_array([8; 32]), b"PX12");
905 assert_eq!(
906 ExternalAccount::<SampleExternal>::try_new(&wrong_owner).unwrap_err(),
907 ProgramError::IncorrectProgramId
908 );
909
910 let (_wrong_prefix_backing, wrong_prefix) = make_external_account(EXTERNAL_OWNER, b"NO12");
911 assert_eq!(
912 ExternalAccount::<SampleExternal>::try_new(&wrong_prefix).unwrap_err(),
913 ProgramError::InvalidAccountData
914 );
915
916 let (_short_backing, short) = make_external_account(EXTERNAL_OWNER, b"PX");
917 assert_eq!(
918 ExternalAccount::<SampleExternal>::try_new(&short).unwrap_err(),
919 ProgramError::AccountDataTooSmall
920 );
921 }
922
923 #[test]
924 fn external_account_revalidates_owner_and_discriminator_after_binding() {
925 let (_owner_backing, owner_changed) = make_external_account(EXTERNAL_OWNER, b"PX12");
926 let external = ExternalAccount::<SampleExternal>::try_new(&owner_changed).unwrap();
927 let checked = external.checked::<BlindProof>().unwrap();
928 unsafe {
931 owner_changed.assign(&Address::new_from_array([8; 32]));
932 }
933 assert!(matches!(
934 external.view(),
935 Err(ProgramError::IncorrectProgramId)
936 ));
937 assert!(matches!(
938 checked.view(),
939 Err(ProgramError::IncorrectProgramId)
940 ));
941 assert!(matches!(
942 external.checked::<BlindProof>(),
943 Err(ProgramError::IncorrectProgramId)
944 ));
945
946 let (_disc_backing, discriminator_changed) = make_external_account(EXTERNAL_OWNER, b"PX12");
947 let external = ExternalAccount::<SampleExternal>::try_new(&discriminator_changed).unwrap();
948 let checked = external.checked::<BlindProof>().unwrap();
949 {
950 let mut data = discriminator_changed.try_borrow_mut().unwrap();
951 data[..2].copy_from_slice(b"NO");
952 }
953 assert!(matches!(
954 external.view(),
955 Err(ProgramError::InvalidAccountData)
956 ));
957 assert!(matches!(
958 checked.view(),
959 Err(ProgramError::InvalidAccountData)
960 ));
961 assert!(matches!(
962 external.checked::<BlindProof>(),
963 Err(ProgramError::InvalidAccountData)
964 ));
965 }
966}