hopper_native/pod.rs
1//! Substrate-level `Pod` marker.
2//!
3//! Every zero-copy access path, including the native substrate, requires a Pod
4//! bound rather than the loose `T: Copy`. This module is that marker.
5//!
6//! ## Hopper-owned safety
7//!
8//! `Zeroable` and `Pod` are Hopper-owned marker traits. Hopper macros
9//! emit field-level proof blocks that require every field to already
10//! implement Hopper `Pod` before the containing layout receives its own
11//! impl. That gives the same useful rejection points users got from the
12//! old dependency-backed path while keeping the proof surface inside the
13//! framework:
14//!
15//! - `bool`, `char`, references, not all bit patterns valid
16//! - padded `#[repr(C)]` structs, padding bytes aren't accounted for
17//! - non-alignment-1 primitives when alignment-1 was claimed
18//! - enums with niches and non-zero variants
19//!
20//! Hopper's `#[hopper::pod]` derive and `#[hopper::state]` macro emit these
21//! field-level proofs, so layouts can use the Hopper-owned marker directly.
22//!
23//! See `hopper_runtime::pod::Pod` (downstream re-export) for the
24//! runtime-side view.
25
26/// Marker for `Copy + Sized` values that are valid for every bit pattern.
27///
28/// # Safety
29///
30/// This is the **by-value** contract: a `Zeroable` value can be produced
31/// by copying `size_of::<T>()` arbitrary bytes (e.g. a zero fill, or an
32/// unaligned `read_unaligned`). It says **nothing** about alignment, so
33/// it holds for native multi-byte integers as well. To overlay a type as
34/// `&T` / `&mut T` directly on account bytes, which requires
35/// alignment 1, use [`Pod`] (and, at the framework level,
36/// `hopper_runtime::ZeroCopy`).
37pub unsafe trait Zeroable: Copy + Sized {}
38
39/// Marker for types that can be safely overlaid as `&T` / `&mut T` on raw
40/// account bytes at **any** offset.
41///
42/// # Safety
43///
44/// Implementing `Pod` for a type `T` asserts all of:
45///
46/// 1. Every `[u8; size_of::<T>()]` bit pattern decodes to a valid `T`.
47/// 2. `align_of::<T>() == 1`, so a reference can be formed at any byte
48/// offset without an unaligned-reference (which is UB).
49/// 3. `T` contains no padding.
50/// 4. `T` contains no internal pointers or references.
51///
52/// Native multi-byte integers (`u16`, `u32`, `u64`, `u128`, `i16`…`i128`)
53/// are deliberately **not** `Pod`: their alignment is greater than 1, so
54/// forming `&u64` from an arbitrary account offset is undefined behaviour.
55/// Use the alignment-1 wire types (`WireU64`, `WireI64`, …) in layouts,
56/// and [`ValuePod`] + [`read_unaligned_value`] for by-value scalar reads.
57///
58/// Hopper macros mechanically enforce the field-level proof before
59/// emitting this impl. Hand-written impls carry the same unsafe contract.
60pub unsafe trait Pod: Zeroable {}
61
62/// Marker for `Copy + Sized` scalars/arrays that may be read **by value**
63/// from raw bytes with [`read_unaligned_value`] (alignment-independent).
64///
65/// # Safety
66///
67/// Unlike [`Pod`], `ValuePod` does not permit forming a `&T` overlay, so
68/// it is safe to implement for native multi-byte integers. Use it for
69/// instruction-argument decoding and local scalar loads where the value
70/// is copied out, not referenced in place. Implementers assert every
71/// `[u8; size_of::<T>()]` bit pattern decodes to a valid `T`.
72pub unsafe trait ValuePod: Copy + Sized {}
73
74// ── Primitive implementations ───────────────────────────────────────
75//
76// `Zeroable` / `ValuePod`: every native integer is a valid by-value POD.
77// `Pod`: only alignment-1 types (so `&T` overlays are never misaligned).
78unsafe impl Zeroable for u8 {}
79unsafe impl Pod for u8 {}
80unsafe impl Zeroable for u16 {}
81unsafe impl Zeroable for u32 {}
82unsafe impl Zeroable for u64 {}
83unsafe impl Zeroable for u128 {}
84unsafe impl Zeroable for i8 {}
85unsafe impl Pod for i8 {}
86unsafe impl Zeroable for i16 {}
87unsafe impl Zeroable for i32 {}
88unsafe impl Zeroable for i64 {}
89unsafe impl Zeroable for i128 {}
90unsafe impl<T: Zeroable, const N: usize> Zeroable for [T; N] {}
91unsafe impl<T: Pod, const N: usize> Pod for [T; N] {}
92unsafe impl Zeroable for () {}
93unsafe impl Pod for () {}
94
95unsafe impl ValuePod for u8 {}
96unsafe impl ValuePod for u16 {}
97unsafe impl ValuePod for u32 {}
98unsafe impl ValuePod for u64 {}
99unsafe impl ValuePod for u128 {}
100unsafe impl ValuePod for i8 {}
101unsafe impl ValuePod for i16 {}
102unsafe impl ValuePod for i32 {}
103unsafe impl ValuePod for i64 {}
104unsafe impl ValuePod for i128 {}
105unsafe impl<T: ValuePod, const N: usize> ValuePod for [T; N] {}
106
107/// Read a `ValuePod` scalar/array out of `bytes` at `offset` by value,
108/// tolerating any alignment (uses `core::ptr::read_unaligned`).
109///
110/// Returns `Err(AccountDataTooSmall)` if the range is out of bounds. This
111/// is the correct path for native multi-byte integers, which must never
112/// be formed as a `&T` reference at an arbitrary offset.
113#[inline]
114pub fn read_unaligned_value<T: ValuePod>(
115 bytes: &[u8],
116 offset: usize,
117) -> Result<T, crate::error::ProgramError> {
118 let end = offset
119 .checked_add(core::mem::size_of::<T>())
120 .ok_or(crate::error::ProgramError::ArithmeticOverflow)?;
121 if end > bytes.len() {
122 return Err(crate::error::ProgramError::AccountDataTooSmall);
123 }
124 // SAFETY: bounds checked above; `read_unaligned` imposes no alignment
125 // requirement and `T: ValuePod` guarantees all bit patterns are valid.
126 Ok(unsafe { core::ptr::read_unaligned(bytes.as_ptr().add(offset) as *const T) })
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 fn require<T: Pod>() {}
134 fn require_value<T: ValuePod>() {}
135
136 #[test]
137 fn primitives_are_pod() {
138 require::<u8>();
139 require::<i8>();
140 require::<[u8; 32]>();
141 }
142
143 #[test]
144 fn multibyte_ints_are_value_pod_not_pod() {
145 // By-value reads are fine for native integers...
146 require_value::<u64>();
147 require_value::<i128>();
148 require_value::<[u32; 4]>();
149 // ...but they are intentionally NOT `Pod` (alignment > 1), so the
150 // overlay APIs that bound on `Pod` reject them at compile time.
151
152 // Aligned and unaligned by-value reads both work.
153 let bytes = [1u8, 0, 0, 0, 0, 0, 0, 0, 7, 0];
154 let v0: u64 = read_unaligned_value(&bytes, 0).unwrap();
155 assert_eq!(v0, 1); // bytes[0..8] LE = 1
156 let v1: u64 = read_unaligned_value(&bytes, 1).unwrap();
157 assert_eq!(v1, 7 << 56); // bytes[1..9] LE = [0,0,0,0,0,0,0,7]
158 // Out-of-bounds is a clean error, never UB.
159 assert!(read_unaligned_value::<u64>(&bytes, 5).is_err());
160 }
161
162 /// Demonstrates that `bool`, `Copy + Sized` but not all bit
163 /// patterns valid, is **not** `Pod` under Hopper's contract.
164 /// This relies on Hopper not providing a primitive impl for bool;
165 /// Hopper macros also reject bool fields because every field must
166 /// already satisfy Hopper `Pod`.
167 #[test]
168 fn bool_is_not_pod() {
169 trait NotPod {}
170 impl<T> NotPod for T {}
171 // Compiles, bool has `NotPod` blanket impl.
172 fn _f<T: NotPod>() {}
173 _f::<bool>();
174 }
175}