Skip to main content

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}