Skip to main content

hopper_macros/
lib.rs

1//! # Hopper Macros (Declarative)
2//!
3//! **Support infrastructure, not the main public entry.** These
4//! `macro_rules!` macros generate layout structure, field metadata, and
5//! validation primitives at compile time. They are deliberately limited
6//! to *structure* generation: no hidden runtime logic, no surprise
7//! control flow, no validation engines.
8//!
9//! Programs that want richer DX should enable the `proc-macros` feature
10//! and reach for `#[hopper::state]`, `#[hopper::context]`, and
11//! `#[hopper::program]` in the `hopper-derive` package. Programs that prefer a
12//! zero-tool-chain authoring path can use these declarative macros
13//! directly, both paths lower to the same runtime.
14//!
15//! ## Topical index
16//!
17//! | Section            | Macros |
18//! |--------------------|--------|
19//! | Layout             | `hopper_layout!` |
20//! | Validation         | `hopper_check!`, `hopper_error!`, `hopper_require!` |
21//! | Lifecycle          | `hopper_init!`, `hopper_close!` |
22//! | Dispatch           | `hopper_register_discs!` |
23//! | PDA                | `hopper_verify_pda!` |
24//! | Invariants         | `hopper_invariant!` |
25//! | Manifest           | `hopper_manifest!` |
26//! | Segments           | `hopper_segment!` |
27//! | Pipelines          | `hopper_validate!` |
28//! | Virtual state      | `hopper_virtual!` |
29//! | Compat / ABI       | `hopper_assert_compatible!`, `hopper_assert_fingerprint!`, `const_assert_pod!` |
30//! | Cross-program      | `hopper_interface!` |
31//! | Account structs    | `hopper_accounts!` |
32//!
33//! Every macro below is `#[macro_export]`ed and usable from the root of
34//! the `hopper-macros` crate regardless of the section banner it lives
35//! under. The banners exist only to help readers navigate the file.
36
37#![no_std]
38
39// ═════════════════════════════════════════════════════════════════════
40//  Section: Layout
41// ═════════════════════════════════════════════════════════════════════
42
43/// Define a zero-copy account layout.
44///
45/// Generates a `#[repr(C)]` struct with:
46/// - 16-byte Hopper header
47/// - Alignment-1 fields
48/// - Deterministic `LAYOUT_ID` via SHA-256
49/// - Tiered loading: `load`, `load_mut`, `load_cross_program`, `load_compatible`, `load_unverified`
50/// - Compile-time size and alignment assertions
51///
52/// # Example
53///
54/// ```ignore
55/// hopper_layout! {
56///     pub struct Vault, disc = 1, version = 1 {
57///         authority: [u8; 32]  = 32,
58///         mint:      [u8; 32]  = 32,
59///         balance:   WireU64   = 8,
60///         bump:      u8        = 1,
61///     }
62/// }
63/// ```
64#[macro_export]
65macro_rules! hopper_layout {
66    (
67        $(#[$attr:meta])*
68        pub struct $name:ident, disc = $disc:literal, version = $ver:literal
69        {
70            $(
71                $(#[$field_attr:meta])*
72                $field:ident : $fty:ty = $fsize:literal
73            ),+ $(,)?
74        }
75    ) => {
76        $(#[$attr])*
77        #[derive(Clone, Copy)]
78        #[repr(C)]
79        pub struct $name {
80            pub header: $crate::hopper_core::account::AccountHeader,
81            $(
82                $(#[$field_attr])*
83                pub $field: $fty,
84            )+
85        }
86
87        // Compile-time assertions
88        const _: () = {
89            // Size check: header + sum of field sizes
90            let expected = $crate::hopper_core::account::HEADER_LEN $( + $fsize )+;
91            assert!(
92                core::mem::size_of::<$name>() == expected,
93                "Layout size mismatch: struct size != declared field sizes + header"
94            );
95            // Alignment-1 check
96            assert!(
97                core::mem::align_of::<$name>() == 1,
98                "Layout alignment must be 1 for zero-copy safety"
99            );
100        };
101
102        // Bytemuck proof (Hopper Safety Audit Must-Fix #5): every
103        // field must itself satisfy `bytemuck::Pod + Zeroable`.
104        // Hopper's Pod supertrait requires these impls; because all
105        // field types generated by `hopper_layout!` are Hopper wire
106        // types (which carry their own bytemuck impls) or byte
107        // arrays, these blanket claims are safe.
108        #[cfg(feature = "hopper-native-backend")]
109        unsafe impl $crate::hopper_runtime::__hopper_native::bytemuck::Zeroable for $name {}
110        #[cfg(feature = "hopper-native-backend")]
111        unsafe impl $crate::hopper_runtime::__hopper_native::bytemuck::Pod for $name {}
112
113        // SAFETY: #[repr(C)] over alignment-1 fields, all bit patterns valid
114        // for the constituent Pod types (header, wire integers, byte arrays).
115        unsafe impl $crate::hopper_core::account::Pod for $name {}
116
117        // Audit final-API Step 5 seal. `hopper_layout!` stamps the
118        // Hopper-authored marker so the `ZeroCopy` blanket picks up
119        // declarative layouts the same way it picks up `#[hopper::state]`
120        // ones.
121        unsafe impl $crate::hopper_runtime::__sealed::HopperZeroCopySealed for $name {}
122
123        impl $crate::hopper_core::account::FixedLayout for $name {
124            const SIZE: usize = $crate::hopper_core::account::HEADER_LEN $( + $fsize )+;
125        }
126
127        impl $crate::hopper_core::field_map::FieldMap for $name {
128            const FIELDS: &'static [$crate::hopper_core::field_map::FieldInfo] = {
129                const FIELD_COUNT: usize = 0 $( + { let _ = stringify!($field); 1 } )+;
130                const NAMES: [&str; FIELD_COUNT] = [ $( stringify!($field) ),+ ];
131                const SIZES: [usize; FIELD_COUNT] = [ $( $fsize ),+ ];
132                const FIELDS: [$crate::hopper_core::field_map::FieldInfo; FIELD_COUNT] = {
133                    let mut result = [$crate::hopper_core::field_map::FieldInfo::new("", 0, 0); FIELD_COUNT];
134                    let mut offset = $crate::hopper_core::account::HEADER_LEN;
135                    let mut index = 0;
136                    while index < FIELD_COUNT {
137                        result[index] = $crate::hopper_core::field_map::FieldInfo::new(
138                            NAMES[index],
139                            offset,
140                            SIZES[index],
141                        );
142                        offset += SIZES[index];
143                        index += 1;
144                    }
145                    result
146                };
147                &FIELDS
148            };
149        }
150
151        impl $crate::hopper_runtime::LayoutContract for $name {
152            const DISC: u8 = $disc;
153            const VERSION: u8 = $ver;
154            const LAYOUT_ID: [u8; 8] = $name::LAYOUT_ID;
155            const SIZE: usize = $name::LEN;
156            const TYPE_OFFSET: usize = 0;
157        }
158
159        impl $crate::hopper_schema::SchemaExport for $name {
160            fn layout_manifest() -> $crate::hopper_schema::LayoutManifest {
161                const FIELD_COUNT: usize = 0 $( + { let _ = stringify!($field); 1 } )+;
162                const SIZES: [u16; FIELD_COUNT] = [ $( $fsize ),+ ];
163                const NAMES: [&str; FIELD_COUNT] = [ $( stringify!($field) ),+ ];
164                const TYPES: [&str; FIELD_COUNT] = [ $( stringify!($fty) ),+ ];
165                const FIELDS: [$crate::hopper_schema::FieldDescriptor; FIELD_COUNT] = {
166                    let mut result = [$crate::hopper_schema::FieldDescriptor {
167                        name: "", canonical_type: "", size: 0, offset: 0,
168                        intent: $crate::hopper_schema::FieldIntent::Custom,
169                    }; FIELD_COUNT];
170                    let mut offset = $crate::hopper_core::account::HEADER_LEN as u16;
171                    let mut index = 0;
172                    while index < FIELD_COUNT {
173                        result[index] = $crate::hopper_schema::FieldDescriptor {
174                            name: NAMES[index],
175                            canonical_type: TYPES[index],
176                            size: SIZES[index],
177                            offset,
178                            intent: $crate::hopper_schema::FieldIntent::Custom,
179                        };
180                        offset += SIZES[index];
181                        index += 1;
182                    }
183                    result
184                };
185                $crate::hopper_schema::LayoutManifest {
186                    name: stringify!($name),
187                    version: <$name>::VERSION,
188                    disc: <$name>::DISC,
189                    layout_id: <$name>::LAYOUT_ID,
190                    total_size: <$name>::LEN,
191                    field_count: FIELD_COUNT,
192                    fields: &FIELDS,
193                }
194            }
195        }
196
197        impl $name {
198            /// Total byte size of this layout.
199            pub const LEN: usize = $crate::hopper_core::account::HEADER_LEN $( + $fsize )+;
200
201            /// Discriminator tag.
202            pub const DISC: u8 = $disc;
203
204            /// Layout version.
205            pub const VERSION: u8 = $ver;
206
207            /// Deterministic layout fingerprint.
208            ///
209            /// SHA-256 of: `"hopper:v1:Name:version:field:type:size,..."`
210            /// First 8 bytes for efficient comparison.
211            pub const LAYOUT_ID: [u8; 8] = {
212                // Build the canonical hash input at compile time
213                const INPUT: &str = concat!(
214                    "hopper:v1:",
215                    stringify!($name), ":",
216                    stringify!($ver), ":",
217                    $( stringify!($field), ":", stringify!($fty), ":", stringify!($fsize), ",", )+
218                );
219                const HASH: [u8; 32] = $crate::hopper_core::__sha256_const(INPUT.as_bytes());
220                [
221                    HASH[0], HASH[1], HASH[2], HASH[3],
222                    HASH[4], HASH[5], HASH[6], HASH[7],
223                ]
224            };
225
226            /// Zero-copy overlay over an already-borrowed byte slice (immutable).
227            ///
228            /// Prefer [`Self::load`] for account data so owner/header checks and
229            /// Hopper borrow tracking stay in force. Use this helper for tests,
230            /// scratch buffers, and explicitly sliced extension segments whose
231            /// surrounding account was already validated.
232            #[inline(always)]
233            pub fn overlay(data: &[u8]) -> Result<&Self, $crate::hopper_runtime::error::ProgramError> {
234                $crate::hopper_core::account::pod_from_bytes::<Self>(data)
235            }
236
237            /// Zero-copy overlay over an already-borrowed byte slice (mutable).
238            ///
239            /// Prefer [`Self::load_mut`] for account data so owner/header checks,
240            /// writable checks, and Hopper borrow tracking stay in force. Use this
241            /// helper for tests, scratch buffers, and explicitly sliced extension
242            /// segments whose surrounding account was already validated.
243            #[inline(always)]
244            pub fn overlay_mut(data: &mut [u8]) -> Result<&mut Self, $crate::hopper_runtime::error::ProgramError> {
245                $crate::hopper_core::account::pod_from_bytes_mut::<Self>(data)
246            }
247
248            /// Tier 1: Full validation load (own program accounts).
249            ///
250            /// Validates: owner + discriminator + version + layout_id + exact size.
251            #[inline]
252            pub fn load<'a>(
253                account: &'a $crate::hopper_runtime::AccountView,
254                program_id: &$crate::hopper_runtime::Address,
255            ) -> Result<
256                $crate::hopper_core::account::VerifiedAccount<'a, Self>,
257                $crate::hopper_runtime::error::ProgramError,
258            > {
259                $crate::hopper_core::check::check_owner(account, program_id)?;
260                let data = account.try_borrow()?;
261                $crate::hopper_core::account::check_header(
262                    &*data,
263                    Self::DISC,
264                    Self::VERSION,
265                    &Self::LAYOUT_ID,
266                )?;
267                $crate::hopper_core::check::check_size(&*data, Self::LEN)?;
268                $crate::hopper_core::account::VerifiedAccount::from_ref(data)
269            }
270
271            /// Tier 1m: Full validation load (mutable).
272            #[inline]
273            pub fn load_mut<'a>(
274                account: &'a $crate::hopper_runtime::AccountView,
275                program_id: &$crate::hopper_runtime::Address,
276            ) -> Result<
277                $crate::hopper_core::account::VerifiedAccountMut<'a, Self>,
278                $crate::hopper_runtime::error::ProgramError,
279            > {
280                $crate::hopper_core::check::check_owner(account, program_id)?;
281                $crate::hopper_core::check::check_writable(account)?;
282                let data = account.try_borrow_mut()?;
283                $crate::hopper_core::account::check_header(
284                    &*data,
285                    Self::DISC,
286                    Self::VERSION,
287                    &Self::LAYOUT_ID,
288                )?;
289                $crate::hopper_core::check::check_size(&*data, Self::LEN)?;
290                $crate::hopper_core::account::VerifiedAccountMut::from_ref_mut(data)
291            }
292
293            /// Tier 2: Foreign account load (cross-program reads).
294            ///
295            /// Validates: owner + layout_id + exact size (no disc/version check).
296            ///
297            /// **Deprecated:** Renamed to `load_cross_program()` for clarity.
298            #[deprecated(since = "0.2.0", note = "renamed to load_cross_program()")]
299            #[inline]
300            pub fn load_foreign<'a>(
301                account: &'a $crate::hopper_runtime::AccountView,
302                expected_owner: &$crate::hopper_runtime::Address,
303            ) -> Result<
304                $crate::hopper_core::account::VerifiedAccount<'a, Self>,
305                $crate::hopper_runtime::error::ProgramError,
306            > {
307                $crate::hopper_core::check::check_owner(account, expected_owner)?;
308                let data = account.try_borrow()?;
309                let layout_id = $crate::hopper_core::account::read_layout_id(&*data)?;
310                if layout_id != Self::LAYOUT_ID {
311                    return Err($crate::hopper_runtime::error::ProgramError::InvalidAccountData);
312                }
313                $crate::hopper_core::check::check_size(&*data, Self::LEN)?;
314                $crate::hopper_core::account::VerifiedAccount::from_ref(data)
315            }
316
317            /// Cross-program account load (reads accounts owned by other programs).
318            ///
319            /// Validates: owner + layout_id + exact size (no disc/version check).
320            /// This is the successor to `load_foreign()` with a clearer name.
321            #[inline]
322            pub fn load_cross_program<'a>(
323                account: &'a $crate::hopper_runtime::AccountView,
324                expected_owner: &$crate::hopper_runtime::Address,
325            ) -> Result<
326                $crate::hopper_core::account::VerifiedAccount<'a, Self>,
327                $crate::hopper_runtime::error::ProgramError,
328            > {
329                $crate::hopper_core::check::check_owner(account, expected_owner)?;
330                let data = account.try_borrow()?;
331                let layout_id = $crate::hopper_core::account::read_layout_id(&*data)?;
332                if layout_id != Self::LAYOUT_ID {
333                    return Err($crate::hopper_runtime::error::ProgramError::InvalidAccountData);
334                }
335                $crate::hopper_core::check::check_size(&*data, Self::LEN)?;
336                $crate::hopper_core::account::VerifiedAccount::from_ref(data)
337            }
338
339            /// Tier 3: Version-compatible load for migration scenarios.
340            ///
341            /// Validates: owner + discriminator + minimum version + minimum size.
342            /// Does **not** check layout_id, so it accepts any version of this
343            /// account type whose version byte is ≥ `min_version` and whose
344            /// data is at least as large as this layout.
345            ///
346            /// Use this during migration rollouts when a single instruction
347            /// must accept both old and new versions of an account.
348            ///
349            /// # Arguments
350            /// * `min_version`: lowest acceptable version byte (header byte 1).
351            ///   Pass `1` to accept V1+, `2` to accept V2+ only, etc.
352            #[inline]
353            pub fn load_compatible<'a>(
354                account: &'a $crate::hopper_runtime::AccountView,
355                program_id: &$crate::hopper_runtime::Address,
356                min_version: u8,
357            ) -> Result<
358                $crate::hopper_core::account::VerifiedAccount<'a, Self>,
359                $crate::hopper_runtime::error::ProgramError,
360            > {
361                $crate::hopper_core::check::check_owner(account, program_id)?;
362                let data = account.try_borrow()?;
363                if data.len() < $crate::hopper_core::account::HEADER_LEN {
364                    return Err($crate::hopper_runtime::error::ProgramError::AccountDataTooSmall);
365                }
366                // Check discriminator (same account type, any version).
367                if data[0] != Self::DISC {
368                    return Err($crate::hopper_runtime::error::ProgramError::InvalidAccountData);
369                }
370                // Check minimum version.
371                if data[1] < min_version {
372                    return Err($crate::hopper_runtime::error::ProgramError::InvalidAccountData);
373                }
374                // Minimum size (account may be larger if migrated to a newer version).
375                if data.len() < Self::LEN {
376                    return Err($crate::hopper_runtime::error::ProgramError::AccountDataTooSmall);
377                }
378                $crate::hopper_core::account::VerifiedAccount::from_ref(data)
379            }
380
381            /// Tier 3m: Version-compatible load (mutable).
382            ///
383            /// Same as [`load_compatible`] but returns a mutable overlay.
384            #[inline]
385            pub fn load_compatible_mut<'a>(
386                account: &'a $crate::hopper_runtime::AccountView,
387                program_id: &$crate::hopper_runtime::Address,
388                min_version: u8,
389            ) -> Result<
390                $crate::hopper_core::account::VerifiedAccountMut<'a, Self>,
391                $crate::hopper_runtime::error::ProgramError,
392            > {
393                $crate::hopper_core::check::check_owner(account, program_id)?;
394                $crate::hopper_core::check::check_writable(account)?;
395                let data = account.try_borrow_mut()?;
396                if data.len() < $crate::hopper_core::account::HEADER_LEN {
397                    return Err($crate::hopper_runtime::error::ProgramError::AccountDataTooSmall);
398                }
399                if data[0] != Self::DISC {
400                    return Err($crate::hopper_runtime::error::ProgramError::InvalidAccountData);
401                }
402                if data[1] < min_version {
403                    return Err($crate::hopper_runtime::error::ProgramError::InvalidAccountData);
404                }
405                if data.len() < Self::LEN {
406                    return Err($crate::hopper_runtime::error::ProgramError::AccountDataTooSmall);
407                }
408                $crate::hopper_core::account::VerifiedAccountMut::from_ref_mut(data)
409            }
410
411            /// Tier 4: Unchecked load (caller assumes all risk).
412            ///
413            /// # Safety
414            /// Caller must guarantee the data is valid for this layout.
415            ///
416            /// **Deprecated:** Use `load()` for safe access. For explicit
417            /// unsafe access, use the raw byte pointer directly.
418            #[deprecated(since = "0.2.0", note = "use load() for safe access")]
419            #[inline(always)]
420            pub unsafe fn load_unchecked(data: &[u8]) -> &Self {
421                &*(data.as_ptr() as *const Self)
422            }
423
424            /// Write the header for a freshly initialized account.
425            #[inline(always)]
426            pub fn write_init_header(data: &mut [u8]) -> Result<(), $crate::hopper_runtime::error::ProgramError> {
427                $crate::hopper_core::account::write_header(
428                    data,
429                    Self::DISC,
430                    Self::VERSION,
431                    &Self::LAYOUT_ID,
432                )
433            }
434
435            // -- BUMP_OFFSET PDA Optimization ------
436            //
437            // Scans fields for a `bump` field. If found, generates
438            // BUMP_OFFSET const and verify_pda_cached() convenience method.
439            // Saves ~344 CU per PDA check vs find_program_address.
440
441            /// Byte offset of the bump field (if present). Used by BUMP_OFFSET PDA optimization.
442            /// If no bump field exists, this is set to usize::MAX as a sentinel.
443            pub const BUMP_OFFSET: usize = {
444                let mut offset = $crate::hopper_core::account::HEADER_LEN;
445                let mut found = usize::MAX;
446                $(
447                    if $crate::hopper_core::__str_eq(stringify!($field), "bump") {
448                        found = offset;
449                    }
450                    offset += $fsize;
451                )+
452                let _ = offset;
453                found
454            };
455
456            /// Returns `true` if this layout has a bump field for PDA optimization.
457            #[inline(always)]
458            pub const fn has_bump_offset() -> bool {
459                Self::BUMP_OFFSET != usize::MAX
460            }
461
462            /// Verify a PDA using the bump stored in account data (~200 CU).
463            ///
464            /// Reads the bump from `BUMP_OFFSET`, appends it to seeds, then
465            /// uses SHA-256 verify-only. Saves ~1300 CU vs `find_program_address`.
466            ///
467            /// Only available on layouts with a `bump` field. Panics at compile
468            /// time otherwise (asserts `BUMP_OFFSET != usize::MAX`).
469            #[inline]
470            pub fn verify_pda_cached(
471                account: &$crate::hopper_runtime::AccountView,
472                seeds: &[&[u8]],
473                program_id: &$crate::hopper_runtime::Address,
474            ) -> Result<(), $crate::hopper_runtime::error::ProgramError> {
475                // BUMP_OFFSET is a const, so this comparison is optimized away.
476                if Self::BUMP_OFFSET == usize::MAX {
477                    return Err($crate::hopper_runtime::error::ProgramError::InvalidArgument);
478                }
479                $crate::hopper_runtime::pda::verify_pda_from_stored_bump(
480                    account, seeds, Self::BUMP_OFFSET, program_id,
481                )
482            }
483
484            // -- Tier 5: Unverified Overlay ------
485            //
486            // Best-effort loading for indexers and off-chain tooling.
487            // Attempts header validation but returns the overlay even on
488            // failure, with a bool indicating whether validation passed.
489
490            /// Tier 5: Unverified overlay for indexers/tooling.
491            ///
492            /// Attempts to validate the header but returns the overlay
493            /// regardless. The returned bool is `true` if validation passed.
494            ///
495            /// This is safe to call on any data -- it never panics.
496            #[inline]
497            pub fn load_unverified(data: &[u8]) -> Option<(&Self, bool)> {
498                if data.len() < Self::LEN {
499                    return None;
500                }
501                let validated = $crate::hopper_core::account::check_header(
502                    data,
503                    Self::DISC,
504                    Self::VERSION,
505                    &Self::LAYOUT_ID,
506                )
507                .is_ok();
508                // SAFETY: Size checked above. T: Pod, alignment-1.
509                let overlay = unsafe { &*(data.as_ptr() as *const Self) };
510                Some((overlay, validated))
511            }
512
513            // -- Multi-Owner Foreign Load ------
514            //
515            // Load foreign account that could be owned by any of several
516            // programs (e.g., Token vs Token-2022).
517
518            /// Tier 2m: Foreign load with multiple possible owners.
519            ///
520            /// Returns `(VerifiedAccount, owner_index)` where `owner_index`
521            /// indicates which owner matched.
522            #[inline]
523            pub fn load_foreign_multi<'a>(
524                account: &'a $crate::hopper_runtime::AccountView,
525                owners: &[&$crate::hopper_runtime::Address],
526            ) -> Result<
527                ($crate::hopper_core::account::VerifiedAccount<'a, Self>, usize),
528                $crate::hopper_runtime::error::ProgramError,
529            > {
530                let owner_idx = $crate::hopper_core::check::check_owner_multi(account, owners)?;
531                let data = account.try_borrow()?;
532                let layout_id = $crate::hopper_core::account::read_layout_id(&*data)?;
533                if layout_id != Self::LAYOUT_ID {
534                    return Err($crate::hopper_runtime::error::ProgramError::InvalidAccountData);
535                }
536                $crate::hopper_core::check::check_size(&*data, Self::LEN)?;
537                let verified = $crate::hopper_core::account::VerifiedAccount::from_ref(data)?;
538                Ok((verified, owner_idx))
539            }
540        }
541
542        // Implement HopperLayout for modifier-style wrappers.
543        impl $crate::hopper_core::check::modifier::HopperLayout for $name {
544            const DISC: u8 = $disc;
545            const VERSION: u8 = $ver;
546            const LAYOUT_ID: [u8; 8] = $name::LAYOUT_ID;
547            const LEN_WITH_HEADER: usize = $name::LEN;
548        }
549    };
550}
551
552// ═════════════════════════════════════════════════════════════════════
553//  Section: Validation (check / error / require)
554// ═════════════════════════════════════════════════════════════════════
555
556/// Composable account constraint checking.
557///
558/// ```ignore
559/// hopper_check!(vault,
560///     owner = program_id,
561///     writable,
562///     signer,
563///     disc = Vault::DISC,
564///     size >= Vault::LEN,
565/// );
566/// ```
567#[macro_export]
568macro_rules! hopper_check {
569    ($account:expr, $( $constraint:tt )+) => {{
570        $crate::_hopper_check_inner!($account, $( $constraint )+)
571    }};
572}
573
574// Internal constraint dispatcher
575#[doc(hidden)]
576#[macro_export]
577macro_rules! _hopper_check_inner {
578    // owner = $id
579    ($account:expr, owner = $id:expr $(, $($rest:tt)+ )?) => {{
580        $crate::hopper_core::check::check_owner($account, $id)?;
581        $( $crate::_hopper_check_inner!($account, $($rest)+); )?
582    }};
583    // writable
584    ($account:expr, writable $(, $($rest:tt)+ )?) => {{
585        $crate::hopper_core::check::check_writable($account)?;
586        $( $crate::_hopper_check_inner!($account, $($rest)+); )?
587    }};
588    // signer
589    ($account:expr, signer $(, $($rest:tt)+ )?) => {{
590        $crate::hopper_core::check::check_signer($account)?;
591        $( $crate::_hopper_check_inner!($account, $($rest)+); )?
592    }};
593    // disc = $d
594    ($account:expr, disc = $d:expr $(, $($rest:tt)+ )?) => {{
595        let data = $account.try_borrow()?;
596        $crate::hopper_core::check::check_discriminator(&*data, $d)?;
597        $( $crate::_hopper_check_inner!($account, $($rest)+); )?
598    }};
599    // size >= $n
600    ($account:expr, size >= $n:expr $(, $($rest:tt)+ )?) => {{
601        let data = $account.try_borrow()?;
602        $crate::hopper_core::check::check_size(&*data, $n)?;
603        $( $crate::_hopper_check_inner!($account, $($rest)+); )?
604    }};
605    // Base case
606    ($account:expr,) => {};
607}
608
609/// Generate sequential error codes.
610///
611/// ```ignore
612/// hopper_error! {
613///     base = 6000;
614///     Undercollateralized,  // 6000
615///     Expired,              // 6001
616///     InvalidOracle,        // 6002
617/// }
618/// ```
619#[macro_export]
620macro_rules! hopper_error {
621    (
622        base = $base:literal;
623        $( $name:ident ),+ $(,)?
624    ) => {
625        $crate::_hopper_error_inner!($base; $( $name ),+);
626    };
627}
628
629#[doc(hidden)]
630#[macro_export]
631macro_rules! _hopper_error_inner {
632    // Base case: single ident
633    ($code:expr; $name:ident) => {
634        pub struct $name;
635        impl $name {
636            pub const CODE: u32 = $code;
637        }
638        impl From<$name> for $crate::hopper_runtime::error::ProgramError {
639            fn from(_: $name) -> $crate::hopper_runtime::error::ProgramError {
640                $crate::hopper_runtime::error::ProgramError::Custom($code)
641            }
642        }
643    };
644    // Recursive case: first ident + rest
645    ($code:expr; $name:ident, $($rest:ident),+) => {
646        $crate::_hopper_error_inner!($code; $name);
647        $crate::_hopper_error_inner!($code + 1; $($rest),+);
648    };
649}
650
651/// Require a condition, returning a custom error if false.
652///
653/// ```ignore
654/// hopper_require!(amount > 0, ZeroAmount)?;
655/// ```
656#[macro_export]
657macro_rules! hopper_require {
658    ($cond:expr, $err:expr) => {
659        if !$cond {
660            return Err($err.into());
661        }
662    };
663}
664
665// ═════════════════════════════════════════════════════════════════════
666//  Section: Lifecycle (init / close)
667// ═════════════════════════════════════════════════════════════════════
668
669/// Initialize an account: allocate, assign, zero-init, write header.
670///
671/// ```ignore
672/// hopper_init!(payer, account, system_program, program_id, Vault)?;
673/// hopper_init!(payer, account, system_program, program_id, Vault, Vault::ALLOC_SPACE)?;
674/// ```
675#[macro_export]
676macro_rules! hopper_init {
677    ($payer:expr, $account:expr, $system:expr, $program_id:expr, $layout:ty) => {{
678        $crate::hopper_init!(
679            $payer,
680            $account,
681            $system,
682            $program_id,
683            $layout,
684            <$layout>::LEN
685        )
686    }};
687    ($payer:expr, $account:expr, $system:expr, $program_id:expr, $layout:ty, $space:expr) => {{
688        let payer = $payer;
689        let account = $account;
690        let program_id = $program_id;
691
692        let space = ($space) as usize;
693        if space < <$layout>::LEN {
694            Err($crate::hopper_runtime::ProgramError::InvalidAccountData)?;
695        }
696
697        let lamports = $crate::hopper_core::check::rent_exempt_min(space);
698        let space = space as u64;
699
700        if account.data_len() != 0 {
701            Err($crate::hopper_runtime::ProgramError::AccountAlreadyInitialized)?;
702        }
703
704        let current_lamports = account.lamports();
705        if current_lamports == 0 {
706            $crate::hopper_system::CreateAccount {
707                from: payer,
708                to: account,
709                lamports,
710                space,
711                owner: program_id,
712            }
713            .invoke()?;
714        } else {
715            if current_lamports < lamports {
716                $crate::hopper_system::Transfer {
717                    from: payer,
718                    to: account,
719                    lamports: lamports - current_lamports,
720                }
721                .invoke()?;
722            }
723            $crate::hopper_system::Allocate { account, space }.invoke()?;
724            $crate::hopper_system::Assign {
725                account,
726                owner: program_id,
727            }
728            .invoke()?;
729        }
730
731        let mut data = account.try_borrow_mut()?;
732        $crate::hopper_core::account::zero_init(&mut *data);
733        <$layout>::write_init_header(&mut *data)
734    }};
735}
736
737/// Safely close an account with sentinel protection.
738///
739/// ```ignore
740/// hopper_close!(account, destination)?;
741/// ```
742#[macro_export]
743macro_rules! hopper_close {
744    ($account:expr, $destination:expr) => {
745        $crate::hopper_core::account::safe_close_with_sentinel($account, $destination)
746    };
747}
748
749// ═════════════════════════════════════════════════════════════════════
750//  Section: Dispatch (discriminator registry)
751// ═════════════════════════════════════════════════════════════════════
752
753/// Discriminator registry -- compile-time uniqueness enforcement.
754///
755/// Lists all account types for a program and asserts that no two share
756/// a discriminator. This prevents silent bugs where `Vault::load()` could
757/// accidentally succeed on a `Pool` account.
758///
759/// ```ignore
760/// hopper_register_discs! {
761///     Vault,
762///     Pool,
763///     Position,
764/// }
765/// ```
766///
767/// Fails at compile time if any two types share the same DISC value.
768#[macro_export]
769macro_rules! hopper_register_discs {
770    ( $( $layout:ty ),+ $(,)? ) => {
771        const _: () = {
772            let discs: &[u8] = &[ $( <$layout>::DISC, )+ ];
773            let names: &[&str] = &[ $( stringify!($layout), )+ ];
774            let n = discs.len();
775            let mut i = 0;
776            while i < n {
777                let mut j = i + 1;
778                while j < n {
779                    assert!(
780                        discs[i] != discs[j],
781                        // Can't format at const time, but this gives a clear enough message
782                        "Duplicate discriminator detected in hopper_register_discs!"
783                    );
784                    j += 1;
785                }
786                i += 1;
787            }
788            let _ = names; // consumed for error messages in non-const contexts
789        };
790    };
791}
792
793// ═════════════════════════════════════════════════════════════════════
794//  Section: PDA
795// ═════════════════════════════════════════════════════════════════════
796
797/// PDA verification with BUMP_OFFSET optimization.
798///
799/// If the layout has a bump field, reads bump from account data and uses
800/// `create_program_address` (~200 CU). Otherwise falls back to
801/// `find_program_address` (~544 CU).
802///
803/// ```ignore
804/// hopper_verify_pda!(vault_account, &[b"vault", authority.as_ref()], program_id, Vault)?;
805/// ```
806#[macro_export]
807macro_rules! hopper_verify_pda {
808    ($account:expr, $seeds:expr, $program_id:expr, $layout:ty) => {{
809        if <$layout>::has_bump_offset() {
810            $crate::hopper_core::check::verify_pda_cached(
811                $account,
812                $seeds,
813                <$layout>::BUMP_OFFSET,
814                $program_id,
815            )
816        } else {
817            // Fallback: no bump field, use regular verify
818            match $crate::hopper_core::check::find_and_verify_pda($account, $seeds, $program_id) {
819                Ok(_bump) => Ok(()),
820                Err(e) => Err(e),
821            }
822        }
823    }};
824}
825
826// ═════════════════════════════════════════════════════════════════════
827//  Section: Invariants
828// ═════════════════════════════════════════════════════════════════════
829
830/// Invariant checking macro.
831///
832/// Defines a set of invariants for an instruction that run after mutation.
833/// Each invariant is a closure over account data that returns `ProgramResult`.
834///
835/// ```ignore
836/// hopper_invariant! {
837///     "balance_conserved" => |vault: &Vault| {
838///         let bal = vault.balance.get();
839///         hopper_require!(bal <= MAX_SUPPLY, BalanceOverflow);
840///         Ok(())
841///     },
842///     "authority_unchanged" => |vault: &Vault, old: &Vault| {
843///         hopper_require!(vault.authority == old.authority, AuthorityChanged);
844///         Ok(())
845///     },
846/// }
847/// ```
848///
849/// Generates an inline invariant runner that returns the first failure.
850#[macro_export]
851macro_rules! hopper_invariant {
852    ( $( $label:literal => $check:expr ),+ $(,)? ) => {{
853        let mut _result: $crate::hopper_runtime::ProgramResult = Ok(());
854        $(
855            if _result.is_ok() {
856                _result = $check;
857            }
858        )+
859        _result
860    }};
861}
862
863// ═════════════════════════════════════════════════════════════════════
864//  Section: Manifest (schema export)
865// ═════════════════════════════════════════════════════════════════════
866
867/// Generate a layout manifest for schema tooling.
868///
869/// Produces a `const LayoutManifest` for a layout type, with field
870/// descriptors suitable for off-chain tooling, indexers, and migration
871/// compatibility checks.
872///
873/// ```ignore
874/// hopper_manifest! {
875///     VAULT_MANIFEST = Vault {
876///         authority: [u8; 32]  = 32,
877///         mint:      [u8; 32]  = 32,
878///         balance:   WireU64   = 8,
879///         bump:      u8        = 1,
880///     }
881/// }
882/// ```
883///
884/// Generates: `pub const VAULT_MANIFEST: hopper_schema::LayoutManifest`
885#[macro_export]
886macro_rules! hopper_manifest {
887    (
888        $const_name:ident = $name:ident {
889            $( $field:ident : $fty:ty = $fsize:literal ),+ $(,)?
890        }
891    ) => {
892        pub const $const_name: $crate::hopper_schema::LayoutManifest = {
893            const FIELD_COUNT: usize = 0 $( + { let _ = stringify!($field); 1 } )+;
894            const SIZES: [u16; FIELD_COUNT] = [ $( $fsize ),+ ];
895            const NAMES: [&str; FIELD_COUNT] = [ $( stringify!($field) ),+ ];
896            const TYPES: [&str; FIELD_COUNT] = [ $( stringify!($fty) ),+ ];
897            const FIELDS: [$crate::hopper_schema::FieldDescriptor; FIELD_COUNT] = {
898                let h = $crate::hopper_core::account::HEADER_LEN as u16;
899                let mut result = [$crate::hopper_schema::FieldDescriptor {
900                    name: "", canonical_type: "", size: 0, offset: 0,
901                    intent: $crate::hopper_schema::FieldIntent::Custom,
902                }; FIELD_COUNT];
903                let mut offset = h;
904                let mut i = 0;
905                while i < FIELD_COUNT {
906                    result[i] = $crate::hopper_schema::FieldDescriptor {
907                        name: NAMES[i],
908                        canonical_type: TYPES[i],
909                        size: SIZES[i],
910                        offset,
911                        intent: $crate::hopper_schema::FieldIntent::Custom,
912                    };
913                    offset += SIZES[i];
914                    i += 1;
915                }
916                result
917            };
918            $crate::hopper_schema::LayoutManifest {
919                name: stringify!($name),
920                version: <$name>::VERSION,
921                disc: <$name>::DISC,
922                layout_id: <$name>::LAYOUT_ID,
923                total_size: <$name>::LEN,
924                field_count: FIELD_COUNT,
925                fields: &FIELDS,
926            }
927        };
928    };
929}
930
931// ═════════════════════════════════════════════════════════════════════
932//  Section: Segmented accounts
933// ═════════════════════════════════════════════════════════════════════
934
935/// Declare a segmented account with typed segments.
936///
937/// Generates:
938/// - Segment ID constants (FNV-1a)
939/// - A `register_segments` function that initializes the segment registry
940/// - Per-segment accessor methods on a generated context struct
941///
942/// ```ignore
943/// hopper_segment! {
944///     pub struct Treasury, disc = 3 {
945///         core:        TreasuryCore        = 128,
946///         permissions: PermissionsTable     = 256,
947///         history:     HistoryLog           = 512,
948///     }
949/// }
950///
951/// // Initialize:
952/// Treasury::init_segments(data)?;
953///
954/// // Read:
955/// let core: &TreasuryCore = Treasury::load_segment::<TreasuryCore>(data, Treasury::CORE_ID)?;
956/// ```
957#[macro_export]
958macro_rules! hopper_segment {
959    (
960        $(#[$attr:meta])*
961        pub struct $name:ident, disc = $disc:literal
962        {
963            $( $seg:ident : $sty:ty = $ssize:literal ),+ $(,)?
964        }
965    ) => {
966        $(#[$attr])*
967        pub struct $name;
968
969        impl $name {
970            pub const DISC: u8 = $disc;
971
972            // Generate segment ID constants
973            $crate::_hopper_segment_ids!($( $seg ),+);
974
975            // Segment count
976            pub const SEGMENT_COUNT: usize = $crate::_hopper_segment_count!($( $seg ),+);
977
978            /// Total account size: header + registry header + entries + segment data.
979            pub const TOTAL_SIZE: usize = {
980                let registry_size = $crate::hopper_core::account::registry::REGISTRY_HEADER_SIZE
981                    + (Self::SEGMENT_COUNT * $crate::hopper_core::account::registry::SEGMENT_ENTRY_SIZE);
982                $crate::hopper_core::account::HEADER_LEN
983                    + registry_size
984                    $( + $ssize )+
985            };
986
987            /// Initialize the segment registry with all declared segments.
988            #[inline]
989            pub fn init_segments(data: &mut [u8]) -> Result<(), $crate::hopper_runtime::error::ProgramError> {
990                let specs: &[($crate::hopper_core::account::registry::SegmentId, u32, u8)] = &[
991                    $(
992                        (
993                            $crate::hopper_core::account::registry::segment_id(stringify!($seg)),
994                            $ssize as u32,
995                            1u8,
996                        ),
997                    )+
998                ];
999                $crate::hopper_core::account::SegmentRegistryMut::init(data, specs)
1000            }
1001
1002            /// Load a typed overlay from a named segment (immutable).
1003            #[inline]
1004            pub fn load_segment<T: $crate::hopper_core::account::Pod + $crate::hopper_core::account::FixedLayout>(
1005                data: &[u8],
1006                seg_id: &$crate::hopper_core::account::registry::SegmentId,
1007            ) -> Result<&T, $crate::hopper_runtime::error::ProgramError> {
1008                let registry = $crate::hopper_core::account::SegmentRegistry::from_account(data)?;
1009                registry.segment_overlay::<T>(seg_id)
1010            }
1011
1012            /// Load a typed overlay from a named segment (mutable).
1013            #[inline]
1014            pub fn load_segment_mut<T: $crate::hopper_core::account::Pod + $crate::hopper_core::account::FixedLayout>(
1015                data: &mut [u8],
1016                seg_id: &$crate::hopper_core::account::registry::SegmentId,
1017            ) -> Result<&mut T, $crate::hopper_runtime::error::ProgramError> {
1018                let mut registry = $crate::hopper_core::account::SegmentRegistryMut::from_account_mut(data)?;
1019                registry.segment_overlay_mut::<T>(seg_id)
1020            }
1021        }
1022    };
1023}
1024
1025/// Generate uppercase segment ID constants from field names.
1026#[doc(hidden)]
1027#[macro_export]
1028macro_rules! _hopper_segment_ids {
1029    ( $( $seg:ident ),+ ) => {
1030        $(
1031            // Use paste-style approach: just use the name directly as a const
1032            // The user references it as TypeName::SEGNAME_ID
1033            $crate::_hopper_segment_id_const!($seg);
1034        )+
1035    };
1036}
1037
1038/// Generate a single segment ID constant.
1039///
1040/// Produces `pub const {NAME}_ID: SegmentId = segment_id("name");`
1041/// Due to macro_rules limitations we use the exact field name in
1042/// uppercase manually. This generates as-is with _ID suffix.
1043#[doc(hidden)]
1044#[macro_export]
1045macro_rules! _hopper_segment_id_const {
1046    ($seg:ident) => {
1047        #[doc = concat!("Segment ID for `", stringify!($seg), "`.")]
1048        #[allow(non_upper_case_globals)]
1049        pub const $seg: $crate::hopper_core::account::registry::SegmentId =
1050            $crate::hopper_core::account::registry::segment_id(stringify!($seg));
1051    };
1052}
1053
1054/// Count segments.
1055#[doc(hidden)]
1056#[macro_export]
1057macro_rules! _hopper_segment_count {
1058    ( $( $seg:ident ),+ ) => {
1059        {
1060            let mut _n = 0usize;
1061            $( let _ = stringify!($seg); _n += 1; )+
1062            _n
1063        }
1064    };
1065}
1066
1067// ═════════════════════════════════════════════════════════════════════
1068//  Section: Validation pipeline builder
1069// ═════════════════════════════════════════════════════════════════════
1070
1071/// Build a validation pipeline declaratively.
1072///
1073/// Each rule is a combinator that returns `impl Fn(&ValidationContext) -> ProgramResult`.
1074/// The macro creates a context, enforces unique writable accounts by default,
1075/// and then invokes each rule in order (fail-fast).
1076#[macro_export]
1077macro_rules! hopper_validate {
1078    (
1079        accounts = $accounts:expr,
1080        program_id = $program_id:expr,
1081        data = $data:expr,
1082        rules {
1083            $( $rule:expr ),+ $(,)?
1084        }
1085    ) => {{
1086        let _vctx = $crate::hopper_core::check::graph::ValidationContext::new(
1087            $program_id,
1088            $accounts,
1089            $data,
1090        );
1091        $crate::hopper_core::check::graph::require_unique_writable_accounts()(&_vctx)?;
1092        $( ($rule)(&_vctx)?; )+
1093        Ok::<(), $crate::hopper_runtime::error::ProgramError>(())
1094    }};
1095}
1096
1097// ═════════════════════════════════════════════════════════════════════
1098//  Section: Virtual state (multi-account mapping)
1099// ═════════════════════════════════════════════════════════════════════
1100
1101/// Declare a multi-account virtual state mapping.
1102///
1103/// ```ignore
1104/// let market = hopper_virtual! {
1105///     slots = 3,
1106///     map {
1107///         0 => account_index: 1, owned, writable,
1108///         1 => account_index: 2, owned,
1109///         2 => account_index: 3,
1110///     }
1111/// };
1112///
1113/// market.validate(accounts, program_id)?;
1114/// let core: &MarketCore = market.overlay::<MarketCore>(accounts, 0)?;
1115/// ```
1116#[macro_export]
1117macro_rules! hopper_virtual {
1118    (
1119        slots = $n:literal,
1120        map {
1121            $( $slot:literal => account_index: $idx:literal
1122                $(, owned $( = $owner:expr )? )?
1123                $(, writable )?
1124            ),+ $(,)?
1125        }
1126    ) => {{
1127        let mut vs = $crate::hopper_core::virtual_state::VirtualState::<$n>::new();
1128        $(
1129            vs = $crate::_hopper_virtual_slot!(vs, $slot, $idx
1130                $(, owned $( = $owner )? )?
1131                $(, writable )?
1132            );
1133        )+
1134        vs
1135    }};
1136}
1137
1138/// Apply a single virtual slot mapping.
1139#[doc(hidden)]
1140#[macro_export]
1141macro_rules! _hopper_virtual_slot {
1142    // owned + writable
1143    ($vs:expr, $slot:literal, $idx:literal, owned, writable) => {
1144        $vs.map_mut($slot, $idx)
1145    };
1146    // owned only
1147    ($vs:expr, $slot:literal, $idx:literal, owned) => {
1148        $vs.map($slot, $idx)
1149    };
1150    // writable only (no owner check)
1151    ($vs:expr, $slot:literal, $idx:literal, writable) => {
1152        $vs.set_slot(
1153            $slot,
1154            $crate::hopper_core::virtual_state::VirtualSlot {
1155                account_index: $idx,
1156                require_owned: false,
1157                require_writable: true,
1158            },
1159        )
1160    };
1161    // bare (no constraints)
1162    ($vs:expr, $slot:literal, $idx:literal) => {
1163        $vs.map_foreign($slot, $idx)
1164    };
1165}
1166
1167// ═════════════════════════════════════════════════════════════════════
1168//  Section: Compile-time compatibility & ABI assertions
1169// ═════════════════════════════════════════════════════════════════════
1170
1171/// Assert that two layout versions have compatible fingerprints.
1172///
1173/// Fails at compile time if the assertion doesn't hold.
1174/// Use this in tests and CI to catch accidental schema breaks.
1175///
1176/// ```ignore
1177/// // Assert V2 is a strict superset of V1 (append-only):
1178/// hopper_assert_compatible!(VaultV1, VaultV2, append);
1179///
1180/// // Assert two layouts have different fingerprints (version bump required):
1181/// hopper_assert_compatible!(VaultV1, VaultV2, differs);
1182/// ```
1183#[macro_export]
1184macro_rules! hopper_assert_compatible {
1185    // Assert V2 is append-compatible with V1: different fingerprint + larger size + same disc
1186    ($old:ty, $new:ty, append) => {
1187        const _: () = {
1188            assert!(
1189                <$new>::LEN > <$old>::LEN,
1190                "New layout must be larger than old for append compatibility"
1191            );
1192            assert!(
1193                <$new>::DISC == <$old>::DISC,
1194                "Discriminator must remain the same across versions"
1195            );
1196            assert!(
1197                <$new>::VERSION > <$old>::VERSION,
1198                "New version must be strictly greater"
1199            );
1200            // Layout IDs must differ (field set changed)
1201            let old_id = <$old>::LAYOUT_ID;
1202            let new_id = <$new>::LAYOUT_ID;
1203            let mut same = true;
1204            let mut i = 0;
1205            while i < 8 {
1206                if old_id[i] != new_id[i] {
1207                    same = false;
1208                }
1209                i += 1;
1210            }
1211            assert!(!same, "Layout IDs must differ between versions");
1212        };
1213    };
1214    // Assert two layouts have different fingerprints
1215    ($old:ty, $new:ty, differs) => {
1216        const _: () = {
1217            let old_id = <$old>::LAYOUT_ID;
1218            let new_id = <$new>::LAYOUT_ID;
1219            let mut same = true;
1220            let mut i = 0;
1221            while i < 8 {
1222                if old_id[i] != new_id[i] {
1223                    same = false;
1224                }
1225                i += 1;
1226            }
1227            assert!(!same, "Layout IDs must differ between versions");
1228        };
1229    };
1230}
1231
1232/// Assert that a layout's fingerprint matches an expected value.
1233///
1234/// Use this to pin a layout's fingerprint in tests. If someone changes the
1235/// layout fields, this assertion catches the ABI break at compile time.
1236///
1237/// ```ignore
1238/// hopper_assert_fingerprint!(Vault, [0x1a, 0x2b, 0x3c, 0x4d, 0x5e, 0x6f, 0x70, 0x81]);
1239/// ```
1240#[macro_export]
1241macro_rules! hopper_assert_fingerprint {
1242    ($layout:ty, $expected:expr) => {
1243        const _: () = {
1244            let actual = <$layout>::LAYOUT_ID;
1245            let expected: [u8; 8] = $expected;
1246            let mut i = 0;
1247            while i < 8 {
1248                assert!(
1249                    actual[i] == expected[i],
1250                    "Layout fingerprint doesn't match expected value -- ABI may have changed"
1251                );
1252                i += 1;
1253            }
1254        };
1255    };
1256}
1257
1258// Re-export dispatch from core
1259pub use hopper_core;
1260pub use hopper_runtime;
1261pub use hopper_schema;
1262pub use hopper_system;
1263
1264/// Compile-time assertion for safe manual `Pod` implementations.
1265///
1266/// Verifies that a type meets all Pod requirements:
1267/// - `align_of == 1` (required for zero-copy overlay at any offset)
1268/// - `size_of == SIZE` matches declared SIZE
1269///
1270/// Use this when implementing `Pod` manually (outside of `hopper_layout!`).
1271///
1272/// ```ignore
1273/// #[repr(C)]
1274/// #[derive(Clone, Copy)]
1275/// pub struct MyEntry {
1276///     pub key: [u8; 32],
1277///     pub value: WireU64,
1278/// }
1279///
1280/// const_assert_pod!(MyEntry, 40);
1281/// unsafe impl Pod for MyEntry {}
1282/// ```
1283#[macro_export]
1284macro_rules! const_assert_pod {
1285    ($ty:ty, $size:expr) => {
1286        const _: () = assert!(
1287            core::mem::align_of::<$ty>() == 1,
1288            concat!(
1289                "Pod type `",
1290                stringify!($ty),
1291                "` must have alignment 1 for zero-copy safety. ",
1292                "Ensure all fields use alignment-1 wire types ([u8; N], WireU64, etc.)."
1293            )
1294        );
1295        const _: () = assert!(
1296            core::mem::size_of::<$ty>() == $size,
1297            concat!(
1298                "Pod type `",
1299                stringify!($ty),
1300                "` size mismatch: ",
1301                "expected ",
1302                stringify!($size),
1303                " bytes"
1304            )
1305        );
1306    };
1307}
1308
1309// ═════════════════════════════════════════════════════════════════════
1310//  Section: Cross-program interface
1311// ═════════════════════════════════════════════════════════════════════
1312
1313/// Declare a cross-program interface view.
1314///
1315/// Generates a read-only overlay struct for reading accounts owned by
1316/// another program **without any crate dependency**. The interface is
1317/// pinned by `LAYOUT_ID` -- the same deterministic SHA-256 fingerprint
1318/// used by `hopper_layout!`. If the originating program changes its
1319/// layout, the fingerprint will differ and `load_foreign()` will reject
1320/// the account at runtime.
1321///
1322/// The generated struct includes:
1323/// - `#[repr(C)]` zero-copy overlay with alignment-1 guarantee
1324/// - Deterministic `LAYOUT_ID` matching the originating layout
1325/// - `load_foreign(account, expected_owner)` for Tier-2 cross-program reads
1326/// - `load_foreign_multi(account, owners)` for multi-owner scenarios
1327/// - `load_with_profile(account, TrustProfile)` for configurable trust
1328/// - Compile-time size and alignment assertions
1329///
1330/// # Example
1331///
1332/// Program A defines a Vault:
1333/// ```ignore
1334/// hopper_layout! {
1335///     pub struct Vault, disc = 1, version = 1 {
1336///         authority: TypedAddress<Authority> = 32,
1337///         balance:   WireU64                = 8,
1338///         bump:      u8                     = 1,
1339///     }
1340/// }
1341/// ```
1342///
1343/// Program B reads it **without importing Program A**:
1344/// ```ignore
1345/// hopper_interface! {
1346///     /// Read-only view of Program A's Vault.
1347///     pub struct VaultView, disc = 1, version = 1 {
1348///         authority: TypedAddress<Authority> = 32,
1349///         balance:   WireU64                = 8,
1350///         bump:      u8                     = 1,
1351///     }
1352/// }
1353///
1354/// let verified = VaultView::load_foreign(vault_account, &PROGRAM_A_ID)?;
1355/// let balance = verified.get().balance.get();
1356/// ```
1357///
1358/// If the fields match Program A's Vault exactly, the LAYOUT_IDs will
1359/// be identical and `load_foreign` succeeds. Any structural divergence
1360/// produces a different hash and the load fails.
1361#[macro_export]
1362macro_rules! hopper_interface {
1363    (
1364        $(#[$attr:meta])*
1365        pub struct $name:ident, disc = $disc:literal, version = $ver:literal
1366        {
1367            $(
1368                $(#[$field_attr:meta])*
1369                $field:ident : $fty:ty = $fsize:literal
1370            ),+ $(,)?
1371        }
1372    ) => {
1373        $(#[$attr])*
1374        #[derive(Clone, Copy)]
1375        #[repr(C)]
1376        pub struct $name {
1377            pub header: $crate::hopper_core::account::AccountHeader,
1378            $(
1379                $(#[$field_attr])*
1380                pub $field: $fty,
1381            )+
1382        }
1383
1384        // Compile-time assertions
1385        const _: () = {
1386            let expected = $crate::hopper_core::account::HEADER_LEN $( + $fsize )+;
1387            assert!(
1388                core::mem::size_of::<$name>() == expected,
1389                "Interface size mismatch: struct size != declared field sizes + header"
1390            );
1391            assert!(
1392                core::mem::align_of::<$name>() == 1,
1393                "Interface alignment must be 1 for zero-copy safety"
1394            );
1395        };
1396
1397        // Bytemuck proof (Hopper Safety Audit Must-Fix #5), same
1398        // justification as `hopper_layout!`: `#[repr(C)]` over Hopper
1399        // wire types is bytemuck-safe by construction.
1400        #[cfg(feature = "hopper-native-backend")]
1401        unsafe impl $crate::hopper_runtime::__hopper_native::bytemuck::Zeroable for $name {}
1402        #[cfg(feature = "hopper-native-backend")]
1403        unsafe impl $crate::hopper_runtime::__hopper_native::bytemuck::Pod for $name {}
1404
1405        // SAFETY: #[repr(C)] over alignment-1 fields, all bit patterns valid.
1406        unsafe impl $crate::hopper_core::account::Pod for $name {}
1407
1408        // Audit final-API Step 5 seal (second declarative-macro form).
1409        unsafe impl $crate::hopper_runtime::__sealed::HopperZeroCopySealed for $name {}
1410
1411        impl $crate::hopper_core::account::FixedLayout for $name {
1412            const SIZE: usize = $crate::hopper_core::account::HEADER_LEN $( + $fsize )+;
1413        }
1414
1415        impl $crate::hopper_core::field_map::FieldMap for $name {
1416            const FIELDS: &'static [$crate::hopper_core::field_map::FieldInfo] = {
1417                const FIELD_COUNT: usize = 0 $( + { let _ = stringify!($field); 1 } )+;
1418                const NAMES: [&str; FIELD_COUNT] = [ $( stringify!($field) ),+ ];
1419                const SIZES: [usize; FIELD_COUNT] = [ $( $fsize ),+ ];
1420                const FIELDS: [$crate::hopper_core::field_map::FieldInfo; FIELD_COUNT] = {
1421                    let mut result = [$crate::hopper_core::field_map::FieldInfo::new("", 0, 0); FIELD_COUNT];
1422                    let mut offset = $crate::hopper_core::account::HEADER_LEN;
1423                    let mut index = 0;
1424                    while index < FIELD_COUNT {
1425                        result[index] = $crate::hopper_core::field_map::FieldInfo::new(
1426                            NAMES[index],
1427                            offset,
1428                            SIZES[index],
1429                        );
1430                        offset += SIZES[index];
1431                        index += 1;
1432                    }
1433                    result
1434                };
1435                &FIELDS
1436            };
1437        }
1438
1439        impl $crate::hopper_runtime::LayoutContract for $name {
1440            const DISC: u8 = $disc;
1441            const VERSION: u8 = $ver;
1442            const LAYOUT_ID: [u8; 8] = $name::LAYOUT_ID;
1443            const SIZE: usize = $name::LEN;
1444            const TYPE_OFFSET: usize = 0;
1445        }
1446
1447        impl $crate::hopper_schema::SchemaExport for $name {
1448            fn layout_manifest() -> $crate::hopper_schema::LayoutManifest {
1449                const FIELD_COUNT: usize = 0 $( + { let _ = stringify!($field); 1 } )+;
1450                const SIZES: [u16; FIELD_COUNT] = [ $( $fsize ),+ ];
1451                const NAMES: [&str; FIELD_COUNT] = [ $( stringify!($field) ),+ ];
1452                const TYPES: [&str; FIELD_COUNT] = [ $( stringify!($fty) ),+ ];
1453                const FIELDS: [$crate::hopper_schema::FieldDescriptor; FIELD_COUNT] = {
1454                    let mut result = [$crate::hopper_schema::FieldDescriptor {
1455                        name: "", canonical_type: "", size: 0, offset: 0,
1456                        intent: $crate::hopper_schema::FieldIntent::Custom,
1457                    }; FIELD_COUNT];
1458                    let mut offset = $crate::hopper_core::account::HEADER_LEN as u16;
1459                    let mut index = 0;
1460                    while index < FIELD_COUNT {
1461                        result[index] = $crate::hopper_schema::FieldDescriptor {
1462                            name: NAMES[index],
1463                            canonical_type: TYPES[index],
1464                            size: SIZES[index],
1465                            offset,
1466                            intent: $crate::hopper_schema::FieldIntent::Custom,
1467                        };
1468                        offset += SIZES[index];
1469                        index += 1;
1470                    }
1471                    result
1472                };
1473                $crate::hopper_schema::LayoutManifest {
1474                    name: stringify!($name),
1475                    version: <$name>::VERSION,
1476                    disc: <$name>::DISC,
1477                    layout_id: <$name>::LAYOUT_ID,
1478                    total_size: <$name>::LEN,
1479                    field_count: FIELD_COUNT,
1480                    fields: &FIELDS,
1481                }
1482            }
1483        }
1484
1485        impl $name {
1486            /// Total byte size of this interface view.
1487            pub const LEN: usize = $crate::hopper_core::account::HEADER_LEN $( + $fsize )+;
1488
1489            /// Expected discriminator of the originating layout.
1490            pub const DISC: u8 = $disc;
1491
1492            /// Expected version of the originating layout.
1493            pub const VERSION: u8 = $ver;
1494
1495            /// Deterministic layout fingerprint.
1496            ///
1497            /// Matches the originating layout's `LAYOUT_ID` if the field
1498            /// names, types, sizes, and ordering are identical.
1499            pub const LAYOUT_ID: [u8; 8] = {
1500                const INPUT: &str = concat!(
1501                    "hopper:v1:",
1502                    stringify!($name), ":",
1503                    stringify!($ver), ":",
1504                    $( stringify!($field), ":", stringify!($fty), ":", stringify!($fsize), ",", )+
1505                );
1506                const HASH: [u8; 32] = $crate::hopper_core::__sha256_const(INPUT.as_bytes());
1507                [
1508                    HASH[0], HASH[1], HASH[2], HASH[3],
1509                    HASH[4], HASH[5], HASH[6], HASH[7],
1510                ]
1511            };
1512
1513            /// Read-only overlay (immutable).
1514            #[inline(always)]
1515            pub fn overlay(data: &[u8]) -> Result<&Self, $crate::hopper_runtime::error::ProgramError> {
1516                $crate::hopper_core::account::pod_from_bytes::<Self>(data)
1517            }
1518
1519            /// Tier 2: Cross-program foreign load (read-only).
1520            ///
1521            /// Validates: owner + layout_id + exact size.
1522            /// No discriminator or version check -- the layout_id is the ABI proof.
1523            ///
1524            /// **Deprecated:** Renamed to `load_cross_program()` for clarity.
1525            #[deprecated(since = "0.2.0", note = "renamed to load_cross_program()")]
1526            #[inline]
1527            pub fn load_foreign<'a>(
1528                account: &'a $crate::hopper_runtime::AccountView,
1529                expected_owner: &$crate::hopper_runtime::Address,
1530            ) -> Result<
1531                $crate::hopper_core::account::VerifiedAccount<'a, Self>,
1532                $crate::hopper_runtime::error::ProgramError,
1533            > {
1534                Self::load_cross_program(account, expected_owner)
1535            }
1536
1537            /// Tier 2: Cross-program load (read-only).
1538            ///
1539            /// Validates: owner + layout_id + exact size.
1540            /// The layout_id is the ABI proof, so no discriminator or version check is needed.
1541            #[inline]
1542            pub fn load_cross_program<'a>(
1543                account: &'a $crate::hopper_runtime::AccountView,
1544                expected_owner: &$crate::hopper_runtime::Address,
1545            ) -> Result<
1546                $crate::hopper_core::account::VerifiedAccount<'a, Self>,
1547                $crate::hopper_runtime::error::ProgramError,
1548            > {
1549                $crate::hopper_core::check::check_owner(account, expected_owner)?;
1550                let data = account.try_borrow()?;
1551                let layout_id = $crate::hopper_core::account::read_layout_id(&*data)?;
1552                if layout_id != Self::LAYOUT_ID {
1553                    return Err($crate::hopper_runtime::error::ProgramError::InvalidAccountData);
1554                }
1555                $crate::hopper_core::check::check_size(&*data, Self::LEN)?;
1556                $crate::hopper_core::account::VerifiedAccount::from_ref(data)
1557            }
1558
1559            /// Tier 2m: Foreign load with multiple possible owners.
1560            ///
1561            /// Returns `(VerifiedAccount, owner_index)` where `owner_index`
1562            /// indicates which expected owner matched.
1563            #[inline]
1564            pub fn load_foreign_multi<'a>(
1565                account: &'a $crate::hopper_runtime::AccountView,
1566                owners: &[&$crate::hopper_runtime::Address],
1567            ) -> Result<
1568                ($crate::hopper_core::account::VerifiedAccount<'a, Self>, usize),
1569                $crate::hopper_runtime::error::ProgramError,
1570            > {
1571                let owner_idx = $crate::hopper_core::check::check_owner_multi(account, owners)?;
1572                let data = account.try_borrow()?;
1573                let layout_id = $crate::hopper_core::account::read_layout_id(&*data)?;
1574                if layout_id != Self::LAYOUT_ID {
1575                    return Err($crate::hopper_runtime::error::ProgramError::InvalidAccountData);
1576                }
1577                $crate::hopper_core::check::check_size(&*data, Self::LEN)?;
1578                let verified = $crate::hopper_core::account::VerifiedAccount::from_ref(data)?;
1579                Ok((verified, owner_idx))
1580            }
1581
1582            /// Load with a TrustProfile for configurable cross-program validation.
1583            ///
1584            /// Supports Strict, Compatible, and Observational trust levels.
1585            #[inline]
1586            pub fn load_with_profile<'a>(
1587                account: &'a $crate::hopper_runtime::AccountView,
1588                profile: &$crate::hopper_core::check::trust::TrustProfile<'a>,
1589            ) -> Result<
1590                $crate::hopper_core::account::VerifiedAccount<'a, Self>,
1591                $crate::hopper_runtime::error::ProgramError,
1592            > {
1593                let data = profile.load(account)?;
1594                $crate::hopper_core::account::VerifiedAccount::from_ref(data)
1595            }
1596
1597            /// Tier 5: Unverified overlay for indexers/tooling.
1598            #[inline]
1599            pub fn load_unverified(data: &[u8]) -> Option<(&Self, bool)> {
1600                if data.len() < Self::LEN {
1601                    return None;
1602                }
1603                let validated = $crate::hopper_core::account::check_header(
1604                    data,
1605                    Self::DISC,
1606                    Self::VERSION,
1607                    &Self::LAYOUT_ID,
1608                )
1609                .is_ok();
1610                // SAFETY: Size checked above. T: Pod, alignment-1.
1611                let overlay = unsafe { &*(data.as_ptr() as *const Self) };
1612                Some((overlay, validated))
1613            }
1614        }
1615    };
1616}
1617
1618// ═════════════════════════════════════════════════════════════════════
1619//  Section: Typed account-struct context generation
1620// ═════════════════════════════════════════════════════════════════════
1621
1622/// Generate a typed instruction context struct with validated account parsing.
1623///
1624/// Produces:
1625/// - The account struct itself
1626/// - A `Bumps` struct for PDA bump storage
1627/// - A `HopperAccounts` impl with `try_from_accounts`
1628/// - A static `ContextDescriptor` for schema/explain
1629///
1630/// # Account kinds
1631///
1632/// Each field specifies a `kind` wrapped in parentheses, with optional modifiers:
1633///
1634/// | Kind                 | Description                     | Writable | Signer |
1635/// |----------------------|---------------------------------|----------|--------|
1636/// | `(signer)`           | Verified signer (SignerAccount) | no       | yes    |
1637/// | `(mut signer)`       | Mutable + signer                | yes      | yes    |
1638/// | `(account<T>)`       | Layout-bound HopperAccount      | no       | no     |
1639/// | `(mut account<T>)`   | Mutable layout-bound account    | yes      | no     |
1640/// | `(program)`          | Verified executable (ProgramRef)| no       | no     |
1641/// | `(unchecked)`        | No-validation passthrough       | no       | no     |
1642/// | `(mut unchecked)`    | Mutable unchecked passthrough   | yes      | no     |
1643///
1644/// # Example
1645///
1646/// ```ignore
1647/// hopper_accounts! {
1648///     pub struct Deposit {
1649///         authority: (mut signer),
1650///         vault: (mut account<VaultState>),
1651///         system_program: (program),
1652///     }
1653/// }
1654/// ```
1655///
1656/// Then use with `hopper_entry`:
1657///
1658/// ```ignore
1659/// hopper_entry::<DepositIx, _>(program_id, accounts, data, |ctx, args| {
1660///     let vault = ctx.accounts.vault.write()?;
1661///     // ...
1662///     Ok(())
1663/// })
1664/// ```
1665#[macro_export]
1666macro_rules! hopper_accounts {
1667    // Main entry: parse struct with field list
1668    (
1669        $(#[$attr:meta])*
1670        pub struct $name:ident {
1671            $( $field:ident : ( $($kind:tt)+ ) ),+ $(,)?
1672        }
1673    ) => {
1674        // Wrap each kind in parens so it becomes a single tt group,
1675        // which eliminates the greedy-tt ambiguity in the inner macro.
1676        $crate::_hopper_accounts_struct!($name; $( $field: ($($kind)+) ; )+);
1677    };
1678}
1679
1680/// Internal: parse each field's kind and generate the struct + impls.
1681///
1682/// Each `$kind` is a single parenthesised token tree, e.g. `(mut signer)`.
1683#[doc(hidden)]
1684#[macro_export]
1685macro_rules! _hopper_accounts_struct {
1686    ($name:ident; $( $field:ident : $kind:tt ; )+) => {
1687
1688        // --- Context struct ---
1689        pub struct $name<'a> {
1690            $(
1691                pub $field: $crate::_hopper_field_type!($kind),
1692            )+
1693        }
1694
1695        // --- HopperAccounts impl ---
1696        impl<'a> $crate::hopper_core::accounts::HopperAccounts<'a> for $name<'a> {
1697            type Bumps = ();
1698
1699            const ACCOUNT_COUNT: usize = {
1700                // Count fields at compile time using the array-length trick.
1701                #[allow(unused)]
1702                const N: usize = [$( { let _ = stringify!($field); 0u8 }, )+].len();
1703                N
1704            };
1705
1706            fn try_from_accounts(
1707                program_id: &'a $crate::hopper_runtime::Address,
1708                accounts: &'a [$crate::hopper_runtime::AccountView],
1709                _instruction_data: &'a [u8],
1710            ) -> Result<(Self, Self::Bumps), $crate::hopper_runtime::error::ProgramError> {
1711                let mut _idx: usize = 0;
1712                $(
1713                    if _idx >= accounts.len() {
1714                        return Err($crate::hopper_runtime::error::ProgramError::NotEnoughAccountKeys);
1715                    }
1716                    let $field = $crate::_hopper_field_parse!(
1717                        &accounts[_idx], program_id, $kind
1718                    )?;
1719                    _idx += 1;
1720                )+
1721                Ok((Self { $( $field, )+ }, ()))
1722            }
1723
1724            #[cfg(feature = "explain")]
1725            fn context_schema() -> Option<
1726                &'static $crate::hopper_core::accounts::explain::ContextSchema
1727            > {
1728                static FIELDS: &[$crate::hopper_core::accounts::explain::AccountFieldSchema] = &[
1729                    $(
1730                        $crate::hopper_core::accounts::explain::AccountFieldSchema {
1731                            name: stringify!($field),
1732                            kind: $crate::_hopper_field_kind_name!($kind),
1733                            mutable: $crate::_hopper_field_is_mut!($kind),
1734                            signer: $crate::_hopper_field_is_signer!($kind),
1735                            layout: $crate::_hopper_field_layout_name!($kind),
1736                            policy: None,
1737                            seeds: &[],
1738                            optional: false,
1739                        },
1740                    )+
1741                ];
1742                static SCHEMA: $crate::hopper_core::accounts::explain::ContextSchema =
1743                    $crate::hopper_core::accounts::explain::ContextSchema {
1744                        name: stringify!($name),
1745                        fields: FIELDS,
1746                        policy_names: &[],
1747                        receipts_expected: false,
1748                        mutation_classes: &[],
1749                    };
1750                Some(&SCHEMA)
1751            }
1752        }
1753    };
1754}
1755
1756// --- Field type resolution ---
1757
1758#[doc(hidden)]
1759#[macro_export]
1760macro_rules! _hopper_field_type {
1761    ((mut signer)) => { $crate::hopper_core::accounts::SignerAccount<'a> };
1762    ((signer)) => { $crate::hopper_core::accounts::SignerAccount<'a> };
1763    ((mut account < $layout:ty >)) => { $crate::hopper_core::accounts::HopperAccount<'a, $layout> };
1764    ((account < $layout:ty >)) => { $crate::hopper_core::accounts::HopperAccount<'a, $layout> };
1765    ((program)) => { $crate::hopper_core::accounts::ProgramRef<'a> };
1766    ((unchecked)) => { $crate::hopper_core::accounts::UncheckedAccount<'a> };
1767    ((mut unchecked)) => { $crate::hopper_core::accounts::UncheckedAccount<'a> };
1768}
1769
1770// --- Field parsing at runtime ---
1771
1772#[doc(hidden)]
1773#[macro_export]
1774macro_rules! _hopper_field_parse {
1775    ($account:expr, $program_id:expr, (mut signer)) => {{
1776        $crate::hopper_core::check::check_writable($account)?;
1777        $crate::hopper_core::accounts::SignerAccount::from_account($account)
1778    }};
1779    ($account:expr, $program_id:expr, (signer)) => {
1780        $crate::hopper_core::accounts::SignerAccount::from_account($account)
1781    };
1782    ($account:expr, $program_id:expr, (mut account < $layout:ty >)) => {
1783        $crate::hopper_core::accounts::HopperAccount::<$layout>::from_account_mut(
1784            $account,
1785            $program_id,
1786        )
1787    };
1788    ($account:expr, $program_id:expr, (account < $layout:ty >)) => {
1789        $crate::hopper_core::accounts::HopperAccount::<$layout>::from_account($account, $program_id)
1790    };
1791    ($account:expr, $program_id:expr, (program)) => {
1792        $crate::hopper_core::accounts::ProgramRef::from_account($account)
1793    };
1794    ($account:expr, $program_id:expr, (unchecked)) => {
1795        Ok::<_, $crate::hopper_runtime::error::ProgramError>(
1796            $crate::hopper_core::accounts::UncheckedAccount::new($account),
1797        )
1798    };
1799    ($account:expr, $program_id:expr, (mut unchecked)) => {{
1800        $crate::hopper_core::check::check_writable($account)?;
1801        Ok::<_, $crate::hopper_runtime::error::ProgramError>(
1802            $crate::hopper_core::accounts::UncheckedAccount::new($account),
1803        )
1804    }};
1805}
1806
1807// --- Static metadata helpers ---
1808
1809#[doc(hidden)]
1810#[macro_export]
1811macro_rules! _hopper_field_kind_name {
1812    ((mut signer)) => {
1813        "Signer"
1814    };
1815    ((signer)) => {
1816        "Signer"
1817    };
1818    ((mut account < $layout:ty >)) => {
1819        "HopperAccount"
1820    };
1821    ((account < $layout:ty >)) => {
1822        "HopperAccount"
1823    };
1824    ((program)) => {
1825        "ProgramRef"
1826    };
1827    ((unchecked)) => {
1828        "Unchecked"
1829    };
1830    ((mut unchecked)) => {
1831        "Unchecked"
1832    };
1833}
1834
1835#[doc(hidden)]
1836#[macro_export]
1837macro_rules! _hopper_field_is_mut {
1838    ((mut signer)) => {
1839        true
1840    };
1841    ((signer)) => {
1842        false
1843    };
1844    ((mut account < $layout:ty >)) => {
1845        true
1846    };
1847    ((account < $layout:ty >)) => {
1848        false
1849    };
1850    ((program)) => {
1851        false
1852    };
1853    ((unchecked)) => {
1854        false
1855    };
1856    ((mut unchecked)) => {
1857        true
1858    };
1859}
1860
1861#[doc(hidden)]
1862#[macro_export]
1863macro_rules! _hopper_field_is_signer {
1864    ((mut signer)) => {
1865        true
1866    };
1867    ((signer)) => {
1868        true
1869    };
1870    ((mut account < $layout:ty >)) => {
1871        false
1872    };
1873    ((account < $layout:ty >)) => {
1874        false
1875    };
1876    ((program)) => {
1877        false
1878    };
1879    ((unchecked)) => {
1880        false
1881    };
1882    ((mut unchecked)) => {
1883        false
1884    };
1885}
1886
1887#[doc(hidden)]
1888#[macro_export]
1889macro_rules! _hopper_field_layout_name {
1890    ((mut signer)) => {
1891        None
1892    };
1893    ((signer)) => {
1894        None
1895    };
1896    ((mut account < $layout:ty >)) => {
1897        Some(stringify!($layout))
1898    };
1899    ((account < $layout:ty >)) => {
1900        Some(stringify!($layout))
1901    };
1902    ((program)) => {
1903        None
1904    };
1905    ((unchecked)) => {
1906        None
1907    };
1908    ((mut unchecked)) => {
1909        None
1910    };
1911}