Skip to main content

hopper_runtime/
pod.rs

1//! `Pod`, the canonical runtime-layer "safe to interpret from raw bytes" marker.
2//!
3//! Hopper's typed access primitives (`segment_ref`, `segment_mut`,
4//! `raw_ref`, `raw_mut`, `read_data`) all overlay a `T` on a slice of
5//! account bytes. That overlay is only sound if **every bit pattern of
6//! the right size** decodes to a valid `T` and the type has alignment 1
7//! (so the offset within the BPF input buffer is always valid for `T`).
8//!
9//! Requiring only `T: Copy` is too loose: `bool`, `char`, references,
10//! and structs with padding are all
11//! `Copy + Sized` but **not** safe to overlay on raw bytes. This module
12//! carries the tightened marker.
13//!
14//! ## Contract
15//!
16//! Implementing `Pod` for a type `T` asserts all of:
17//!
18//! 1. Every `[u8; size_of::<T>()]` byte pattern represents a valid `T`.
19//!    No "niches", no enum-discriminant invariants, no `bool`-style
20//!    forbidden bit patterns.
21//! 2. `align_of::<T>() == 1`, the type can be read from any byte
22//!    offset of an account buffer without alignment fault.
23//! 3. `T` contains no padding (`#[repr(C)]` with alignment-1 fields, or
24//!    `#[repr(transparent)]` over a `Pod` type).
25//! 4. `T` contains no internal pointers / references, overlay always
26//!    yields data that's safe to `Copy`.
27//!
28//! Hopper's higher-layer macros (`#[hopper::state]`, `#[hopper::pod]`,
29//! `hopper_layout!`) enforce these conditions at compile time and emit
30//! the derived `unsafe impl Pod`. Hand-authored layouts opt in via
31//! `unsafe impl Pod for MyLayout {}`.
32//!
33//! ## Compile-fail demonstration
34//!
35//! The following misuse patterns are rejected at compile time. Hopper's
36//! `Pod + Zeroable` proof layer enforces field-level validity during macro
37//! expansion, so every zero-copy access path rejects them automatically.
38//!
39//! `bool` is not Pod (the bit patterns `0x02..=0xFF` don't decode to
40//! a valid `bool`):
41//!
42//! ```compile_fail
43//! # use hopper_runtime::{AccountView, segment_borrow::SegmentBorrowRegistry};
44//! # fn example(account: &AccountView, borrows: &mut SegmentBorrowRegistry) {
45//! let _ = account.segment_ref::<bool>(borrows, 16, 1);
46//! # }
47//! ```
48//!
49//! `char` is not Pod (valid Unicode scalar values form a sparse set):
50//!
51//! ```compile_fail
52//! # use hopper_runtime::{AccountView, segment_borrow::SegmentBorrowRegistry};
53//! # fn example(account: &AccountView, borrows: &mut SegmentBorrowRegistry) {
54//! let _ = account.segment_ref::<char>(borrows, 16, 4);
55//! # }
56//! ```
57//!
58//! A `#[repr(C)]` struct with implicit padding is not Pod because the
59//! padding bytes would be part of the raw overlay contract:
60//!
61//! ```compile_fail
62//! # use hopper_runtime::{AccountView, segment_borrow::SegmentBorrowRegistry};
63//! # fn example(account: &AccountView, borrows: &mut SegmentBorrowRegistry) {
64//! #[derive(Copy, Clone)]
65//! #[repr(C)]
66//! struct Padded {
67//!     a: u8,
68//!     // implicit 7 bytes of padding to align b
69//!     b: u64,
70//! }
71//! let _ = account.segment_ref::<Padded>(borrows, 16, 16);
72//! # }
73//! ```
74//!
75//! A type-level user mis-spelling `unsafe impl Pod for Padded {}` can
76//! still opt into an unsafe contract manually, but Hopper's macro entry
77//! points reject that layout before emitting the impl.
78//!
79//! A well-formed primitive or wire type is accepted:
80//!
81//! ```ignore
82//! # use hopper_runtime::{AccountView, segment_borrow::SegmentBorrowRegistry};
83//! # fn example(account: &AccountView, borrows: &mut SegmentBorrowRegistry) {
84//! let _: Result<hopper_runtime::SegRef<'_, [u8; 8]>, _> =
85//!     account.segment_ref::<[u8; 8]>(borrows, 16, 8);
86//! # }
87//! ```
88//!
89//! ## Trait identity across layers
90//!
91//! Hopper re-exports [`hopper_native::Pod`] as the single Pod trait for the
92//! stack. One `unsafe impl Pod for MyStruct {}` unlocks every Hopper access API
93//! from the lowest-level `AccountView::raw_mut` up to `#[hopper::state]`-
94//! generated accessors, across all crates, with no orphan-rule gymnastics.
95
96// Re-export `hopper_native::Pod` directly so the "one canonical Pod"
97// invariant holds end-to-end.
98pub use hopper_native::{read_unaligned_value, Pod, ValuePod, Zeroable};
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    fn assert_pod<T: Pod>() {}
105
106    #[test]
107    fn primitives_are_pod() {
108        assert_pod::<u8>();
109        assert_pod::<i8>();
110        assert_pod::<[u8; 32]>();
111    }
112
113    #[test]
114    fn address_satisfies_pod() {
115        // `Address` is declared `#[repr(transparent)] [u8; 32]` with a
116        // hand-rolled `unsafe impl Pod`. Under the native backend that
117        // impl is on `hopper_native::Pod`; here we're just checking the
118        // re-export plumbing lands.
119        assert_pod::<crate::address::Address>();
120    }
121}