jiminy/header.rs
1use pinocchio::error::ProgramError;
2
3/// Size of the Jiminy standard account header, in bytes.
4///
5/// Layout:
6/// - Byte 0: `u8` discriminator (account type tag)
7/// - Byte 1: `u8` version (schema version - bump when layout changes)
8/// - Byte 2: `u8` flags (application-defined bitfield)
9/// - Byte 3: `u8` reserved (must be zero)
10/// - Bytes 4-7: `u32` data_len (optional; payload length for variable-size accounts)
11///
12/// Total: 8 bytes, naturally aligned.
13pub const HEADER_LEN: usize = 8;
14
15/// Write the standard 8-byte Jiminy account header.
16///
17/// Sets the discriminator, version, and flags. Reserved byte and `data_len`
18/// are zeroed. Call this immediately after allocating the account (and after
19/// `zero_init` if desired).
20///
21/// ```rust,ignore
22/// let mut raw = account.try_borrow_mut()?;
23/// jiminy::write_header(&mut raw, VAULT_DISC, 1, 0)?;
24/// let mut w = DataWriter::new(&mut raw[HEADER_LEN..]);
25/// w.write_u64(0)?; // balance
26/// w.write_address(&authority)?; // authority pubkey
27/// ```
28#[inline(always)]
29pub fn write_header(
30 data: &mut [u8],
31 discriminator: u8,
32 version: u8,
33 flags: u8,
34) -> Result<(), ProgramError> {
35 if data.len() < HEADER_LEN {
36 return Err(ProgramError::AccountDataTooSmall);
37 }
38 data[0] = discriminator;
39 data[1] = version;
40 data[2] = flags;
41 data[3] = 0; // reserved
42 // data_len = 0 (bytes 4-7)
43 data[4..8].copy_from_slice(&0u32.to_le_bytes());
44 Ok(())
45}
46
47/// Write the full 8-byte header including an explicit `data_len` value.
48///
49/// Use this when initializing variable-length accounts where the payload
50/// size is not implied by the fixed account allocation.
51#[inline(always)]
52pub fn write_header_with_len(
53 data: &mut [u8],
54 discriminator: u8,
55 version: u8,
56 flags: u8,
57 data_len: u32,
58) -> Result<(), ProgramError> {
59 if data.len() < HEADER_LEN {
60 return Err(ProgramError::AccountDataTooSmall);
61 }
62 data[0] = discriminator;
63 data[1] = version;
64 data[2] = flags;
65 data[3] = 0; // reserved
66 data[4..8].copy_from_slice(&data_len.to_le_bytes());
67 Ok(())
68}
69
70/// Validate the discriminator and minimum version of an account header.
71///
72/// Returns `InvalidAccountData` if the discriminator doesn't match or
73/// the stored version is below `min_version`.
74#[inline(always)]
75pub fn check_header(
76 data: &[u8],
77 expected_disc: u8,
78 min_version: u8,
79) -> Result<(), ProgramError> {
80 if data.len() < HEADER_LEN {
81 return Err(ProgramError::AccountDataTooSmall);
82 }
83 if data[0] != expected_disc {
84 return Err(ProgramError::InvalidAccountData);
85 }
86 if data[1] < min_version {
87 return Err(ProgramError::InvalidAccountData);
88 }
89 Ok(())
90}
91
92/// Read the version byte from an account header.
93#[inline(always)]
94pub fn read_version(data: &[u8]) -> Result<u8, ProgramError> {
95 if data.len() < 2 {
96 return Err(ProgramError::AccountDataTooSmall);
97 }
98 Ok(data[1])
99}
100
101/// Read the flags byte from an account header.
102#[inline(always)]
103pub fn read_header_flags(data: &[u8]) -> Result<u8, ProgramError> {
104 if data.len() < 3 {
105 return Err(ProgramError::AccountDataTooSmall);
106 }
107 Ok(data[2])
108}
109
110/// Read the `data_len` field (bytes 4-7) from an account header.
111#[inline(always)]
112pub fn read_data_len(data: &[u8]) -> Result<u32, ProgramError> {
113 if data.len() < HEADER_LEN {
114 return Err(ProgramError::AccountDataTooSmall);
115 }
116 Ok(u32::from_le_bytes(
117 data[4..8].try_into().unwrap(),
118 ))
119}
120
121/// Return the payload slice that follows the 8-byte header.
122///
123/// ```rust,ignore
124/// let data = account.try_borrow()?;
125/// check_header(&data, VAULT_DISC, 1)?;
126/// let payload = header_payload(&data);
127/// let mut cur = SliceCursor::new(payload);
128/// ```
129#[inline(always)]
130pub fn header_payload(data: &[u8]) -> &[u8] {
131 if data.len() <= HEADER_LEN {
132 &[]
133 } else {
134 &data[HEADER_LEN..]
135 }
136}
137
138/// Return the mutable payload slice that follows the 8-byte header.
139#[inline(always)]
140pub fn header_payload_mut(data: &mut [u8]) -> &mut [u8] {
141 if data.len() <= HEADER_LEN {
142 &mut []
143 } else {
144 &mut data[HEADER_LEN..]
145 }
146}