Skip to main content

hopper_core/account/
pod.rs

1//! Pod and FixedLayout traits for zero-copy account access.
2//!
3//! `Pod` is the canonical "safe to overlay on raw bytes" marker trait.
4//! It lives in [`hopper_runtime::pod`] so every Hopper access path, the
5//! native substrate, runtime accessors, frame, and core helpers, can
6//! agree on one contract. Hopper-core re-exports it unchanged so
7//! existing `use hopper_core::account::Pod` call sites keep compiling.
8//!
9//! The safety contract (enforced by `unsafe impl`): every
10//! `[u8; size_of::<T>()]` bit pattern decodes to a valid `T`, alignment
11//! is 1, no padding, no internal pointers. `#[hopper::state]` emits the
12//! derived `unsafe impl Pod` for every generated layout; hand-authored
13//! layouts must opt in explicitly.
14
15use hopper_runtime::error::ProgramError;
16
17pub use hopper_runtime::pod::Pod;
18
19/// Trait for types with a compile-time known wire size.
20pub trait FixedLayout {
21    /// Total byte size on the wire (including any header if applicable).
22    const SIZE: usize;
23}
24
25/// Zero-copy cast from bytes to an immutable reference.
26///
27/// # Safety
28///
29/// The returned reference aliases the input slice. Callers must not create
30/// overlapping mutable references to the same memory.
31#[inline(always)]
32pub fn pod_from_bytes<T: Pod + FixedLayout>(data: &[u8]) -> Result<&T, ProgramError> {
33    if data.len() < T::SIZE {
34        return Err(ProgramError::InvalidAccountData);
35    }
36    // SAFETY: T: Pod guarantees all bit patterns valid. We checked length.
37    // Alignment is 1 for all our wire types (compile-time enforced by WireType).
38    // For user structs, alignment is 1 via #[repr(C)] over alignment-1 fields.
39    Ok(unsafe { &*(data.as_ptr() as *const T) })
40}
41
42/// Zero-copy cast from bytes to a mutable reference.
43///
44/// # Safety
45///
46/// The returned reference aliases the input slice mutably. Callers must not
47/// create overlapping references (mutable or immutable) to the same memory.
48#[inline(always)]
49pub fn pod_from_bytes_mut<T: Pod + FixedLayout>(data: &mut [u8]) -> Result<&mut T, ProgramError> {
50    if data.len() < T::SIZE {
51        return Err(ProgramError::InvalidAccountData);
52    }
53    // SAFETY: Same as pod_from_bytes, plus we have exclusive (&mut) access.
54    Ok(unsafe { &mut *(data.as_mut_ptr() as *mut T) })
55}
56
57/// Copy a Pod value from bytes (alignment-safe).
58#[inline(always)]
59pub fn pod_read<T: Pod + FixedLayout>(data: &[u8]) -> Result<T, ProgramError> {
60    if data.len() < T::SIZE {
61        return Err(ProgramError::InvalidAccountData);
62    }
63    // SAFETY: T: Pod, all bit patterns valid. read_unaligned handles alignment.
64    Ok(unsafe { core::ptr::read_unaligned(data.as_ptr() as *const T) })
65}
66
67/// Write a Pod value to bytes (alignment-safe).
68#[inline(always)]
69pub fn pod_write<T: Pod + FixedLayout>(data: &mut [u8], value: &T) -> Result<(), ProgramError> {
70    if data.len() < T::SIZE {
71        return Err(ProgramError::InvalidAccountData);
72    }
73    // SAFETY: T: Pod, we checked length, write_unaligned handles alignment.
74    unsafe {
75        core::ptr::write_unaligned(data.as_mut_ptr() as *mut T, *value);
76    }
77    Ok(())
78}
79
80// ---------------------------------------------------------------------------
81// Tier C -- Unchecked Raw Escape Hatch
82// ---------------------------------------------------------------------------
83
84/// Raw unchecked cast from bytes to an immutable reference.
85///
86/// **Tier C escape hatch.** No size check, no header validation, no
87/// fingerprint verification. The caller owns all layout, compatibility,
88/// and upgrade risk.
89///
90/// # Safety
91///
92/// - `data.len()` must be at least `size_of::<T>()`.
93/// - `T` must be `Pod` (all bit patterns valid, alignment-1, `Copy`).
94/// - No concurrent mutable references may alias `data`.
95#[inline(always)]
96pub unsafe fn cast_unchecked<T: Pod>(data: &[u8]) -> &T {
97    // SAFETY: Caller guarantees length and aliasing requirements.
98    unsafe { &*(data.as_ptr() as *const T) }
99}
100
101/// Raw unchecked cast from bytes to a mutable reference.
102///
103/// **Tier C escape hatch.** Same as [`cast_unchecked`] but returns `&mut T`.
104///
105/// # Safety
106///
107/// - `data.len()` must be at least `size_of::<T>()`.
108/// - `T` must be `Pod` (all bit patterns valid, alignment-1, `Copy`).
109/// - No other references (mutable or immutable) may alias `data`.
110#[inline(always)]
111pub unsafe fn cast_unchecked_mut<T: Pod>(data: &mut [u8]) -> &mut T {
112    // SAFETY: Caller guarantees length, aliasing, and exclusive access.
113    unsafe { &mut *(data.as_mut_ptr() as *mut T) }
114}