Skip to main content

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}