hopper_native/pod.rs
1//! Substrate-level `Pod` marker.
2//!
3//! The Hopper Safety Audit asked for every zero-copy access path -
4//! all the way down to the native substrate, to require a real Pod
5//! bound rather than the loose `T: Copy`. This module is that marker.
6//!
7//! ## Bytemuck-backed safety (default)
8//!
9//! With the `bytemuck` feature enabled (default), `Pod` is declared
10//! as a **sub-trait** of `bytemuck::Pod + bytemuck::Zeroable`. That
11//! raises the bar exactly the way the audit recommends: every
12//! `unsafe impl Pod for T {}` must be accompanied by a
13//! `#[derive(bytemuck::Pod, bytemuck::Zeroable)]` (or hand-written
14//! impls that satisfy bytemuck's machine-checked obligations).
15//! Bytemuck's derive emits a compile-time proof that **every field**
16//! of `T` is itself `Pod`, which mechanically rejects:
17//!
18//! - `bool`, `char`, references, not all bit patterns valid
19//! - padded `#[repr(C)]` structs, padding bytes aren't accounted for
20//! - non-alignment-1 primitives when alignment-1 was claimed
21//! - enums with niches and non-zero variants
22//!
23//! This is the **Must-Fix #5** the audit flagged: "enforce field-level
24//! Pod proof at macro expansion time". Hopper's `#[hopper::pod]` and
25//! `#[hopper::state]` macros now emit the `#[derive(…)]` automatically
26//! so users never see the bytemuck name in their own sources.
27//!
28//! ## Disable-able for zero-dep builds
29//!
30//! Programs that want to avoid any external dependency can turn off
31//! the `bytemuck` feature. In that mode `Pod` is a standalone marker
32//! with the documented four-point contract; the compile-time
33//! obligation falls entirely on the `unsafe impl`. Existing primitive
34//! impls continue to work either way.
35//!
36//! See [`hopper_runtime::pod::Pod`] (downstream re-export) for the
37//! runtime-side view.
38
39/// Marker for types that can be safely overlaid on raw account bytes.
40///
41/// # Safety
42///
43/// Implementing `Pod` for a type `T` asserts all of:
44///
45/// 1. Every `[u8; size_of::<T>()]` bit pattern decodes to a valid `T`.
46/// 2. `align_of::<T>() == 1`.
47/// 3. `T` contains no padding.
48/// 4. `T` contains no internal pointers or references.
49///
50/// With `feature = "bytemuck"` on (default), the trait is sealed so
51/// callers must **also** prove `T: bytemuck::Pod + bytemuck::Zeroable`,
52/// which gets them obligations 1, 3, and 4 mechanically via bytemuck's
53/// derive. Obligation 2 (alignment) is still a Hopper-specific
54/// constraint enforced by the `#[hopper::pod]` / `#[hopper::state]`
55/// compile-time asserts.
56#[cfg(feature = "bytemuck")]
57pub unsafe trait Pod: Copy + Sized + bytemuck::Pod + bytemuck::Zeroable {}
58
59/// Marker for types that can be safely overlaid on raw account bytes.
60///
61/// `bytemuck` feature disabled: the four-point contract must be
62/// satisfied by the `unsafe impl` alone.
63#[cfg(not(feature = "bytemuck"))]
64pub unsafe trait Pod: Copy + Sized {}
65
66// ── Primitive implementations ───────────────────────────────────────
67//
68// Both feature configurations get the same set of blanket impls.
69// With `bytemuck` on these compile because bytemuck also has blanket
70// impls for the same primitive types.
71
72unsafe impl Pod for u8 {}
73unsafe impl Pod for u16 {}
74unsafe impl Pod for u32 {}
75unsafe impl Pod for u64 {}
76unsafe impl Pod for u128 {}
77unsafe impl Pod for i8 {}
78unsafe impl Pod for i16 {}
79unsafe impl Pod for i32 {}
80unsafe impl Pod for i64 {}
81unsafe impl Pod for i128 {}
82unsafe impl<const N: usize> Pod for [u8; N] {}
83unsafe impl Pod for () {}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 fn require<T: Pod>() {}
90
91 #[test]
92 fn primitives_are_pod() {
93 require::<u8>();
94 require::<u64>();
95 require::<i128>();
96 require::<[u8; 32]>();
97 }
98
99 /// Demonstrates that `bool`, `Copy + Sized` but not all bit
100 /// patterns valid, is **not** `Pod` under Hopper's contract.
101 /// With the `bytemuck` feature on this is enforced mechanically
102 /// because `bool` isn't `bytemuck::Pod`. Without the feature the
103 /// trait is a plain marker and the rejection relies on the user
104 /// not writing `unsafe impl Pod for bool`.
105 #[test]
106 fn bool_is_not_pod() {
107 trait NotPod {}
108 impl<T> NotPod for T {}
109 trait IsPod {}
110 impl<T: Pod> IsPod for T {}
111 // Compiles, bool has `NotPod` blanket impl.
112 fn _f<T: NotPod>() {}
113 _f::<bool>();
114 }
115}