hopper-core 0.1.0

Core engine for the Hopper zero-copy state framework. Account memory architecture, ABI types, validation graphs, phased execution, zero-copy collections, layout evolution, and cross-program interfaces.
Documentation
//! Compile-time segment mapping for zero-copy account layouts.
//!
//! `SegmentMap` provides a static description of the byte regions within a
//! struct layout. This is separate from
//! [`SegmentRegistry`](crate::account::SegmentRegistry) (which manages
//! on-chain *dynamic* segment metadata, written into the account body
//! at runtime). `SegmentMap` is compile-time knowledge that proc macros
//! (or hand-written impls) generate from struct definitions.
//!
//! ## When to use which
//!
//! - **`SegmentMap` + [`StaticSegment`]** - fixed-layout structs annotated
//!   with `#[hopper::state]`. The segment list is known at compile time;
//!   lookups fold to const loads.
//! - **[`SegmentRegistry`](crate::account::SegmentRegistry) +
//!   [`SegmentDescriptor`](crate::account::SegmentDescriptor)** - accounts
//!   whose segment count grows on-chain (extension-heavy patterns,
//!   Token-2022-style mints, dynamic plug-ins). The segment table lives
//!   inside the account body and is walked at runtime.
//!
//! Most Hopper programs use the compile-time path. The runtime registry
//! exists for genuinely dynamic layouts.
//!
//! ## Design Philosophy
//!
//! Segment offsets are evaluated at compile time and stored as constants.
//! This means segment lookups compile down to a const load, no string
//! matching, no branching, no heap. The generated code is the same shape
//! as raw Pinocchio pointer arithmetic.
//!
//! ## Example
//!
//! ```ignore
//! // Generated by #[hopper::state] or hand-written:
//! impl SegmentMap for Vault {
//!     const SEGMENTS: &'static [StaticSegment] = &[
//!         StaticSegment::new("authority", 0, 32),
//!         StaticSegment::new("balance", 32, 8),
//!         StaticSegment::new("bump", 40, 1),
//!     ];
//! }
//!
//! // Look up a segment:
//! let seg = Vault::segment("balance").unwrap();
//! assert_eq!(seg.offset, 32);
//! assert_eq!(seg.size, 8);
//! ```

/// A compile-time constant segment descriptor.
///
/// Describes a named byte region within an account's data area.
/// All fields are const-evaluable.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct StaticSegment {
    /// Field name (compile-time string).
    pub name: &'static str,
    /// Byte offset from the start of the layout body (after header).
    pub offset: u32,
    /// Byte size of this segment.
    pub size: u32,
}

impl StaticSegment {
    /// Construct a new static segment descriptor.
    #[inline(always)]
    pub const fn new(name: &'static str, offset: u32, size: u32) -> Self {
        Self { name, offset, size }
    }

    /// Byte offset past the end of this segment.
    #[inline(always)]
    pub const fn end(&self) -> u32 {
        self.offset + self.size
    }
}

/// Compile-time segment layout for a zero-copy struct.
///
/// Types implementing this trait declare their byte-level field map
/// as a constant array. This enables segment-level access with
/// zero runtime overhead, the compiler can resolve segment offsets
/// to immediate constants in the generated code.
///
/// ## Runtime vs Compile-Time
///
/// - `SegmentMap` = compile-time, generated from struct definitions
/// - `SegmentRegistry` = runtime, stored on-chain for dynamic accounts
///
/// Both can coexist: a struct might implement `SegmentMap` for its fixed
/// fields while also using `SegmentRegistry` for its dynamic segments.
pub trait SegmentMap {
    /// All segments in this layout, ordered by offset.
    const SEGMENTS: &'static [StaticSegment];

    /// Look up a segment by field name.
    ///
    /// Linear scan over const array. For layouts with ≤16 fields (the norm),
    /// this compiles to a small branchless sequence.
    #[inline]
    fn segment(name: &str) -> Option<StaticSegment> {
        let mut i = 0;
        while i < Self::SEGMENTS.len() {
            let seg = Self::SEGMENTS[i];
            if const_str_eq(seg.name, name) {
                return Some(seg);
            }
            i += 1;
        }
        None
    }

    /// Total size of the layout body (sum of all segments, or max(end)).
    #[inline]
    fn body_size() -> u32 {
        let segments = Self::SEGMENTS;
        if segments.is_empty() {
            return 0;
        }
        // Last segment end offset (assuming sorted by offset).
        let last = segments[segments.len() - 1];
        last.offset + last.size
    }

    /// Number of segments.
    #[inline(always)]
    fn segment_count() -> usize {
        Self::SEGMENTS.len()
    }

    /// Look up a segment by index (zero-overhead const access).
    ///
    /// Prefer this over `segment()` in generated accessor code where the
    /// index is a compile-time constant. The compiler will resolve this to
    /// an immediate constant load, no branching, no string comparison.
    #[inline(always)]
    fn segment_by_index(index: usize) -> StaticSegment {
        Self::SEGMENTS[index]
    }
}

/// Constant string equality for segment name matching.
///
/// Works in const contexts. Used by the default `SegmentMap::segment()`.
#[inline(always)]
const fn const_str_eq(a: &str, b: &str) -> bool {
    let a = a.as_bytes();
    let b = b.as_bytes();
    if a.len() != b.len() {
        return false;
    }
    let mut i = 0;
    while i < a.len() {
        if a[i] != b[i] {
            return false;
        }
        i += 1;
    }
    true
}

// ══════════════════════════════════════════════════════════════════════
//  FieldMap ↔ SegmentMap alignment assertion
// ══════════════════════════════════════════════════════════════════════

/// Compile-time assertion that a type's `SegmentMap` and `FieldMap` are
/// isomorphic: same count, same names, same offsets, same sizes.
///
/// Generated macros should produce both from a single source; this
/// catches any accidental divergence.
///
/// Usage:
/// ```ignore
/// const _: () = assert_segment_field_alignment::<MyLayout>(HEADER_LEN);
/// ```
///
/// `header_offset` is subtracted from `FieldInfo.offset` before comparing
/// with `StaticSegment.offset`, since `FieldInfo` offsets are absolute
/// (including the Hopper header) while `StaticSegment` offsets are relative
/// to the body start.
pub const fn assert_segment_field_alignment<T: SegmentMap + hopper_runtime::field_map::FieldMap>(
    header_offset: usize,
) {
    let segments = T::SEGMENTS;
    let fields = T::FIELDS;
    assert!(
        segments.len() == fields.len(),
        "SegmentMap and FieldMap have different field counts"
    );
    let mut i = 0;
    while i < segments.len() {
        let seg = &segments[i];
        let field = &fields[i];
        assert!(
            seg.offset as usize == field.offset.wrapping_sub(header_offset),
            "SegmentMap/FieldMap offset mismatch"
        );
        assert!(
            seg.size as usize == field.size,
            "SegmentMap/FieldMap size mismatch"
        );
        // Name equality
        assert!(
            const_str_eq(seg.name, field.name),
            "SegmentMap/FieldMap name mismatch"
        );
        i += 1;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    struct TestLayout;

    impl SegmentMap for TestLayout {
        const SEGMENTS: &'static [StaticSegment] = &[
            StaticSegment::new("authority", 0, 32),
            StaticSegment::new("balance", 32, 8),
            StaticSegment::new("bump", 40, 1),
        ];
    }

    #[test]
    fn lookup_by_name() {
        let seg = TestLayout::segment("balance").unwrap();
        assert_eq!(seg.offset, 32);
        assert_eq!(seg.size, 8);
    }

    #[test]
    fn lookup_missing() {
        assert!(TestLayout::segment("nonexistent").is_none());
    }

    #[test]
    fn body_size_computed() {
        assert_eq!(TestLayout::body_size(), 41);
    }

    #[test]
    fn segment_count() {
        assert_eq!(TestLayout::segment_count(), 3);
    }

    #[test]
    fn segment_end() {
        let seg = TestLayout::segment("authority").unwrap();
        assert_eq!(seg.end(), 32);
    }

    #[test]
    fn segment_by_index_constant_access() {
        let seg = TestLayout::segment_by_index(1);
        assert_eq!(seg.name, "balance");
        assert_eq!(seg.offset, 32);
        assert_eq!(seg.size, 8);
    }

    // Verify alignment assertion compiles for matching SegmentMap + FieldMap.
    impl hopper_runtime::field_map::FieldMap for TestLayout {
        const FIELDS: &'static [hopper_runtime::field_map::FieldInfo] = &[
            hopper_runtime::field_map::FieldInfo::new("authority", 16, 32),
            hopper_runtime::field_map::FieldInfo::new("balance", 48, 8),
            hopper_runtime::field_map::FieldInfo::new("bump", 56, 1),
        ];
    }

    // Compiles = SegmentMap and FieldMap are isomorphic.
    const _: () = assert_segment_field_alignment::<TestLayout>(16);
}