Skip to main content

hopper_core/accounts/
segmented.rs

1//! Segmented account wrapper with role-aware access.
2//!
3//! Wraps an account that uses Hopper's segment registry to divide data
4//! into typed regions (Core, Extension, Journal, Cache, etc.).
5
6use hopper_runtime::error::ProgramError;
7use hopper_runtime::{AccountView, Address, Ref, RefMut};
8
9use crate::account::{
10    FixedLayout, Pod, SegmentEntry, SegmentId, SegmentRegistry, REGISTRY_HEADER_SIZE,
11    REGISTRY_OFFSET, SEGMENT_ENTRY_SIZE,
12};
13use crate::check;
14use crate::check::modifier::HopperLayout;
15
16/// Borrow-carrying registry view for segmented accounts.
17pub struct BorrowedSegmentRegistry<'a> {
18    data: Ref<'a, [u8]>,
19}
20
21impl<'a> BorrowedSegmentRegistry<'a> {
22    #[inline]
23    pub fn segment_count(&self) -> Result<usize, ProgramError> {
24        Ok(SegmentRegistry::from_account(&self.data)?.segment_count())
25    }
26
27    #[inline]
28    pub fn data_region_offset(&self) -> Result<usize, ProgramError> {
29        Ok(SegmentRegistry::from_account(&self.data)?.data_region_offset())
30    }
31
32    #[inline]
33    pub fn entry(&self, index: usize) -> Result<&SegmentEntry, ProgramError> {
34        let registry = SegmentRegistry::from_account(&self.data)?;
35        if index >= registry.segment_count() {
36            return Err(ProgramError::InvalidArgument);
37        }
38        let offset = REGISTRY_OFFSET + REGISTRY_HEADER_SIZE + index * SEGMENT_ENTRY_SIZE;
39        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
40        Ok(unsafe { &*(self.data.as_bytes_ptr().add(offset) as *const SegmentEntry) })
41    }
42
43    #[inline]
44    pub fn segment_data(&self, id: &SegmentId) -> Result<&[u8], ProgramError> {
45        let count = self.segment_count()?;
46        let mut index = 0;
47        while index < count {
48            let entry = self.entry(index)?;
49            if entry.id == *id {
50                let start = entry.offset() as usize;
51                let end = start
52                    .checked_add(entry.size() as usize)
53                    .ok_or(ProgramError::ArithmeticOverflow)?;
54                if end > self.data.len() {
55                    return Err(ProgramError::AccountDataTooSmall);
56                }
57                return Ok(&self.data[start..end]);
58            }
59            index += 1;
60        }
61        Err(ProgramError::InvalidArgument)
62    }
63}
64
65/// A segmented account with role-aware region access.
66///
67/// The account data is divided into a segment registry header followed by
68/// typed segments. Each segment has a role (Core, Extension, Journal, etc.)
69/// and can be accessed individually.
70pub struct SegmentedAccount<'a, T: Pod + FixedLayout + HopperLayout> {
71    view: &'a AccountView,
72    #[allow(dead_code)] // stored for future segment CPI operations
73    program_id: &'a Address,
74    _marker: core::marker::PhantomData<T>,
75}
76
77impl<'a, T: Pod + FixedLayout + HopperLayout> SegmentedAccount<'a, T> {
78    /// Construct from an AccountView with header and owner validation.
79    #[inline]
80    pub fn from_account(
81        account: &'a AccountView,
82        program_id: &'a Address,
83    ) -> Result<Self, ProgramError> {
84        check::check_owner(account, program_id)?;
85        let data = account.try_borrow()?;
86        crate::account::check_header(&data, T::DISC, T::VERSION, &T::LAYOUT_ID)?;
87        Ok(Self {
88            view: account,
89            program_id,
90            _marker: core::marker::PhantomData,
91        })
92    }
93
94    /// Access the segment registry.
95    #[inline]
96    pub fn registry(&self) -> Result<BorrowedSegmentRegistry<'_>, ProgramError> {
97        Ok(BorrowedSegmentRegistry {
98            data: self.view.try_borrow()?,
99        })
100    }
101
102    /// Read a segment's raw bytes by index.
103    #[inline]
104    pub fn segment_by_index(&self, index: usize) -> Result<Ref<'_, [u8]>, ProgramError> {
105        let data = self.view.try_borrow()?;
106        let (start, size) = {
107            let registry = SegmentRegistry::from_account(&data)?;
108            let entry = registry.entry(index)?;
109            (entry.offset() as usize, entry.size() as usize)
110        };
111        data.slice(start, size)
112    }
113
114    /// Read a segment's data by its 4-byte ID.
115    #[inline]
116    pub fn segment_data(
117        &self,
118        id: &crate::account::SegmentId,
119    ) -> Result<Ref<'_, [u8]>, ProgramError> {
120        let data = self.view.try_borrow()?;
121        let (start, size) = {
122            let registry = SegmentRegistry::from_account(&data)?;
123            let (_, entry) = registry.find(id)?;
124            (entry.offset() as usize, entry.size() as usize)
125        };
126        data.slice(start, size)
127    }
128
129    /// Read a segment's raw bytes mutably by index.
130    #[inline]
131    pub fn segment_by_index_mut(&self, index: usize) -> Result<RefMut<'_, [u8]>, ProgramError> {
132        let data = self.view.try_borrow_mut()?;
133        let (start, size) = {
134            let registry = SegmentRegistry::from_account(&data)?;
135            let entry = registry.entry(index)?;
136            (entry.offset() as usize, entry.size() as usize)
137        };
138        data.slice(start, size)
139    }
140
141    /// The account's address.
142    #[inline(always)]
143    pub fn address(&self) -> &Address {
144        self.view.address()
145    }
146
147    /// The underlying AccountView.
148    #[inline(always)]
149    pub fn to_account_view(&self) -> &'a AccountView {
150        self.view
151    }
152}