Skip to main content

jiminy_core/account/
overlay.rs

1//! Zero-copy struct overlay for account data.
2//!
3//! Maps a `#[repr(C)]` struct directly onto borrowed account bytes.
4//! No deserialization, no copies. Read fields via offset accessors
5//! that the [`zero_copy_layout!`](crate::zero_copy_layout!) macro generates for you.
6//!
7//! ## Why a declarative macro instead of a proc-macro?
8//!
9//! Jiminy is no-proc-macro by design. `zero_copy_layout!` generates
10//! typed accessors using field-offset tables computed from the declared
11//! layout. The struct itself is `#[repr(C)]` + `Pod` + `FixedLayout`.
12//!
13//! ## Usage
14//!
15//! ```rust,ignore
16//! use jiminy_core::zero_copy_layout;
17//! use jiminy_core::account::{AccountHeader, Pod, FixedLayout, HEADER_LEN};
18//! use pinocchio::Address;
19//!
20//! zero_copy_layout! {
21//!     /// My on-chain vault account.
22//!     pub struct Vault, discriminator = 1, version = 1 {
23//!         header:    AccountHeader = 16,
24//!         authority: Address       = 32,
25//!         mint:      Address       = 32,
26//!         balance:   u64           = 8,
27//!         bump:      u8            = 1,
28//!     }
29//! }
30//!
31//! // Read from borrowed account data:
32//! let vault = Vault::overlay(&data)?;            // &Vault, zero-copy
33//! let vault = Vault::overlay_mut(&mut data)?;    // &mut Vault, zero-copy
34//! let auth: &Address = &vault.authority;
35//!
36//! // Constants generated by the macro:
37//! assert_eq!(Vault::DISC, 1);
38//! assert_eq!(Vault::VERSION, 1);
39//! let _id: [u8; 8] = Vault::LAYOUT_ID;
40//! ```
41//!
42//! ## Generated API
43//!
44//! The macro emits for each struct:
45//!
46//! ### Constants
47//!
48//! `LEN`, `DISC`, `VERSION`, `LAYOUT_ID`
49//!
50//! ### Raw-byte methods (operate on `&[u8]` / `&mut [u8]`)
51//!
52//! | Method | Returns | What it checks |
53//! |--------|---------|----------------|
54//! | `overlay(data)` | `&Self` | size only |
55//! | `overlay_mut(data)` | `&mut Self` | size only |
56//! | `read(data)` | `Self` (copy) | size only (alignment-safe) |
57//! | `load_checked(data)` | `&Self` | disc + version + layout_id + size |
58//! | `load_checked_mut(data)` | `&mut Self` | disc + version + layout_id + size |
59//!
60//! ### Tiered loading (operate on `AccountView`, see `view` module)
61//!
62//! | Tier | Method | Returns | Validates |
63//! |------|--------|---------|-----------|
64//! | 1 | `load(account, program_id)` | `VerifiedAccount` | owner + disc + version + layout_id + exact size |
65//! | 1m | `load_mut(account, program_id)` | `VerifiedAccountMut` | same as Tier 1 |
66//! | 2 | `load_foreign(account, owner)` | `VerifiedAccount` | owner + layout_id + exact size |
67//! | 4 | `unsafe load_unchecked(data)` | `&Self` | none |
68//! | 5 | `load_unverified_overlay(data)` | `(&Self, bool)` | header if present, fallback |
69//!
70//! ### Borrow-splitting
71//!
72//! | Method | Returns |
73//! |--------|---------|
74//! | `split_fields(data)` | `(FieldRef, FieldRef, ...)` |
75//! | `split_fields_mut(data)` | `(FieldMut, FieldMut, ...)` |
76
77/// Generate `const OFFSET_<FIELD>` for each field in a layout.
78///
79/// Uses a TT-muncher to accumulate offsets from field sizes. Called
80/// internally by `zero_copy_layout!`.
81#[doc(hidden)]
82#[macro_export]
83macro_rules! __gen_offsets {
84    // Base case: no fields left.
85    (@acc $offset:expr ;) => {};
86    // Recursive case: emit one const, advance accumulator.
87    (@acc $offset:expr ; $field:ident = $fsize:expr, $( $rest_field:ident = $rest_fsize:expr, )*) => {
88        /// Byte offset of this field within the account layout.
89        #[allow(non_upper_case_globals)]
90        #[doc(hidden)]
91        pub const $field: usize = $offset;
92        $crate::__gen_offsets!(@acc ($offset + $fsize) ; $( $rest_field = $rest_fsize, )*);
93    };
94    // Entry point: start accumulator at 0.
95    ( $( $field:ident = $fsize:expr ),+ $(,)? ) => {
96        $crate::__gen_offsets!(@acc 0usize ; $( $field = $fsize, )+);
97    };
98}
99
100/// Emit a single `pub const <field>: usize` constant.
101#[doc(hidden)]
102#[macro_export]
103macro_rules! __gen_offset_const {
104    ($field:ident, $offset:expr) => {
105        #[doc = concat!("Byte offset of the `", stringify!($field), "` field.")]
106        #[allow(non_upper_case_globals)]
107        pub const $field: usize = $offset;
108    };
109}
110
111/// Map any token to `FieldRef<'_>` in type position. Used by `zero_copy_layout!`
112/// to repeat `FieldRef` once per field in the return-type tuple.
113#[doc(hidden)]
114#[macro_export]
115macro_rules! __field_ref_type {
116    ($ignore:ident) => { $crate::abi::FieldRef<'_> };
117}
118
119/// Map any token to `FieldMut<'_>` in type position. Used by `zero_copy_layout!`
120/// to repeat `FieldMut` once per field in the return-type tuple.
121#[doc(hidden)]
122#[macro_export]
123macro_rules! __field_mut_type {
124    ($ignore:ident) => { $crate::abi::FieldMut<'_> };
125}
126
127/// Map a Rust identifier type to its canonical hash string.
128///
129/// Used internally by [`zero_copy_layout!`] to produce deterministic
130/// layout IDs regardless of type aliases.
131#[doc(hidden)]
132#[macro_export]
133macro_rules! __canonical_type {
134    (u8) => { "u8" };
135    (u16) => { "u16" };
136    (u32) => { "u32" };
137    (u64) => { "u64" };
138    (u128) => { "u128" };
139    (i8) => { "i8" };
140    (i16) => { "i16" };
141    (i32) => { "i32" };
142    (i64) => { "i64" };
143    (i128) => { "i128" };
144    (bool) => { "bool" };
145    (Address) => { "pubkey" };
146    (AccountHeader) => { "header" };
147    // Jiminy-native Le* types hash to the same canonical strings.
148    (LeU16) => { "u16" };
149    (LeU32) => { "u32" };
150    (LeU64) => { "u64" };
151    (LeU128) => { "u128" };
152    (LeI16) => { "i16" };
153    (LeI32) => { "i32" };
154    (LeI64) => { "i64" };
155    (LeI128) => { "i128" };
156    (LeBool) => { "bool" };
157    ($other:ident) => { stringify!($other) };
158}
159
160/// Declare a zero-copy account layout with typed field accessors.
161///
162/// Each field specifies `name: Type = byte_size`. The macro generates a
163/// `#[repr(C)]` struct along with `Pod`, `FixedLayout`, overlay methods,
164/// and a deterministic `LAYOUT_ID` computed via SHA-256.
165///
166/// ```rust,ignore
167/// zero_copy_layout! {
168///     pub struct Pool, discriminator = 3, version = 1 {
169///         header:     AccountHeader = 16,
170///         authority:  Address       = 32,
171///         reserve_a:  u64           = 8,
172///         reserve_b:  u64           = 8,
173///     }
174/// }
175/// ```
176///
177/// ## Layout Inheritance
178///
179/// Use `extends = ParentType` to assert that a new version is a
180/// byte-compatible superset of an older version. The macro verifies
181/// at compile time that the child has the same discriminator and is
182/// at least as large as the parent:
183///
184/// ```rust,ignore
185/// zero_copy_layout! {
186///     pub struct PoolV2, discriminator = 3, version = 2, extends = Pool {
187///         header:     AccountHeader = 16,
188///         authority:  Address       = 32,
189///         reserve_a:  u64           = 8,
190///         reserve_b:  u64           = 8,
191///         fee_bps:    u16           = 2,
192///     }
193/// }
194/// ```
195#[macro_export]
196macro_rules! zero_copy_layout {
197    (
198        $(#[$meta:meta])*
199        $vis:vis struct $name:ident, discriminator = $disc:literal, version = $ver:literal {
200            $( $(#[$fmeta:meta])* $field:ident : $fty:ident = $fsize:expr ),+ $(,)?
201        }
202    ) => {
203        $(#[$meta])*
204        #[repr(C)]
205        #[derive(Clone, Copy)]
206        $vis struct $name {
207            $( $(#[$fmeta])* pub $field: $fty ),+
208        }
209
210        // SAFETY: The type is repr(C), Copy, and all fields implement Pod.
211        // The caller guarantees all bit patterns are valid.
212        unsafe impl $crate::account::Pod for $name {}
213
214        impl $crate::account::FixedLayout for $name {
215            const SIZE: usize = 0 $( + $fsize )+;
216        }
217
218        // Compile-time assertion: size_of must equal the declared LEN.
219        // Catches cases where a field's declared byte size doesn't match
220        // its actual Rust type size.
221        const _: () = assert!(
222            core::mem::size_of::<$name>() == 0 $( + $fsize )+,
223            "size_of does not match declared LEN - check field sizes"
224        );
225
226        // Compile-time assertion: alignment must not exceed 8 bytes.
227        // Solana's loader aligns program input to 8-byte boundaries.
228        // Types requiring >8 alignment (e.g. u128) would cause UB on-chain.
229        // Use Le128 / Le* wrappers for 16-byte scalars.
230        const _: () = assert!(
231            core::mem::align_of::<$name>() <= 8,
232            "layout alignment exceeds 8 bytes - use Le* wrappers for u128 fields"
233        );
234
235        impl $name {
236            /// Total byte size of this account layout.
237            pub const LEN: usize = 0 $( + $fsize )+;
238
239            /// Account discriminator byte.
240            pub const DISC: u8 = $disc;
241
242            /// Account schema version.
243            pub const VERSION: u8 = $ver;
244
245            /// Deterministic ABI fingerprint (first 8 bytes of SHA-256).
246            ///
247            /// Hash input: `"jiminy:v1:<Name>:<version>:<field>:<canonical_type>:<size>,..."`
248            pub const LAYOUT_ID: [u8; 8] = {
249                const INPUT: &str = concat!(
250                    "jiminy:v1:",
251                    stringify!($name), ":",
252                    stringify!($ver), ":",
253                    $( stringify!($field), ":", $crate::__canonical_type!($fty), ":", stringify!($fsize), ",", )+
254                );
255                const HASH: [u8; 32] = $crate::__sha256_const(INPUT.as_bytes());
256                [HASH[0], HASH[1], HASH[2], HASH[3], HASH[4], HASH[5], HASH[6], HASH[7]]
257            };
258
259            /// Overlay an immutable reference onto borrowed account data.
260            ///
261            /// Returns `AccountDataTooSmall` if the slice is shorter than
262            /// the layout size.
263            #[inline(always)]
264            pub fn overlay(data: &[u8]) -> Result<&Self, $crate::pinocchio::error::ProgramError> {
265                $crate::account::pod_from_bytes::<Self>(data)
266            }
267
268            /// Overlay a mutable reference onto borrowed account data.
269            ///
270            /// Returns `AccountDataTooSmall` if the slice is shorter than
271            /// the layout size.
272            #[inline(always)]
273            pub fn overlay_mut(data: &mut [u8]) -> Result<&mut Self, $crate::pinocchio::error::ProgramError> {
274                $crate::account::pod_from_bytes_mut::<Self>(data)
275            }
276
277            /// Read a copy of this struct from a byte slice.
278            ///
279            /// Alignment-safe on all targets (uses `read_unaligned`
280            /// internally). Ideal for native tests.
281            #[inline(always)]
282            pub fn read(data: &[u8]) -> Result<Self, $crate::pinocchio::error::ProgramError> {
283                $crate::account::pod_read::<Self>(data)
284            }
285
286            /// Load with full header validation (disc + version + layout_id),
287            /// then overlay immutably.
288            #[inline(always)]
289            pub fn load_checked(data: &[u8]) -> Result<&Self, $crate::pinocchio::error::ProgramError> {
290                $crate::account::check_header(data, Self::DISC, Self::VERSION, &Self::LAYOUT_ID)?;
291                $crate::account::pod_from_bytes::<Self>(data)
292            }
293
294            /// Load with full header validation (disc + version + layout_id),
295            /// then overlay mutably.
296            #[inline(always)]
297            pub fn load_checked_mut(data: &mut [u8]) -> Result<&mut Self, $crate::pinocchio::error::ProgramError> {
298                $crate::account::check_header(data, Self::DISC, Self::VERSION, &Self::LAYOUT_ID)?;
299                $crate::account::pod_from_bytes_mut::<Self>(data)
300            }
301
302            // ── Tiered loading API ───────────────────────────────────────
303
304            /// **Tier 1: Standard path.** Validate owner + disc + version +
305            /// exact size + layout_id, then borrow.
306            ///
307            /// Returns a `VerifiedAccount` whose `get()` is infallible.
308            /// This is the recommended way to load a Jiminy account.
309            #[inline(always)]
310            pub fn load<'a>(
311                account: &'a $crate::pinocchio::AccountView,
312                program_id: &$crate::pinocchio::Address,
313            ) -> Result<$crate::account::VerifiedAccount<'a, Self>, $crate::pinocchio::error::ProgramError> {
314                let data = $crate::account::view::validate_account(
315                    account, program_id, Self::DISC, Self::VERSION, &Self::LAYOUT_ID, Self::LEN,
316                )?;
317                $crate::account::VerifiedAccount::new(data)
318            }
319
320            /// **Tier 1 (mutable): Standard mutable path.** Same checks as
321            /// `load()` but returns `VerifiedAccountMut` for write access.
322            #[inline(always)]
323            pub fn load_mut<'a>(
324                account: &'a $crate::pinocchio::AccountView,
325                program_id: &$crate::pinocchio::Address,
326            ) -> Result<$crate::account::VerifiedAccountMut<'a, Self>, $crate::pinocchio::error::ProgramError> {
327                let data = $crate::account::view::validate_account_mut(
328                    account, program_id, Self::DISC, Self::VERSION, &Self::LAYOUT_ID, Self::LEN,
329                )?;
330                $crate::account::VerifiedAccountMut::new(data)
331            }
332
333            /// **Tier 2: Cross-program read.** Validate owner + layout_id
334            /// + exact size, then borrow. Use for reading foreign program
335            /// accounts whose discriminator conventions you don't control.
336            ///
337            /// Returns a `VerifiedAccount` whose `get()` is infallible.
338            #[inline(always)]
339            pub fn load_foreign<'a>(
340                account: &'a $crate::pinocchio::AccountView,
341                expected_owner: &$crate::pinocchio::Address,
342            ) -> Result<$crate::account::VerifiedAccount<'a, Self>, $crate::pinocchio::error::ProgramError> {
343                let data = $crate::account::view::validate_foreign(
344                    account, expected_owner, &Self::LAYOUT_ID, Self::LEN,
345                )?;
346                $crate::account::VerifiedAccount::new(data)
347            }
348
349            /// **Tier 4: Unsafe unchecked load.** No validation at all.
350            ///
351            /// # Safety
352            ///
353            /// Caller must ensure the data represents a valid instance of
354            /// this layout. Intended for legacy / non-Jiminy accounts
355            /// where manual validation has already been performed.
356            #[inline(always)]
357            pub unsafe fn load_unchecked(data: &[u8]) -> Result<&Self, $crate::pinocchio::error::ProgramError> {
358                $crate::account::pod_from_bytes::<Self>(data)
359            }
360
361            /// **Tier 5: Unverified overlay.** Try header + layout_id
362            /// validation; if it fails, fall back to a plain overlay.
363            ///
364            /// No ABI guarantees. Returns `(overlay, validated)` where
365            /// `validated` is `true` when the header matched. Useful for
366            /// indexers/tooling. Never use in on-chain program logic.
367            #[inline(always)]
368            pub fn load_unverified_overlay(data: &[u8]) -> Result<(&Self, bool), $crate::pinocchio::error::ProgramError> {
369                $crate::account::view::load_unverified_overlay::<Self>(
370                    data, Self::DISC, Self::VERSION, &Self::LAYOUT_ID,
371                )
372            }
373
374            // ── Const field offsets ──────────────────────────────────────
375
376            $crate::__gen_offsets!( $( $field = $fsize ),+ );
377
378            // ── Borrow-splitting ─────────────────────────────────────────
379
380            /// Split borrowed data into independent per-field `FieldRef` slices.
381            ///
382            /// Each `FieldRef` holds a non-overlapping `&[u8]` subslice,
383            /// enabling typed reads of multiple fields without aliasing issues.
384            #[inline]
385            #[allow(unused_variables)]
386            pub fn split_fields(data: &[u8]) -> Result<( $( $crate::__field_ref_type!($field), )+ ), $crate::pinocchio::error::ProgramError> {
387                if data.len() < Self::LEN {
388                    return Err($crate::pinocchio::error::ProgramError::AccountDataTooSmall);
389                }
390                let mut _pos = 0usize;
391                Ok(( $({
392                    let start = _pos;
393                    _pos += $fsize;
394                    $crate::abi::FieldRef::new(&data[start..start + $fsize])
395                }, )+ ))
396            }
397
398            /// Split mutably borrowed data into independent per-field `FieldMut` slices.
399            ///
400            /// Enables safe simultaneous mutation of multiple fields via
401            /// non-overlapping `&mut [u8]` subslices. The borrow checker sees
402            /// independent mutable references - no `unsafe` needed.
403            ///
404            /// ```rust,ignore
405            /// let (header, balance, authority) = Vault::split_fields_mut(&mut data)?;
406            /// balance.write_u64(1000);
407            /// authority.copy_from(&new_auth);
408            /// ```
409            #[inline]
410            #[allow(unused_variables)]
411            pub fn split_fields_mut(data: &mut [u8]) -> Result<( $( $crate::__field_mut_type!($field), )+ ), $crate::pinocchio::error::ProgramError> {
412                if data.len() < Self::LEN {
413                    return Err($crate::pinocchio::error::ProgramError::AccountDataTooSmall);
414                }
415                let _remaining = &mut data[..Self::LEN];
416                $(
417                    let ($field, _remaining) = _remaining.split_at_mut($fsize);
418                )+
419                Ok(( $( $crate::abi::FieldMut::new($field), )+ ))
420            }
421        }
422    };
423
424    // ── extends arm ──────────────────────────────────────────────────
425    //
426    // Identical to the base arm but adds compile-time assertions that
427    // the child layout is a byte-compatible superset of the parent:
428    //   - same discriminator
429    //   - child LEN >= parent LEN
430    //   - child VERSION > parent VERSION
431    (
432        $(#[$meta:meta])*
433        $vis:vis struct $name:ident, discriminator = $disc:literal, version = $ver:literal, extends = $parent:ty {
434            $( $(#[$fmeta:meta])* $field:ident : $fty:ident = $fsize:expr ),+ $(,)?
435        }
436    ) => {
437        // Delegate to the base arm for all standard codegen.
438        $crate::zero_copy_layout! {
439            $(#[$meta])*
440            $vis struct $name, discriminator = $disc, version = $ver {
441                $( $(#[$fmeta])* $field : $fty = $fsize ),+
442            }
443        }
444
445        // Compile-time V2 ⊃ V1 assertions.
446        const _: () = assert!(
447            <$name>::DISC == <$parent>::DISC,
448            "extends: child and parent must share the same discriminator"
449        );
450        const _: () = assert!(
451            <$name>::LEN >= <$parent>::LEN,
452            "extends: child layout must be at least as large as parent (append-only)"
453        );
454        const _: () = assert!(
455            <$name>::VERSION > <$parent>::VERSION,
456            "extends: child version must be strictly greater than parent version"
457        );
458    };
459}
460
461/// Count the number of token-tree repetitions (segments).
462#[doc(hidden)]
463#[macro_export]
464macro_rules! __count_segments {
465    () => { 0usize };
466    ($head:ident $($tail:ident)*) => { 1usize + $crate::__count_segments!($($tail)*) };
467}
468
469/// Generate `pub const <seg_name>: usize` index constants for each segment.
470#[doc(hidden)]
471#[macro_export]
472macro_rules! __gen_segment_indices {
473    (@acc $offset:expr ;) => {};
474    (@acc $offset:expr ; $seg_name:ident, $( $rest:ident, )*) => {
475        #[doc = concat!("Index of the `", stringify!($seg_name), "` segment in the segment table.")]
476        #[allow(non_upper_case_globals)]
477        pub const $seg_name: usize = $offset;
478        $crate::__gen_segment_indices!(@acc ($offset + 1usize) ; $( $rest, )*);
479    };
480    ($( $seg_name:ident ),+ $(,)?) => {
481        $crate::__gen_segment_indices!(@acc 0usize ; $( $seg_name, )+);
482    };
483}
484
485/// Declare a segmented zero-copy account layout.
486///
487/// Combines a fixed prefix (same as `zero_copy_layout!`) with one or
488/// more variable-length segments. The segment table sits right after
489/// the fixed fields, and the macro generates typed accessors per
490/// segment.
491///
492/// ```rust,ignore
493/// use jiminy_core::segmented_layout;
494/// use jiminy_core::account::{AccountHeader, Pod, FixedLayout};
495/// use pinocchio::Address;
496///
497/// // A simple element type.
498/// #[repr(C)]
499/// #[derive(Clone, Copy, Debug)]
500/// pub struct Order {
501///     pub price: [u8; 8],
502///     pub qty: [u8; 8],
503/// }
504/// unsafe impl Pod for Order {}
505/// impl FixedLayout for Order { const SIZE: usize = 16; }
506///
507/// segmented_layout! {
508///     pub struct OrderBook, discriminator = 5, version = 1 {
509///         header:     AccountHeader = 16,
510///         market:     Address       = 32,
511///     } segments {
512///         bids: Order = 16,
513///         asks: Order = 16,
514///     }
515/// }
516///
517/// // Wire layout (example with 3 bids, 2 asks, Order = 16):
518/// //   Offset 0–15: header, 16–47: market,
519/// //   48–55: bids descriptor, 56–63: asks descriptor,
520/// //   64+: bids data, then asks data.
521///
522/// // Access:
523/// let table = OrderBook::segment_table(&data)?;
524/// let bids = SegmentSlice::<Order>::from_descriptor(&data, &table.descriptor(0)?)?;
525/// let first_bid: Order = bids.read(0)?;
526/// ```
527///
528/// ## Generated API
529///
530/// In addition to the standard `zero_copy_layout!` API on the fixed
531/// prefix struct:
532///
533/// - `SEGMENT_COUNT: usize`: number of declared segments
534/// - `FIXED_LEN: usize`: byte size of the fixed prefix
535/// - `TABLE_OFFSET: usize`: byte offset where the segment table starts
536/// - `DATA_START_OFFSET: usize`: byte offset where segment data starts
537/// - `MIN_ACCOUNT_SIZE: usize`: minimum account size (fixed + table, no data)
538/// - `SEGMENTED_LAYOUT_ID: [u8; 8]`: layout hash including segment entries
539/// - `segment_sizes() -> &[u16]`: expected element sizes per segment
540/// - `segment_table(data) -> SegmentTable`: parse immutable segment table
541/// - `segment_table_mut(data) -> SegmentTableMut`: parse mutable segment table
542/// - `validate_segments(data)`: full segment table validation
543/// - `init_segments(data, counts)`: write initial segment table
544/// - `compute_account_size(counts) -> usize`: total account size for given counts
545#[macro_export]
546macro_rules! segmented_layout {
547    (
548        $(#[$meta:meta])*
549        $vis:vis struct $name:ident, discriminator = $disc:literal, version = $ver:literal {
550            $( $(#[$fmeta:meta])* $field:ident : $fty:ident = $fsize:expr ),+ $(,)?
551        } segments {
552            $( $seg_name:ident : $seg_ty:ident = $seg_elem_size:expr ),+ $(,)?
553        }
554    ) => {
555        // Generate the fixed prefix struct with zero_copy_layout!.
556        $crate::zero_copy_layout! {
557            $(#[$meta])*
558            $vis struct $name, discriminator = $disc, version = $ver {
559                $( $(#[$fmeta])* $field : $fty = $fsize ),+
560            }
561        }
562
563        impl $name {
564            /// Number of dynamic segments in this layout.
565            pub const SEGMENT_COUNT: usize = $crate::__count_segments!($($seg_name)+);
566
567            /// Byte size of the fixed prefix (before the segment table).
568            pub const FIXED_LEN: usize = Self::LEN;
569
570            /// Byte offset where the segment table begins.
571            pub const TABLE_OFFSET: usize = Self::LEN;
572
573            /// Byte offset where segment data starts (after fixed + table).
574            pub const DATA_START_OFFSET: usize =
575                Self::LEN + Self::SEGMENT_COUNT * $crate::account::segment::SEGMENT_DESC_SIZE;
576
577            /// Deterministic ABI fingerprint including segment declarations.
578            ///
579            /// Extends the base hash with `seg:<name>:<type>:<size>` entries.
580            pub const SEGMENTED_LAYOUT_ID: [u8; 8] = {
581                const INPUT: &str = concat!(
582                    "jiminy:v1:",
583                    stringify!($name), ":",
584                    stringify!($ver), ":",
585                    $( stringify!($field), ":", $crate::__canonical_type!($fty), ":", stringify!($fsize), ",", )+
586                    $( "seg:", stringify!($seg_name), ":", stringify!($seg_ty), ":", stringify!($seg_elem_size), ",", )+
587                );
588                const HASH: [u8; 32] = $crate::__sha256_const(INPUT.as_bytes());
589                [HASH[0], HASH[1], HASH[2], HASH[3], HASH[4], HASH[5], HASH[6], HASH[7]]
590            };
591
592            /// Expected element sizes for each segment, in declaration order.
593            #[inline(always)]
594            pub const fn segment_sizes() -> &'static [u16] {
595                // Compile-time assertion: declared sizes must match type sizes.
596                $(
597                    const _: () = assert!(
598                        <$seg_ty as $crate::account::FixedLayout>::SIZE == $seg_elem_size,
599                        "segmented_layout! segment size does not match type FixedLayout::SIZE"
600                    );
601                )+
602                &[ $( <$seg_ty as $crate::account::FixedLayout>::SIZE as u16, )+ ]
603            }
604
605            /// Minimum account size: fixed prefix + segment table (no elements).
606            pub const MIN_ACCOUNT_SIZE: usize = Self::DATA_START_OFFSET;
607
608            /// Read the segment table from account data.
609            #[inline(always)]
610            pub fn segment_table(data: &[u8]) -> Result<$crate::account::segment::SegmentTable<'_>, $crate::pinocchio::error::ProgramError> {
611                if data.len() < Self::DATA_START_OFFSET {
612                    return Err($crate::pinocchio::error::ProgramError::AccountDataTooSmall);
613                }
614                $crate::account::segment::SegmentTable::from_bytes(
615                    &data[Self::TABLE_OFFSET..],
616                    Self::SEGMENT_COUNT,
617                )
618            }
619
620            /// Read the mutable segment table from account data.
621            #[inline(always)]
622            pub fn segment_table_mut(data: &mut [u8]) -> Result<$crate::account::segment::SegmentTableMut<'_>, $crate::pinocchio::error::ProgramError> {
623                if data.len() < Self::DATA_START_OFFSET {
624                    return Err($crate::pinocchio::error::ProgramError::AccountDataTooSmall);
625                }
626                $crate::account::segment::SegmentTableMut::from_bytes(
627                    &mut data[Self::TABLE_OFFSET..],
628                    Self::SEGMENT_COUNT,
629                )
630            }
631
632            /// Validate the segment table against the account data.
633            ///
634            /// Checks element sizes, bounds, ordering, and no overlaps.
635            #[inline]
636            pub fn validate_segments(data: &[u8]) -> Result<(), $crate::pinocchio::error::ProgramError> {
637                let table = Self::segment_table(data)?;
638                table.validate(data.len(), Self::segment_sizes(), Self::DATA_START_OFFSET)
639            }
640
641            /// Initialize segment descriptors in a freshly allocated account.
642            ///
643            /// `counts` gives the initial element count per segment. Offsets
644            /// are computed by laying out segments contiguously based on
645            /// these counts. If you plan to `push` elements later, use
646            /// [`init_segments_with_capacity`](Self::init_segments_with_capacity)
647            /// instead to pre-allocate space.
648            #[inline]
649            pub fn init_segments(
650                data: &mut [u8],
651                counts: &[u16],
652            ) -> Result<(), $crate::pinocchio::error::ProgramError> {
653                if counts.len() != Self::SEGMENT_COUNT {
654                    return Err($crate::pinocchio::error::ProgramError::InvalidArgument);
655                }
656                let sizes = Self::segment_sizes();
657                let specs: [_; Self::SEGMENT_COUNT] = {
658                    let mut arr = [(0u16, 0u16); Self::SEGMENT_COUNT];
659                    let mut i = 0;
660                    while i < Self::SEGMENT_COUNT {
661                        arr[i] = (sizes[i], counts[i]);
662                        i += 1;
663                    }
664                    arr
665                };
666                $crate::account::segment::SegmentTableMut::init(
667                    &mut data[Self::TABLE_OFFSET..],
668                    Self::DATA_START_OFFSET as u32,
669                    &specs,
670                )?;
671                Ok(())
672            }
673
674            /// Initialize segment descriptors with pre-allocated capacity.
675            ///
676            /// `capacities` gives the maximum number of elements each
677            /// segment can hold. Offsets are spaced by capacity so that
678            /// `push` cannot overwrite an adjacent segment. All counts
679            /// start at zero.
680            ///
681            /// Use [`compute_account_size`](Self::compute_account_size)
682            /// with the same `capacities` to determine the account size.
683            ///
684            /// ```rust,ignore
685            /// let size = OrderBook::compute_account_size(&[100, 100])?;
686            /// // ... create account with `size` bytes ...
687            /// OrderBook::init_segments_with_capacity(&mut data, &[100, 100])?;
688            /// // Now you can safely push up to 100 bids and 100 asks.
689            /// ```
690            #[inline]
691            pub fn init_segments_with_capacity(
692                data: &mut [u8],
693                capacities: &[u16],
694            ) -> Result<(), $crate::pinocchio::error::ProgramError> {
695                if capacities.len() != Self::SEGMENT_COUNT {
696                    return Err($crate::pinocchio::error::ProgramError::InvalidArgument);
697                }
698                let sizes = Self::segment_sizes();
699                // Space offsets by capacity, but set counts to 0.
700                let mut offset = Self::DATA_START_OFFSET as u32;
701                let mut table_data = &mut data[Self::TABLE_OFFSET..];
702                for i in 0..Self::SEGMENT_COUNT {
703                    let start = i * $crate::account::segment::SEGMENT_DESC_SIZE;
704                    table_data[start..start + 4].copy_from_slice(&offset.to_le_bytes());
705                    table_data[start + 4..start + 6].copy_from_slice(&0u16.to_le_bytes());
706                    table_data[start + 6..start + 8].copy_from_slice(&sizes[i].to_le_bytes());
707                    let seg_space = (capacities[i] as u32)
708                        .checked_mul(sizes[i] as u32)
709                        .ok_or($crate::pinocchio::error::ProgramError::ArithmeticOverflow)?;
710                    offset = offset
711                        .checked_add(seg_space)
712                        .ok_or($crate::pinocchio::error::ProgramError::ArithmeticOverflow)?;
713                }
714                Ok(())
715            }
716
717            /// Compute the total account size for given element counts.
718            ///
719            /// `counts[i]` is the number of elements in segment `i`.
720            #[inline]
721            pub fn compute_account_size(counts: &[u16]) -> Result<usize, $crate::pinocchio::error::ProgramError> {
722                if counts.len() != Self::SEGMENT_COUNT {
723                    return Err($crate::pinocchio::error::ProgramError::InvalidArgument);
724                }
725                let sizes = Self::segment_sizes();
726                let mut total = Self::DATA_START_OFFSET;
727                let mut i = 0;
728                while i < Self::SEGMENT_COUNT {
729                    let seg_bytes = (counts[i] as usize)
730                        .checked_mul(sizes[i] as usize)
731                        .ok_or($crate::pinocchio::error::ProgramError::ArithmeticOverflow)?;
732                    total = total
733                        .checked_add(seg_bytes)
734                        .ok_or($crate::pinocchio::error::ProgramError::ArithmeticOverflow)?;
735                    i += 1;
736                }
737                Ok(total)
738            }
739
740            // ── Segment index constants ──────────────────────────────────
741
742            $crate::__gen_segment_indices!($($seg_name),+);
743
744            // ── Typed segment accessors ──────────────────────────────────
745
746            /// Get an immutable typed view over a segment by index.
747            ///
748            /// ```rust,ignore
749            /// let bids = OrderBook::segment::<Order>(&data, OrderBook::bids)?;
750            /// let first = bids.read(0)?;
751            /// ```
752            #[inline(always)]
753            pub fn segment<T: $crate::account::Pod + $crate::account::FixedLayout>(
754                data: &[u8],
755                index: usize,
756            ) -> Result<$crate::account::segment::SegmentSlice<'_, T>, $crate::pinocchio::error::ProgramError> {
757                let desc = {
758                    let table = Self::segment_table(data)?;
759                    table.descriptor(index)?
760                };
761                $crate::account::segment::SegmentSlice::from_descriptor(data, &desc)
762            }
763
764            /// Get a mutable typed view over a segment by index.
765            ///
766            /// ```rust,ignore
767            /// let mut bids = OrderBook::segment_mut::<Order>(&mut data, OrderBook::bids)?;
768            /// bids.set(0, &new_order)?;
769            /// ```
770            #[inline(always)]
771            pub fn segment_mut<T: $crate::account::Pod + $crate::account::FixedLayout>(
772                data: &mut [u8],
773                index: usize,
774            ) -> Result<$crate::account::segment::SegmentSliceMut<'_, T>, $crate::pinocchio::error::ProgramError> {
775                let desc = {
776                    let table = Self::segment_table(data)?;
777                    table.descriptor(index)?
778                };
779                $crate::account::segment::SegmentSliceMut::from_descriptor(data, &desc)
780            }
781
782            /// Push an element at the end of a segment.
783            ///
784            /// Updates both the data and the descriptor count automatically.
785            ///
786            /// ```rust,ignore
787            /// OrderBook::push::<Order>(&mut data, OrderBook::bids, &new_order)?;
788            /// ```
789            #[inline(always)]
790            pub fn push<T: $crate::account::Pod + $crate::account::FixedLayout>(
791                data: &mut [u8],
792                index: usize,
793                value: &T,
794            ) -> Result<(), $crate::pinocchio::error::ProgramError> {
795                $crate::account::segment::segment_push::<T>(
796                    data, Self::TABLE_OFFSET, Self::SEGMENT_COUNT, index, value,
797                )
798            }
799
800            /// Remove an element by swapping it with the last. Returns the
801            /// removed element. O(1) but does not preserve order.
802            ///
803            /// ```rust,ignore
804            /// let removed = OrderBook::swap_remove::<Order>(&mut data, OrderBook::bids, 0)?;
805            /// ```
806            #[inline(always)]
807            pub fn swap_remove<T: $crate::account::Pod + $crate::account::FixedLayout>(
808                data: &mut [u8],
809                index: usize,
810                elem_index: u16,
811            ) -> Result<T, $crate::pinocchio::error::ProgramError> {
812                $crate::account::segment::segment_swap_remove::<T>(
813                    data, Self::TABLE_OFFSET, Self::SEGMENT_COUNT, index, elem_index,
814                )
815            }
816        }
817    };
818}