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#[diagnostic::on_unimplemented(
27 message = "`{Self}` cannot be stored in a zero-copy Hopper layout",
28 label = "not `Zeroable`: this type has no all-zero, copyable byte form",
29 note = "layout fields are alignment-1 byte types: `u8`, `i8`, `[T; N]`, `Address`, the wire integers (`WireU16` to `WireU128`, `WireI16` to `WireI128`), `WireBool`, `EnumByte<E>`, `OptionByte<T>`, and other `#[hopper::state]` / `#[hopper::pod]` structs"
30)]
31/// Marker for `Copy + Sized` values that are valid for every bit pattern.
32///
33/// # Safety
34///
35/// This is the **by-value** contract: a `Zeroable` value can be produced
36/// by copying `size_of::<T>()` arbitrary bytes (e.g. a zero fill, or an
37/// unaligned `read_unaligned`). It says **nothing** about alignment, so
38/// it holds for native multi-byte integers as well. To overlay a type as
39/// `&T` / `&mut T` directly on account bytes, which requires
40/// alignment 1, use [`Pod`] (and, at the framework level,
41/// `hopper_runtime::ZeroCopy`).
42pub unsafe trait Zeroable: Copy + Sized {}
43
44#[diagnostic::on_unimplemented(
45 message = "`{Self}` cannot be overlaid on account bytes",
46 label = "not `Pod`: some byte pattern or byte offset is invalid for this type",
47 note = "use the alignment-1 wire form instead: `WireU64` for `u64` (and `WireU16`, `WireU32`, `WireU128`, `WireI16` to `WireI128`), `WireBool` for `bool`, `EnumByte<E>` for a `#[hopper::unit_enum]` enum, `OptionByte<T>` for an optional value, `Address` for a key",
48 note = "a nested struct must itself be declared with `#[hopper::state]` or `#[hopper::pod]`; references, `Vec`, `String`, and `char` have no zero-copy form (see bounded `String<'a, N>` / `Vec<'a, T, N>` tail fields)"
49)]
50/// Marker for types that can be safely overlaid as `&T` / `&mut T` on raw
51/// account bytes at **any** offset.
52///
53/// # Safety
54///
55/// Implementing `Pod` for a type `T` asserts all of:
56///
57/// 1. Every `[u8; size_of::<T>()]` bit pattern decodes to a valid `T`.
58/// 2. `align_of::<T>() == 1`, so a reference can be formed at any byte
59/// offset without an unaligned-reference (which is UB).
60/// 3. `T` contains no padding.
61/// 4. `T` contains no internal pointers or references.
62///
63/// Native multi-byte integers (`u16`, `u32`, `u64`, `u128`, `i16`…`i128`)
64/// are deliberately **not** `Pod`: their alignment is greater than 1, so
65/// forming `&u64` from an arbitrary account offset is undefined behaviour.
66/// Use the alignment-1 wire types (`WireU64`, `WireI64`, …) in layouts,
67/// and [`ValuePod`] + [`read_unaligned_value`] for by-value scalar reads.
68///
69/// Hopper macros mechanically enforce the field-level proof before
70/// emitting this impl. Hand-written impls carry the same unsafe contract.
71pub unsafe trait Pod: Zeroable {}
72
73/// Marker for `Copy + Sized` scalars/arrays that may be read **by value**
74/// from raw bytes with [`read_unaligned_value`] (alignment-independent).
75///
76/// # Safety
77///
78/// Unlike [`Pod`], `ValuePod` does not permit forming a `&T` overlay, so
79/// it is safe to implement for native multi-byte integers. Use it for
80/// instruction-argument decoding and local scalar loads where the value
81/// is copied out, not referenced in place. Implementers assert every
82/// `[u8; size_of::<T>()]` bit pattern decodes to a valid `T`.
83pub unsafe trait ValuePod: Copy + Sized {}
84
85// ── Primitive implementations ───────────────────────────────────────
86//
87// SAFETY: integers, arrays of them, and `()` have no padding, hold no
88// pointers, and accept every bit pattern, all-zero included. `Pod` is
89// implemented for the alignment-1 types only (`u8`, `i8`, arrays of `Pod`,
90// `()`), so a reference overlaid on account bytes is never misaligned.
91//
92// `Zeroable` / `ValuePod`: every native integer is a valid by-value POD.
93// `Pod`: only alignment-1 types (so `&T` overlays are never misaligned).
94unsafe impl Zeroable for u8 {}
95unsafe impl Pod for u8 {}
96unsafe impl Zeroable for u16 {}
97unsafe impl Zeroable for u32 {}
98unsafe impl Zeroable for u64 {}
99unsafe impl Zeroable for u128 {}
100unsafe impl Zeroable for i8 {}
101unsafe impl Pod for i8 {}
102unsafe impl Zeroable for i16 {}
103unsafe impl Zeroable for i32 {}
104unsafe impl Zeroable for i64 {}
105unsafe impl Zeroable for i128 {}
106unsafe impl<T: Zeroable, const N: usize> Zeroable for [T; N] {}
107unsafe impl<T: Pod, const N: usize> Pod for [T; N] {}
108unsafe impl Zeroable for () {}
109unsafe impl Pod for () {}
110
111// SAFETY: `ValuePod` values are only ever copied out of bytes by value
112// (`read_unaligned`), never referenced in place, so alignment does not
113// matter. Every bit pattern of a fixed-width integer is a valid value, the
114// integers have no padding and hold no pointers, and an array of such
115// values inherits all three properties.
116unsafe impl ValuePod for u8 {}
117unsafe impl ValuePod for u16 {}
118unsafe impl ValuePod for u32 {}
119unsafe impl ValuePod for u64 {}
120unsafe impl ValuePod for u128 {}
121unsafe impl ValuePod for i8 {}
122unsafe impl ValuePod for i16 {}
123unsafe impl ValuePod for i32 {}
124unsafe impl ValuePod for i64 {}
125unsafe impl ValuePod for i128 {}
126unsafe impl<T: ValuePod, const N: usize> ValuePod for [T; N] {}
127
128/// Read a `ValuePod` scalar/array out of `bytes` at `offset` by value,
129/// tolerating any alignment (uses `core::ptr::read_unaligned`).
130///
131/// Returns `Err(AccountDataTooSmall)` if the range is out of bounds. This
132/// is the correct path for native multi-byte integers, which must never
133/// be formed as a `&T` reference at an arbitrary offset.
134#[inline]
135pub fn read_unaligned_value<T: ValuePod>(
136 bytes: &[u8],
137 offset: usize,
138) -> Result<T, crate::error::ProgramError> {
139 let end = offset
140 .checked_add(core::mem::size_of::<T>())
141 .ok_or(crate::error::ProgramError::ArithmeticOverflow)?;
142 if end > bytes.len() {
143 return Err(crate::error::ProgramError::AccountDataTooSmall);
144 }
145 // SAFETY: bounds checked above; `read_unaligned` imposes no alignment
146 // requirement and `T: ValuePod` guarantees all bit patterns are valid.
147 Ok(unsafe { core::ptr::read_unaligned(bytes.as_ptr().add(offset) as *const T) })
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 fn require<T: Pod>() {}
155 fn require_value<T: ValuePod>() {}
156
157 #[test]
158 fn primitives_are_pod() {
159 require::<u8>();
160 require::<i8>();
161 require::<[u8; 32]>();
162 }
163
164 #[test]
165 fn multibyte_ints_are_value_pod_not_pod() {
166 // By-value reads are fine for native integers...
167 require_value::<u64>();
168 require_value::<i128>();
169 require_value::<[u32; 4]>();
170 // ...but they are intentionally NOT `Pod` (alignment > 1), so the
171 // overlay APIs that bound on `Pod` reject them at compile time.
172
173 // Aligned and unaligned by-value reads both work.
174 let bytes = [1u8, 0, 0, 0, 0, 0, 0, 0, 7, 0];
175 let v0: u64 = read_unaligned_value(&bytes, 0).unwrap();
176 assert_eq!(v0, 1); // bytes[0..8] LE = 1
177 let v1: u64 = read_unaligned_value(&bytes, 1).unwrap();
178 assert_eq!(v1, 7 << 56); // bytes[1..9] LE = [0,0,0,0,0,0,0,7]
179 // Out-of-bounds is a clean error, never UB.
180 assert!(read_unaligned_value::<u64>(&bytes, 5).is_err());
181 }
182
183 /// Demonstrates that `bool`, `Copy + Sized` but not all bit
184 /// patterns valid, is **not** `Pod` under Hopper's contract.
185 /// This relies on Hopper not providing a primitive impl for bool;
186 /// Hopper macros also reject bool fields because every field must
187 /// already satisfy Hopper `Pod`.
188 #[test]
189 fn bool_is_not_pod() {
190 trait NotPod {}
191 impl<T> NotPod for T {}
192 // Compiles, bool has `NotPod` blanket impl.
193 fn _f<T: NotPod>() {}
194 _f::<bool>();
195 }
196}