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}