Skip to main content

hopper_runtime/
segment.rs

1//! Runtime-local segment primitive.
2//!
3//! `Segment` stores an absolute byte offset and size in two `u32` fields. It is
4//! `Copy`, const-constructable, and carries no field name. The corresponding
5//! `hopper_core::segment_map::StaticSegment` adds a name for schema and tooling
6//! use.
7//!
8//! # Design
9//!
10//! Generated accessors can embed a `Segment` constant next to their layout
11//! metadata:
12//!
13//! - macros emit `const BALANCE: Segment = Segment::body(0, 8);`
14//! - call sites read `account.segment_mut_const::<u64>(&mut b, BALANCE)?`
15//! - the compiler can propagate the constant through bounds checks and pointer
16//!   arithmetic.
17//!
18//! `Segment` never appears in an on-chain layout, it is a compile-time
19//! description only. Use `hopper_core::account::SegmentDescriptor` for
20//! bytes that travel on the wire.
21
22use crate::layout::HopperHeader;
23
24/// Compile-time descriptor of a typed byte range inside an account.
25///
26/// The `u32` fields match Hopper's segment metadata encoding and keep the
27/// descriptor eight bytes wide.
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29#[repr(C)]
30pub struct Segment {
31    /// Absolute byte offset from the start of account data (includes
32    /// the 16-byte Hopper header). This is what the access primitives
33    /// want, so storing it absolute avoids a runtime addition.
34    pub offset: u32,
35    /// Byte size of the segment.
36    pub size: u32,
37}
38
39impl Segment {
40    /// Construct a segment from an absolute offset (measured from the
41    /// start of account data, including the Hopper header).
42    #[inline(always)]
43    pub const fn new(offset: u32, size: u32) -> Self {
44        Self { offset, size }
45    }
46
47    /// Construct a segment from a body-relative offset (offset measured
48    /// past the 16-byte Hopper header). This is the form that macros
49    /// most often emit: `#[hopper::state]` computes field offsets
50    /// relative to the struct body, and body-relative is what
51    /// `SegmentMap::SEGMENTS` stores.
52    #[inline(always)]
53    pub const fn body(body_offset: u32, size: u32) -> Self {
54        Self {
55            offset: HopperHeader::SIZE as u32 + body_offset,
56            size,
57        }
58    }
59
60    /// One-past-the-end byte offset, widened to `u64` so `offset + size`
61    /// cannot wrap before containment and overlap comparisons.
62    #[inline(always)]
63    pub const fn end(&self) -> u64 {
64        self.offset as u64 + self.size as u64
65    }
66
67    /// Whether two segments share any byte.
68    #[inline(always)]
69    pub const fn overlaps(&self, other: &Segment) -> bool {
70        (self.offset as u64) < other.end() && (other.offset as u64) < self.end()
71    }
72
73    /// Whether this segment is contained fully within `container`.
74    #[inline(always)]
75    pub const fn contained_in(&self, container: &Segment) -> bool {
76        self.offset >= container.offset && self.end() <= container.end()
77    }
78}
79
80// ══════════════════════════════════════════════════════════════════════
81//  TypedSegment<T, const OFFSET: u32>
82// ══════════════════════════════════════════════════════════════════════
83//
84// Where `Segment` carries `(offset, size)` at runtime, `TypedSegment`
85// folds **both** values into the type system: `T` determines the size
86// via `size_of::<T>()`, and `OFFSET` is a const generic. The struct
87// itself is a ZST, no memory at all. This is the runtime's
88// const-generic segment implementation: at every
89// call site the compiler substitutes the literal offset and literal
90// size into the bounds check + pointer add, leaving pure
91// `ptr + constant` arithmetic in the emitted BPF.
92//
93// Use `TypedSegment` when you know the layout at compile time (i.e.
94// every `#[hopper::state]` field). Fall back to `Segment` when the
95// offset is data-dependent (e.g. a user-provided index into a fixed
96// array).
97
98/// Compile-time typed segment descriptor: `T` is the overlay type,
99/// `OFFSET` is the absolute byte offset from the start of account
100/// data. Zero-sized.
101///
102/// ```ignore
103/// // Matches Vault.balance at body offset 0, past the 16-byte header:
104/// const VAULT_BALANCE: TypedSegment<WireU64, { HopperHeader::SIZE as u32 }>
105///     = TypedSegment::new();
106///
107/// let bal = account.segment_ref_typed(&mut borrows, VAULT_BALANCE)?;
108/// ```
109#[derive(Copy, Clone, Debug, Default)]
110pub struct TypedSegment<T: crate::Pod, const OFFSET: u32> {
111    _marker: core::marker::PhantomData<fn() -> T>,
112}
113
114impl<T: crate::Pod, const OFFSET: u32> TypedSegment<T, OFFSET> {
115    /// Construct the marker. Runs entirely at compile time.
116    #[inline(always)]
117    pub const fn new() -> Self {
118        Self {
119            _marker: core::marker::PhantomData,
120        }
121    }
122
123    /// The absolute byte offset of this segment (`OFFSET` const-generic).
124    #[inline(always)]
125    pub const fn offset() -> u32 {
126        OFFSET
127    }
128
129    /// The byte size of this segment (`size_of::<T>()`, folded at compile time).
130    #[inline(always)]
131    pub const fn size() -> u32 {
132        core::mem::size_of::<T>() as u32
133    }
134
135    /// One-past-the-end byte offset, widened like [`Segment::end`].
136    #[inline(always)]
137    pub const fn end() -> u64 {
138        OFFSET as u64 + core::mem::size_of::<T>() as u64
139    }
140
141    /// Lower to a runtime [`Segment`] when a heterogeneous collection
142    /// of segments is needed (e.g. a validation pass that iterates).
143    #[inline(always)]
144    pub const fn as_segment() -> Segment {
145        Segment::new(OFFSET, core::mem::size_of::<T>() as u32)
146    }
147}
148
149// SAFETY: Proof that `TypedSegment` really is zero-sized.
150const _: () = {
151    assert!(
152        core::mem::size_of::<TypedSegment<[u8; 8], 0>>() == 0,
153        "TypedSegment must be zero-sized so it costs nothing to pass around",
154    );
155};
156
157/// Generic field role for account field capability descriptors.
158pub const FIELD_ROLE_DATA: u8 = 0;
159/// Field carries authority or signer identity semantics.
160pub const FIELD_ROLE_AUTHORITY: u8 = 1;
161/// Field carries accounting or balance semantics.
162pub const FIELD_ROLE_BALANCE: u8 = 2;
163/// Field carries migration/versioning semantics.
164pub const FIELD_ROLE_VERSION: u8 = 3;
165/// Field is intended to become immutable after initialization.
166pub const FIELD_POLICY_IMMUTABLE_AFTER_INIT: u8 = 1 << 0;
167/// Field mutations should use checked arithmetic.
168pub const FIELD_POLICY_CHECKED_MATH: u8 = 1 << 1;
169/// Field mutations should be gated by an admin or authority proof.
170pub const FIELD_POLICY_AUTHORITY_GATED: u8 = 1 << 2;
171
172/// Zero-sized field capability: type, byte offset, semantic role, and policy.
173///
174/// This is the runtime half of proof-carrying field access. Macros and tooling
175/// can emit these ZSTs for each account field, then require a matching
176/// capability in higher-level mutation helpers without storing extra metadata
177/// in the account body.
178#[derive(Copy, Clone, Debug, Default)]
179pub struct FieldCapability<T: crate::Pod, const OFFSET: u32, const ROLE: u8, const POLICY: u8> {
180    _marker: core::marker::PhantomData<fn() -> T>,
181}
182
183impl<T: crate::Pod, const OFFSET: u32, const ROLE: u8, const POLICY: u8>
184    FieldCapability<T, OFFSET, ROLE, POLICY>
185{
186    #[inline(always)]
187    pub const fn new() -> Self {
188        Self {
189            _marker: core::marker::PhantomData,
190        }
191    }
192
193    #[inline(always)]
194    pub const fn role() -> u8 {
195        ROLE
196    }
197
198    #[inline(always)]
199    pub const fn policy() -> u8 {
200        POLICY
201    }
202
203    #[inline(always)]
204    pub const fn typed_segment() -> TypedSegment<T, OFFSET> {
205        TypedSegment::new()
206    }
207
208    #[inline(always)]
209    pub const fn as_segment() -> Segment {
210        TypedSegment::<T, OFFSET>::as_segment()
211    }
212
213    #[inline(always)]
214    pub const fn has_policy(flag: u8) -> bool {
215        POLICY & flag != 0
216    }
217}
218
219// SAFETY: Field capabilities must also remain ZSTs.
220const _: () = {
221    assert!(
222        core::mem::size_of::<FieldCapability<[u8; 8], 0, FIELD_ROLE_DATA, 0>>() == 0,
223        "FieldCapability must be zero-sized",
224    );
225};
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    #[test]
232    fn typed_segment_is_zero_sized() {
233        assert_eq!(core::mem::size_of::<TypedSegment<[u8; 8], 16>>(), 0);
234    }
235
236    #[test]
237    fn typed_segment_offset_and_size_fold() {
238        const S: TypedSegment<[u8; 8], 16> = TypedSegment::new();
239        // The values come from the type system directly.
240        assert_eq!(TypedSegment::<[u8; 8], 16>::offset(), 16);
241        assert_eq!(TypedSegment::<[u8; 8], 16>::size(), 8);
242        assert_eq!(TypedSegment::<[u8; 8], 16>::end(), 24);
243        let _ = S; // ensure const ctor works
244    }
245
246    #[test]
247    fn typed_segment_lowers_to_runtime_segment() {
248        const S: Segment = TypedSegment::<[u8; 8], 16>::as_segment();
249        assert_eq!(S.offset, 16);
250        assert_eq!(S.size, 8);
251    }
252
253    #[test]
254    fn body_adds_header() {
255        let s = Segment::body(0, 8);
256        assert_eq!(s.offset, HopperHeader::SIZE as u32);
257        assert_eq!(s.size, 8);
258        assert_eq!(s.end(), HopperHeader::SIZE as u64 + 8);
259    }
260
261    #[test]
262    fn overlaps_detects_shared_bytes() {
263        let a = Segment::new(0, 16);
264        let b = Segment::new(8, 16);
265        let c = Segment::new(16, 16);
266        assert!(a.overlaps(&b));
267        assert!(!a.overlaps(&c)); // adjacent, no shared bytes
268        assert!(b.overlaps(&c));
269    }
270
271    #[test]
272    fn contained_in_reports_proper_nesting() {
273        let outer = Segment::new(0, 32);
274        let inner = Segment::new(8, 8);
275        let equal = Segment::new(0, 32);
276        let escape = Segment::new(24, 16);
277        assert!(inner.contained_in(&outer));
278        assert!(equal.contained_in(&outer));
279        assert!(!escape.contained_in(&outer));
280    }
281
282    #[test]
283    fn end_arithmetic_does_not_wrap_at_u32_max() {
284        // Pre-fix, `end()` wrapped: offset u32::MAX + size 2 → end 1,
285        // making this segment falsely "contained" in a small container
286        // and falsely non-overlapping with its own bytes.
287        let huge = Segment::new(u32::MAX, 2);
288        let container = Segment::new(0, 32);
289        assert_eq!(huge.end(), u32::MAX as u64 + 2);
290        assert!(!huge.contained_in(&container));
291        assert!(!huge.overlaps(&container));
292        // And it does overlap a range that genuinely reaches it.
293        let touching = Segment::new(u32::MAX, 1);
294        assert!(huge.overlaps(&touching));
295    }
296}