hopper_core/account/
header.rs1use hopper_runtime::error::ProgramError;
20
21pub const HEADER_LEN: usize = 16;
23
24pub const HEADER_FORMAT: u8 = 1;
26
27const DISC_OFFSET: usize = 0;
29const VERSION_OFFSET: usize = 1;
30const FLAGS_OFFSET: usize = 2;
31const LAYOUT_ID_OFFSET: usize = 4;
32const SCHEMA_EPOCH_OFFSET: usize = 12;
33
34pub const DEFAULT_SCHEMA_EPOCH: u32 = hopper_runtime::layout::DEFAULT_SCHEMA_EPOCH;
36
37#[derive(Clone, Copy, PartialEq, Eq)]
39#[repr(C)]
40pub struct AccountHeader {
41 pub disc: u8,
42 pub version: u8,
43 pub flags: [u8; 2],
44 pub layout_id: [u8; 8],
45 pub schema_epoch: [u8; 4],
46}
47
48const _: () = assert!(core::mem::size_of::<AccountHeader>() == HEADER_LEN);
49const _: () = assert!(core::mem::align_of::<AccountHeader>() == 1);
50
51#[cfg(feature = "hopper-native-backend")]
55unsafe impl ::hopper_runtime::__hopper_native::bytemuck::Zeroable for AccountHeader {}
56#[cfg(feature = "hopper-native-backend")]
57unsafe impl ::hopper_runtime::__hopper_native::bytemuck::Pod for AccountHeader {}
58
59unsafe impl super::Pod for AccountHeader {}
61unsafe impl ::hopper_runtime::__sealed::HopperZeroCopySealed for AccountHeader {}
63
64impl super::FixedLayout for AccountHeader {
65 const SIZE: usize = HEADER_LEN;
66}
67
68impl AccountHeader {
69 #[inline(always)]
71 pub const fn new(disc: u8, version: u8, flags: u16, layout_id: [u8; 8]) -> Self {
72 Self::new_with_schema_epoch(disc, version, flags, layout_id, DEFAULT_SCHEMA_EPOCH)
73 }
74
75 #[inline(always)]
77 pub const fn new_with_schema_epoch(
78 disc: u8,
79 version: u8,
80 flags: u16,
81 layout_id: [u8; 8],
82 schema_epoch: u32,
83 ) -> Self {
84 Self {
85 disc,
86 version,
87 flags: flags.to_le_bytes(),
88 layout_id,
89 schema_epoch: schema_epoch.to_le_bytes(),
90 }
91 }
92
93 #[inline(always)]
95 pub const fn flags_u16(&self) -> u16 {
96 u16::from_le_bytes(self.flags)
97 }
98
99 #[inline(always)]
101 pub const fn schema_epoch_u32(&self) -> u32 {
102 u32::from_le_bytes(self.schema_epoch)
103 }
104}
105
106#[inline(always)]
111pub fn write_header(
112 data: &mut [u8],
113 disc: u8,
114 version: u8,
115 layout_id: &[u8; 8],
116) -> Result<(), ProgramError> {
117 if data.len() < HEADER_LEN {
118 return Err(ProgramError::AccountDataTooSmall);
119 }
120 data[DISC_OFFSET] = disc;
121 data[VERSION_OFFSET] = version;
122 data[FLAGS_OFFSET..FLAGS_OFFSET + 2].copy_from_slice(&0u16.to_le_bytes());
123 data[LAYOUT_ID_OFFSET..LAYOUT_ID_OFFSET + 8].copy_from_slice(layout_id);
124 data[SCHEMA_EPOCH_OFFSET..SCHEMA_EPOCH_OFFSET + 4]
125 .copy_from_slice(&DEFAULT_SCHEMA_EPOCH.to_le_bytes());
126 Ok(())
127}
128
129#[inline(always)]
131pub fn check_header(
132 data: &[u8],
133 expected_disc: u8,
134 min_version: u8,
135 layout_id: &[u8; 8],
136) -> Result<(), ProgramError> {
137 if data.len() < HEADER_LEN {
138 return Err(ProgramError::AccountDataTooSmall);
139 }
140 if data[DISC_OFFSET] != expected_disc {
141 return Err(ProgramError::InvalidAccountData);
142 }
143 if data[VERSION_OFFSET] < min_version {
144 return Err(ProgramError::InvalidAccountData);
145 }
146 if data[LAYOUT_ID_OFFSET..LAYOUT_ID_OFFSET + 8] != *layout_id {
147 return Err(ProgramError::InvalidAccountData);
148 }
149 Ok(())
150}
151
152#[inline(always)]
158pub fn read_discriminator(data: &[u8]) -> Result<u8, ProgramError> {
159 data.first()
160 .copied()
161 .ok_or(ProgramError::AccountDataTooSmall)
162}
163
164#[inline(always)]
166pub fn read_version(data: &[u8]) -> Result<u8, ProgramError> {
167 if data.len() < 2 {
168 return Err(ProgramError::AccountDataTooSmall);
169 }
170 Ok(data[VERSION_OFFSET])
171}
172
173#[inline(always)]
175pub fn read_header_flags(data: &[u8]) -> Result<u16, ProgramError> {
176 if data.len() < 4 {
177 return Err(ProgramError::AccountDataTooSmall);
178 }
179 Ok(u16::from_le_bytes([
180 data[FLAGS_OFFSET],
181 data[FLAGS_OFFSET + 1],
182 ]))
183}
184
185#[inline(always)]
187pub fn read_layout_id(data: &[u8]) -> Result<[u8; 8], ProgramError> {
188 if data.len() < 12 {
189 return Err(ProgramError::AccountDataTooSmall);
190 }
191 let mut id = [0u8; 8];
192 id.copy_from_slice(&data[LAYOUT_ID_OFFSET..LAYOUT_ID_OFFSET + 8]);
193 Ok(id)
194}