Skip to main content

hopper_core/account/
reader.rs

1//! Account reader with header-aware field access.
2
3use super::cursor::SliceCursor;
4use super::header::{AccountHeader, HEADER_LEN};
5use super::pod::pod_from_bytes;
6use hopper_runtime::error::ProgramError;
7
8/// Header-aware read-only account reader.
9///
10/// Provides typed access to the header fields and a `SliceCursor` for the body.
11pub struct AccountReader<'a> {
12    data: &'a [u8],
13}
14
15impl<'a> AccountReader<'a> {
16    /// Create a reader, validating minimum header size.
17    #[inline(always)]
18    pub fn new(data: &'a [u8]) -> Result<Self, ProgramError> {
19        if data.len() < HEADER_LEN {
20            return Err(ProgramError::AccountDataTooSmall);
21        }
22        Ok(Self { data })
23    }
24
25    /// Create a reader with full header validation.
26    #[inline(always)]
27    pub fn new_checked(
28        data: &'a [u8],
29        disc: u8,
30        min_version: u8,
31        layout_id: &[u8; 8],
32    ) -> Result<Self, ProgramError> {
33        super::header::check_header(data, disc, min_version, layout_id)?;
34        Ok(Self { data })
35    }
36
37    /// The underlying header, zero-copy.
38    #[inline(always)]
39    pub fn header(&self) -> &AccountHeader {
40        // SAFETY: We validated data.len() >= HEADER_LEN in constructor.
41        // AccountHeader has align_of == 1 and size == HEADER_LEN.
42        unsafe { &*(self.data.as_ptr() as *const AccountHeader) }
43    }
44
45    /// Discriminator byte.
46    #[inline(always)]
47    pub fn discriminator(&self) -> u8 {
48        self.data[0]
49    }
50
51    /// Version byte.
52    #[inline(always)]
53    pub fn version(&self) -> u8 {
54        self.data[1]
55    }
56
57    /// Flags as u16.
58    #[inline(always)]
59    pub fn flags(&self) -> u16 {
60        u16::from_le_bytes([self.data[2], self.data[3]])
61    }
62
63    /// Layout ID (8 bytes).
64    #[inline(always)]
65    pub fn layout_id(&self) -> [u8; 8] {
66        let mut id = [0u8; 8];
67        id.copy_from_slice(&self.data[4..12]);
68        id
69    }
70
71    /// Body data after the header as a cursor.
72    #[inline(always)]
73    pub fn body(&self) -> SliceCursor<'a> {
74        SliceCursor::new(&self.data[HEADER_LEN..])
75    }
76
77    /// Raw body bytes after the header.
78    #[inline(always)]
79    pub fn body_bytes(&self) -> &'a [u8] {
80        &self.data[HEADER_LEN..]
81    }
82
83    /// Entire raw data including header.
84    #[inline(always)]
85    pub fn raw(&self) -> &'a [u8] {
86        self.data
87    }
88
89    /// Read a u64 at a specific offset from the start of data.
90    #[inline(always)]
91    pub fn u64_at(&self, offset: usize) -> Result<u64, ProgramError> {
92        if offset + 8 > self.data.len() {
93            return Err(ProgramError::InvalidAccountData);
94        }
95        Ok(u64::from_le_bytes([
96            self.data[offset],
97            self.data[offset + 1],
98            self.data[offset + 2],
99            self.data[offset + 3],
100            self.data[offset + 4],
101            self.data[offset + 5],
102            self.data[offset + 6],
103            self.data[offset + 7],
104        ]))
105    }
106
107    /// Read a 32-byte address at a specific offset.
108    #[inline(always)]
109    pub fn address_at(&self, offset: usize) -> Result<&'a [u8; 32], ProgramError> {
110        if offset + 32 > self.data.len() {
111            return Err(ProgramError::InvalidAccountData);
112        }
113        // SAFETY: Checked bounds. [u8; 32] has alignment 1.
114        Ok(unsafe { &*(self.data.as_ptr().add(offset) as *const [u8; 32]) })
115    }
116
117    /// Overlay a Pod type at a specific offset.
118    #[inline(always)]
119    pub fn overlay_at<T: super::Pod + super::FixedLayout>(
120        &self,
121        offset: usize,
122    ) -> Result<&'a T, ProgramError> {
123        if offset > self.data.len() {
124            return Err(ProgramError::InvalidAccountData);
125        }
126        pod_from_bytes::<T>(&self.data[offset..])
127    }
128}