Skip to main content

hopper_core/account/
registry.rs

1//! Segmented Accounts for Hopper.
2//!
3//! This module provides first-class segment-based account architecture:
4//!
5//! - Typed segment registry with compile-time segment IDs and runtime lookup
6//! - Segment introspection: iterate, inspect, and decode segments
7//! - Per-segment access control via freeze/lock flags
8//!
9//! ## Compile-time vs runtime segments
10//!
11//! Hopper distinguishes two segment shapes - both rooted in this module
12//! and [`crate::segment_map`] respectively:
13//!
14//! - **[`crate::segment_map::StaticSegment`]** - *compile-time* metadata
15//!   for fixed-layout structs annotated with `#[hopper::state]`. Each
16//!   field becomes a `StaticSegment { name, offset, size }` const,
17//!   with the offset reported relative to the body (post-header).
18//!   Access is via the `Layout::segment("balance")` static lookup.
19//! - **[`SegmentDescriptor`] (this module)** - *runtime* metadata
20//!   for accounts whose segment table grows on-chain. Each entry
21//!   carries `offset + size + flags + version + count + capacity`,
22//!   stored in the account's segment-registry table. Access is via
23//!   the [`SegmentRegistry`] / [`SegmentRegistryMut`] handles that
24//!   walk the table at runtime.
25//!
26//! For typical Hopper programs you'll use the compile-time path
27//! through `#[hopper::state]` - the runtime registry is reserved
28//! for accounts that genuinely need a dynamic segment count
29//! (extension-heavy patterns, Token-2022-style mints, etc.).
30//!
31//! ## Wire Format
32//!
33//! ```text
34//! [AccountHeader: 16 bytes]
35//! [SegmentRegistry Header: 4 bytes]
36//!   segment_count: u16
37//!   registry_flags: u16
38//! [SegmentEntry 0: 16 bytes]
39//!   segment_id:   [u8; 4]   -- compile-time FNV hash of segment name
40//!   offset:       u32       -- byte offset from account start
41//!   size:         u32       -- total allocated bytes
42//!   flags:        u16       -- locked, frozen, etc.
43//!   version:      u8        -- segment schema version
44//!   _reserved:    u8
45//! [SegmentEntry 1: 16 bytes]
46//! ...
47//! [Segment 0 data...]
48//! [Segment 1 data...]
49//! ```
50//!
51//! ## Usage
52//!
53//! ```ignore
54//! // Define segment IDs at compile time
55//! const CORE_SEG: SegmentId = segment_id("core");
56//! const PERMS_SEG: SegmentId = segment_id("permissions");
57//!
58//! // Read a segment
59//! let registry = SegmentRegistry::from_account(data)?;
60//! let core_data = registry.segment_data(data, CORE_SEG)?;
61//! let core = TreasuryCore::overlay(core_data)?;
62//! ```
63
64use super::segment_role::SegmentRole;
65#[cfg(feature = "collections")]
66use crate::collections::{FixedVec, Journal, RingBuffer, Slab, SlotMap, SortedVec};
67use hopper_runtime::error::ProgramError;
68
69// -- Segment ID --
70
71/// A 4-byte segment identifier, computed from a name at compile time.
72pub type SegmentId = [u8; 4];
73
74/// Compute a segment ID from a name (const FNV-1a hash, truncated to 4 bytes).
75#[inline(always)]
76pub const fn segment_id(name: &str) -> SegmentId {
77    let bytes = name.as_bytes();
78    let mut hash: u32 = 0x811c_9dc5; // FNV offset basis
79    let mut i = 0;
80    while i < bytes.len() {
81        hash ^= bytes[i] as u32;
82        hash = hash.wrapping_mul(0x0100_0193); // FNV prime
83        i += 1;
84    }
85    hash.to_le_bytes()
86}
87
88// -- Segment Entry --
89
90/// Size of one segment entry in bytes.
91pub const SEGMENT_ENTRY_SIZE: usize = 16;
92
93/// Flags for segment entries.
94pub const SEG_FLAG_LOCKED: u16 = 1 << 0; // Cannot be modified
95pub const SEG_FLAG_FROZEN: u16 = 1 << 1; // Temporarily frozen
96pub const SEG_FLAG_DYNAMIC: u16 = 1 << 2; // Supports realloc
97
98/// A segment entry in the registry.
99#[derive(Clone, Copy)]
100#[repr(C)]
101pub struct SegmentEntry {
102    pub id: [u8; 4],
103    offset_bytes: [u8; 4],
104    size_bytes: [u8; 4],
105    flags_bytes: [u8; 2],
106    pub version: u8,
107    pub _reserved: u8,
108}
109
110const _: () = assert!(core::mem::size_of::<SegmentEntry>() == SEGMENT_ENTRY_SIZE);
111const _: () = assert!(core::mem::align_of::<SegmentEntry>() == 1);
112
113impl SegmentEntry {
114    /// Create a new segment entry.
115    #[inline(always)]
116    pub const fn new(id: SegmentId, offset: u32, size: u32, flags: u16, version: u8) -> Self {
117        Self {
118            id,
119            offset_bytes: offset.to_le_bytes(),
120            size_bytes: size.to_le_bytes(),
121            flags_bytes: flags.to_le_bytes(),
122            version,
123            _reserved: 0,
124        }
125    }
126
127    /// Byte offset from account start.
128    #[inline(always)]
129    pub fn offset(&self) -> u32 {
130        u32::from_le_bytes(self.offset_bytes)
131    }
132
133    /// Total allocated size in bytes.
134    #[inline(always)]
135    pub fn size(&self) -> u32 {
136        u32::from_le_bytes(self.size_bytes)
137    }
138
139    /// Segment flags.
140    #[inline(always)]
141    pub fn flags(&self) -> u16 {
142        u16::from_le_bytes(self.flags_bytes)
143    }
144
145    /// Whether the segment is locked (immutable).
146    #[inline(always)]
147    pub fn is_locked(&self) -> bool {
148        self.flags() & SEG_FLAG_LOCKED != 0
149    }
150
151    /// Whether the segment is frozen.
152    #[inline(always)]
153    pub fn is_frozen(&self) -> bool {
154        self.flags() & SEG_FLAG_FROZEN != 0
155    }
156
157    /// The semantic role of this segment (decoded from upper 4 bits of flags).
158    #[inline(always)]
159    pub fn role(&self) -> SegmentRole {
160        SegmentRole::from_flags(self.flags())
161    }
162
163    /// Set the offset.
164    #[inline(always)]
165    pub fn set_offset(&mut self, offset: u32) {
166        self.offset_bytes = offset.to_le_bytes();
167    }
168
169    /// Set the size.
170    #[inline(always)]
171    pub fn set_size(&mut self, size: u32) {
172        self.size_bytes = size.to_le_bytes();
173    }
174
175    /// Set flags.
176    #[inline(always)]
177    pub fn set_flags(&mut self, flags: u16) {
178        self.flags_bytes = flags.to_le_bytes();
179    }
180}
181
182// -- Registry Header --
183
184/// Size of the segment registry header.
185pub const REGISTRY_HEADER_SIZE: usize = 4;
186
187/// Maximum segments in a registry.
188pub const MAX_REGISTRY_SEGMENTS: usize = 16;
189
190// -- Segment Registry (read-only) --
191
192/// Read-only view over a segmented account's registry.
193///
194/// The registry lives right after the 16-byte AccountHeader.
195pub struct SegmentRegistry<'a> {
196    data: &'a [u8],
197    count: usize,
198    entries_offset: usize,
199}
200
201/// Offset where the registry header starts (after AccountHeader).
202pub const REGISTRY_OFFSET: usize = crate::account::HEADER_LEN;
203
204impl<'a> SegmentRegistry<'a> {
205    /// Parse a segment registry from full account data.
206    ///
207    /// Expects account data starting from byte 0 (including AccountHeader).
208    #[inline]
209    pub fn from_account(account_data: &'a [u8]) -> Result<Self, ProgramError> {
210        let start = REGISTRY_OFFSET;
211        if account_data.len() < start + REGISTRY_HEADER_SIZE {
212            return Err(ProgramError::AccountDataTooSmall);
213        }
214
215        let count = u16::from_le_bytes([account_data[start], account_data[start + 1]]) as usize;
216
217        if count > MAX_REGISTRY_SEGMENTS {
218            return Err(ProgramError::InvalidAccountData);
219        }
220
221        let entries_offset = start + REGISTRY_HEADER_SIZE;
222        let needed = entries_offset + count * SEGMENT_ENTRY_SIZE;
223        if account_data.len() < needed {
224            return Err(ProgramError::AccountDataTooSmall);
225        }
226
227        Ok(Self {
228            data: account_data,
229            count,
230            entries_offset,
231        })
232    }
233
234    /// Number of segments in this account.
235    #[inline(always)]
236    pub fn segment_count(&self) -> usize {
237        self.count
238    }
239
240    /// Where segment data begins (after all entries).
241    #[inline(always)]
242    pub fn data_region_offset(&self) -> usize {
243        self.entries_offset + self.count * SEGMENT_ENTRY_SIZE
244    }
245
246    /// Get a segment entry by index.
247    #[inline]
248    pub fn entry(&self, index: usize) -> Result<&SegmentEntry, ProgramError> {
249        if index >= self.count {
250            return Err(ProgramError::InvalidArgument);
251        }
252        let offset = self.entries_offset + index * SEGMENT_ENTRY_SIZE;
253        // SAFETY: Bounds checked in from_account. SegmentEntry is align-1.
254        Ok(unsafe { &*(self.data.as_ptr().add(offset) as *const SegmentEntry) })
255    }
256
257    /// Find a segment by its ID.
258    #[inline]
259    pub fn find(&self, id: &SegmentId) -> Result<(usize, &SegmentEntry), ProgramError> {
260        let mut i = 0;
261        while i < self.count {
262            let offset = self.entries_offset + i * SEGMENT_ENTRY_SIZE;
263            // SAFETY: Bounds validated in from_account.
264            let entry = unsafe { &*(self.data.as_ptr().add(offset) as *const SegmentEntry) };
265            if entry.id == *id {
266                return Ok((i, entry));
267            }
268            i += 1;
269        }
270        Err(ProgramError::InvalidArgument)
271    }
272
273    /// Get the raw data slice for a segment by ID.
274    #[inline]
275    pub fn segment_data(&self, id: &SegmentId) -> Result<&'a [u8], ProgramError> {
276        let (_, entry) = self.find(id)?;
277        let start = entry.offset() as usize;
278        let end = start + entry.size() as usize;
279        if end > self.data.len() {
280            return Err(ProgramError::AccountDataTooSmall);
281        }
282        Ok(&self.data[start..end])
283    }
284
285    /// Get a typed overlay for a segment by ID.
286    #[inline]
287    pub fn segment_overlay<T: super::Pod + super::FixedLayout>(
288        &self,
289        id: &SegmentId,
290    ) -> Result<&'a T, ProgramError> {
291        let data = self.segment_data(id)?;
292        if data.len() < T::SIZE {
293            return Err(ProgramError::AccountDataTooSmall);
294        }
295        // SAFETY: Size checked. T: Pod, alignment-1.
296        Ok(unsafe { &*(data.as_ptr() as *const T) })
297    }
298
299    /// Iterate over all segment entries.
300    #[inline]
301    pub fn iter(&self) -> SegmentIter<'a> {
302        SegmentIter {
303            data: self.data,
304            entries_offset: self.entries_offset,
305            count: self.count,
306            pos: 0,
307        }
308    }
309
310    /// Total bytes consumed by the registry (header + all entries).
311    #[inline(always)]
312    pub fn registry_size(&self) -> usize {
313        REGISTRY_HEADER_SIZE + self.count * SEGMENT_ENTRY_SIZE
314    }
315
316    /// Compute required account size for given segment specs.
317    ///
318    /// `specs` is `(segment_data_size,)` per segment.
319    #[inline]
320    pub const fn required_account_size(
321        header_size: usize,
322        segment_count: usize,
323        total_segment_data: usize,
324    ) -> usize {
325        header_size + REGISTRY_HEADER_SIZE + segment_count * SEGMENT_ENTRY_SIZE + total_segment_data
326    }
327}
328
329/// Iterator over segment entries.
330pub struct SegmentIter<'a> {
331    data: &'a [u8],
332    entries_offset: usize,
333    count: usize,
334    pos: usize,
335}
336
337impl<'a> Iterator for SegmentIter<'a> {
338    type Item = (usize, &'a SegmentEntry);
339
340    #[inline]
341    fn next(&mut self) -> Option<Self::Item> {
342        if self.pos >= self.count {
343            return None;
344        }
345        let idx = self.pos;
346        let offset = self.entries_offset + idx * SEGMENT_ENTRY_SIZE;
347        // SAFETY: Bounds validated by SegmentRegistry::from_account.
348        let entry = unsafe { &*(self.data.as_ptr().add(offset) as *const SegmentEntry) };
349        self.pos += 1;
350        Some((idx, entry))
351    }
352}
353
354// -- Mutable Registry --
355
356/// Mutable view over a segmented account's registry.
357pub struct SegmentRegistryMut<'a> {
358    data: &'a mut [u8],
359    count: usize,
360    entries_offset: usize,
361}
362
363impl<'a> SegmentRegistryMut<'a> {
364    /// Parse a mutable segment registry from full account data.
365    #[inline]
366    pub fn from_account_mut(account_data: &'a mut [u8]) -> Result<Self, ProgramError> {
367        let start = REGISTRY_OFFSET;
368        if account_data.len() < start + REGISTRY_HEADER_SIZE {
369            return Err(ProgramError::AccountDataTooSmall);
370        }
371
372        let count = u16::from_le_bytes([account_data[start], account_data[start + 1]]) as usize;
373
374        if count > MAX_REGISTRY_SEGMENTS {
375            return Err(ProgramError::InvalidAccountData);
376        }
377
378        let entries_offset = start + REGISTRY_HEADER_SIZE;
379        let needed = entries_offset + count * SEGMENT_ENTRY_SIZE;
380        if account_data.len() < needed {
381            return Err(ProgramError::AccountDataTooSmall);
382        }
383
384        Ok(Self {
385            data: account_data,
386            count,
387            entries_offset,
388        })
389    }
390
391    /// Number of segments.
392    #[inline(always)]
393    pub fn segment_count(&self) -> usize {
394        self.count
395    }
396
397    /// Initialize the registry with segment specifications.
398    ///
399    /// `specs` is `(segment_id, data_size, version)` per segment.
400    /// Must be called on a freshly zeroed account.
401    #[inline]
402    pub fn init(data: &mut [u8], specs: &[(SegmentId, u32, u8)]) -> Result<(), ProgramError> {
403        let start = REGISTRY_OFFSET;
404        if specs.len() > MAX_REGISTRY_SEGMENTS {
405            return Err(ProgramError::InvalidArgument);
406        }
407
408        // Reject duplicate segment IDs. Max 16 segments so O(n^2) is fine.
409        let n = specs.len();
410        let mut i = 0;
411        while i < n {
412            let mut j = i + 1;
413            while j < n {
414                if specs[i].0 == specs[j].0 {
415                    return Err(ProgramError::InvalidArgument);
416                }
417                j += 1;
418            }
419            i += 1;
420        }
421
422        let count = specs.len();
423        let entries_offset = start + REGISTRY_HEADER_SIZE;
424        let data_region = entries_offset + count * SEGMENT_ENTRY_SIZE;
425
426        // Write registry header: segment_count (u16 LE) + flags (u16 LE)
427        data[start] = (count & 0xFF) as u8;
428        data[start + 1] = ((count >> 8) & 0xFF) as u8;
429        data[start + 2] = 0; // flags low
430        data[start + 3] = 0; // flags high
431
432        // Write entries and compute offsets
433        let mut current_offset = data_region as u32;
434        for (i, &(id, size, version)) in specs.iter().enumerate() {
435            let entry = SegmentEntry::new(id, current_offset, size, 0, version);
436            let entry_offset = entries_offset + i * SEGMENT_ENTRY_SIZE;
437            // SAFETY: entry_offset + 16 is within bounds (verified above).
438            let dst = &mut data[entry_offset..entry_offset + SEGMENT_ENTRY_SIZE];
439            // SAFETY: SegmentEntry is repr(C), alignment-1, 16 bytes.
440            unsafe {
441                core::ptr::copy_nonoverlapping(
442                    &entry as *const SegmentEntry as *const u8,
443                    dst.as_mut_ptr(),
444                    SEGMENT_ENTRY_SIZE,
445                );
446            }
447            current_offset += size;
448        }
449
450        Ok(())
451    }
452
453    /// Get a mutable entry by index.
454    #[inline]
455    pub fn entry_mut(&mut self, index: usize) -> Result<&mut SegmentEntry, ProgramError> {
456        if index >= self.count {
457            return Err(ProgramError::InvalidArgument);
458        }
459        let offset = self.entries_offset + index * SEGMENT_ENTRY_SIZE;
460        // SAFETY: Bounds checked. SegmentEntry is align-1. Exclusive access.
461        Ok(unsafe { &mut *(self.data.as_mut_ptr().add(offset) as *mut SegmentEntry) })
462    }
463
464    /// Find a segment by ID and return mutable entry.
465    #[inline]
466    pub fn find_mut(&mut self, id: &SegmentId) -> Result<(usize, &mut SegmentEntry), ProgramError> {
467        let mut i = 0;
468        while i < self.count {
469            let offset = self.entries_offset + i * SEGMENT_ENTRY_SIZE;
470            // SAFETY: Bounds validated in from_account_mut.
471            let entry = unsafe { &mut *(self.data.as_mut_ptr().add(offset) as *mut SegmentEntry) };
472            if entry.id == *id {
473                return Ok((i, entry));
474            }
475            i += 1;
476        }
477        Err(ProgramError::InvalidArgument)
478    }
479
480    /// Get the mutable data slice for a segment by ID.
481    ///
482    /// Returns an error if the segment is locked, frozen, or has an
483    /// immutable role (`Audit`). Use [`segment_data_mut_unchecked`] to
484    /// bypass role enforcement (e.g., during initial account setup).
485    #[inline]
486    pub fn segment_data_mut(&mut self, id: &SegmentId) -> Result<&mut [u8], ProgramError> {
487        let (_, entry) = self.find_mut(id)?;
488        if entry.is_locked() || entry.is_frozen() {
489            return Err(ProgramError::InvalidAccountData);
490        }
491        // Role-based write guard: Audit segments are immutable after init.
492        if entry.role().is_immutable_after_init() {
493            return Err(ProgramError::InvalidAccountData);
494        }
495        let start = entry.offset() as usize;
496        let size = entry.size() as usize;
497        let end = start + size;
498        if end > self.data.len() {
499            return Err(ProgramError::AccountDataTooSmall);
500        }
501        Ok(&mut self.data[start..end])
502    }
503
504    /// Get the mutable data slice for a segment without role enforcement.
505    ///
506    /// Use during account initialization when writing to Audit segments
507    /// before they become immutable. Still checks locked/frozen flags.
508    #[inline]
509    pub fn segment_data_mut_unchecked(
510        &mut self,
511        id: &SegmentId,
512    ) -> Result<&mut [u8], ProgramError> {
513        let (_, entry) = self.find_mut(id)?;
514        if entry.is_locked() || entry.is_frozen() {
515            return Err(ProgramError::InvalidAccountData);
516        }
517        let start = entry.offset() as usize;
518        let size = entry.size() as usize;
519        let end = start + size;
520        if end > self.data.len() {
521            return Err(ProgramError::AccountDataTooSmall);
522        }
523        Ok(&mut self.data[start..end])
524    }
525
526    /// Get a mutable typed overlay for a segment.
527    ///
528    /// Returns an error if the segment is locked or frozen.
529    #[inline]
530    pub fn segment_overlay_mut<T: super::Pod + super::FixedLayout>(
531        &mut self,
532        id: &SegmentId,
533    ) -> Result<&mut T, ProgramError> {
534        let data = self.segment_data_mut(id)?;
535        if data.len() < T::SIZE {
536            return Err(ProgramError::AccountDataTooSmall);
537        }
538        // SAFETY: Size checked. T: Pod, alignment-1. Exclusive access.
539        Ok(unsafe { &mut *(data.as_mut_ptr() as *mut T) })
540    }
541
542    /// Overlay a named segment as a bounded `FixedVec`.
543    #[cfg(feature = "collections")]
544    #[inline]
545    pub fn segment_fixed_vec<T: super::Pod + super::FixedLayout>(
546        &mut self,
547        id: &SegmentId,
548    ) -> Result<FixedVec<'_, T>, ProgramError> {
549        FixedVec::from_bytes(self.segment_data_mut(id)?)
550    }
551
552    /// Overlay a named segment as a `SortedVec`.
553    #[cfg(feature = "collections")]
554    #[inline]
555    pub fn segment_sorted_vec<T: super::Pod + super::FixedLayout + Ord>(
556        &mut self,
557        id: &SegmentId,
558    ) -> Result<SortedVec<'_, T>, ProgramError> {
559        SortedVec::from_bytes(self.segment_data_mut(id)?)
560    }
561
562    /// Overlay a named segment as a `RingBuffer`.
563    #[cfg(feature = "collections")]
564    #[inline]
565    pub fn segment_ring_buffer<T: super::Pod + super::FixedLayout>(
566        &mut self,
567        id: &SegmentId,
568    ) -> Result<RingBuffer<'_, T>, ProgramError> {
569        RingBuffer::from_bytes(self.segment_data_mut(id)?)
570    }
571
572    /// Overlay a named segment as a `SlotMap`.
573    #[cfg(feature = "collections")]
574    #[inline]
575    pub fn segment_slot_map<T: super::Pod + super::FixedLayout>(
576        &mut self,
577        id: &SegmentId,
578    ) -> Result<SlotMap<'_, T>, ProgramError> {
579        SlotMap::from_bytes(self.segment_data_mut(id)?)
580    }
581
582    /// Overlay a named segment as a `Journal`.
583    #[cfg(feature = "collections")]
584    #[inline]
585    pub fn segment_journal<T: super::Pod + super::FixedLayout>(
586        &mut self,
587        id: &SegmentId,
588    ) -> Result<Journal<'_, T>, ProgramError> {
589        Journal::from_bytes_mut(self.segment_data_mut(id)?)
590    }
591
592    /// Overlay a named segment as a `Slab`.
593    #[cfg(feature = "collections")]
594    #[inline]
595    pub fn segment_slab<T: super::Pod + super::FixedLayout>(
596        &mut self,
597        id: &SegmentId,
598    ) -> Result<Slab<'_, T>, ProgramError> {
599        Slab::from_bytes_mut(self.segment_data_mut(id)?)
600    }
601
602    /// Freeze a segment (set frozen flag).
603    #[inline]
604    pub fn freeze_segment(&mut self, id: &SegmentId) -> Result<(), ProgramError> {
605        let (_, entry) = self.find_mut(id)?;
606        let new_flags = entry.flags() | SEG_FLAG_FROZEN;
607        entry.set_flags(new_flags);
608        Ok(())
609    }
610
611    /// Unfreeze a segment.
612    #[inline]
613    pub fn unfreeze_segment(&mut self, id: &SegmentId) -> Result<(), ProgramError> {
614        let (_, entry) = self.find_mut(id)?;
615        let new_flags = entry.flags() & !SEG_FLAG_FROZEN;
616        entry.set_flags(new_flags);
617        Ok(())
618    }
619
620    /// Lock a segment permanently (cannot be unlocked).
621    #[inline]
622    pub fn lock_segment(&mut self, id: &SegmentId) -> Result<(), ProgramError> {
623        let (_, entry) = self.find_mut(id)?;
624        let new_flags = entry.flags() | SEG_FLAG_LOCKED;
625        entry.set_flags(new_flags);
626        Ok(())
627    }
628}
629
630#[cfg(test)]
631mod tests {
632    use super::*;
633
634    #[repr(C)]
635    #[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
636    struct Entry8 {
637        value: u8,
638    }
639
640    #[cfg(feature = "hopper-native-backend")]
641    unsafe impl ::hopper_runtime::__hopper_native::bytemuck::Zeroable for Entry8 {}
642    #[cfg(feature = "hopper-native-backend")]
643    unsafe impl ::hopper_runtime::__hopper_native::bytemuck::Pod for Entry8 {}
644    unsafe impl crate::account::Pod for Entry8 {}
645
646    impl crate::account::FixedLayout for Entry8 {
647        const SIZE: usize = 1;
648    }
649
650    #[cfg(feature = "collections")]
651    #[test]
652    fn segment_fixed_vec_adapter_exposes_vec_api() {
653        const CORE: SegmentId = segment_id("core");
654        let total = REGISTRY_OFFSET + REGISTRY_HEADER_SIZE + SEGMENT_ENTRY_SIZE + 8;
655        let mut account = std::vec![0u8; total];
656
657        SegmentRegistryMut::init(&mut account, &[(CORE, 8, 1)]).unwrap();
658
659        let mut registry = SegmentRegistryMut::from_account_mut(&mut account).unwrap();
660        let mut values = registry.segment_fixed_vec::<Entry8>(&CORE).unwrap();
661        values.push(Entry8 { value: 7 }).unwrap();
662        values.push(Entry8 { value: 9 }).unwrap();
663
664        assert_eq!(values.len(), 2);
665        assert_eq!(values.get(0).unwrap().value, 7);
666        assert_eq!(values.get(1).unwrap().value, 9);
667    }
668
669    #[cfg(feature = "collections")]
670    #[test]
671    fn segment_journal_adapter_exposes_journal_api() {
672        const AUDIT: SegmentId = segment_id("audit");
673        let segment_bytes = crate::collections::JOURNAL_HEADER_SIZE + 4;
674        let total = REGISTRY_OFFSET + REGISTRY_HEADER_SIZE + SEGMENT_ENTRY_SIZE + segment_bytes;
675        let mut account = std::vec![0u8; total];
676
677        SegmentRegistryMut::init(&mut account, &[(AUDIT, segment_bytes as u32, 1)]).unwrap();
678
679        {
680            let mut registry = SegmentRegistryMut::from_account_mut(&mut account).unwrap();
681            let mut journal = registry.segment_journal::<Entry8>(&AUDIT).unwrap();
682            journal.init(false);
683            journal.append(Entry8 { value: 3 }).unwrap();
684            journal.append(Entry8 { value: 4 }).unwrap();
685        }
686
687        let registry = SegmentRegistry::from_account(&account).unwrap();
688        let bytes = registry.segment_data(&AUDIT).unwrap();
689        let reader = crate::collections::Journal::<Entry8>::from_bytes(bytes).unwrap();
690        assert_eq!(reader.entry_count(), 2);
691        assert_eq!(reader.read(0).unwrap().value, 3);
692        assert_eq!(reader.read(1).unwrap().value, 4);
693    }
694}