Skip to main content

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}