hopper-runtime 0.4.5

Canonical low-level runtime surface for Hopper programs: direct account memory, validation, borrow guards, CPI, and zero-copy state access.
Documentation
//! Unified zero-copy trait family.
//!
//! This module consolidates `Pod`, `FixedLayout`, `Projectable`, `SafeProjectable`,
//! `LayoutContract`, header metadata, and schema export into one
//! coherent trait stack. This module delivers the foundation:
//!
//! - [`ZeroCopy`], the canonical "safe to overlay on raw bytes"
//!   marker. Equivalent-in-contract to [`Pod`], using
//!   Hopper's owned `Pod` / `Zeroable` proof layer.
//!   A blanket implementation covers `Pod` types that also carry Hopper's
//!   sealed marker, including layouts emitted by Hopper's macros.
//!
//! - [`WireLayout`], a `ZeroCopy` type whose wire size is
//!   `size_of::<Self>()` under the current blanket implementation.
//!
//! - [`AccountLayout`], a `WireLayout` that also carries Hopper's
//!   account header identity (disc, version, wire fingerprint, schema
//!   epoch, type offset). This is the top-level account-layout
//!   trait, with an explicit member list so the contract is
//!   frozen-in-place for migrations and client generation.
//!
//! ## Why three traits, not one
//!
//! The layering mirrors a real capability hierarchy. Every account
//! layout is a wire layout; every wire layout is zero-copy; but not
//! every zero-copy type is a full account layout (`WireU64`, `WireBool`,
//! `TypedAddress<T>` are zero-copy but carry no header). Splitting
//! the traits lets generic helpers demand just what they need.
//!
//! ## Relation to `LayoutContract`
//!
//! The existing [`crate::layout::LayoutContract`] trait predates this
//! module. `LayoutContract` and `AccountLayout` intentionally overlap:
//! both describe "a Hopper layout with disc/version/layout_id".
//! `AccountLayout` presents the same identity through a unified trait stack.
//! A blanket implementation covers types that implement both
//! `LayoutContract` and `ZeroCopy`.

use crate::layout::LayoutContract;
use crate::pod::Pod;

// ══════════════════════════════════════════════════════════════════════
//  Seal
// ══════════════════════════════════════════════════════════════════════

/// Internal marker every framework-defined zero-copy type stamps itself
/// with. Sealed by convention: it lives in a doc-hidden module so
/// downstream code cannot name it except through the canonical
/// Hopper entry points (`#[hopper::pod]`, `#[hopper::state]`,
/// `hopper_layout!`, and the framework's own primitive wire types).
///
/// A user bypassing the macro system with a hand-rolled
/// `unsafe impl Pod for Foo {}` cannot accidentally pick up
/// [`ZeroCopy`] for free. The `ZeroCopy` blanket below additionally
/// requires `HopperZeroCopySealed`, which only framework-defined
/// surfaces implement.
///
/// Users who legitimately need to extend `ZeroCopy` for a custom
/// primitive can declare `unsafe impl ::hopper_runtime::__sealed::HopperZeroCopySealed for MyType {}`
/// manually, but the path-through-doc-hidden-module signals clearly
/// that they are opting out of the macro's field-level proof.
#[doc(hidden)]
pub mod __sealed {
    /// See the module-level documentation. Do not implement directly
    /// unless you understand the full Hopper `Pod` + `Zeroable` +
    /// alignment + no-padding + no-interior-pointers contract.
    ///
    /// # Safety
    ///
    /// Implementors promise the type is a fixed-size, `#[repr(C)]`,
    /// alignment-1 plain-old-data value with no padding bytes and no
    /// interior pointers, so that any byte pattern of the correct length is
    /// a valid instance. Implementing this for a type that violates the
    /// contract makes every downstream [`super::ZeroCopy`] cast unsound.
    pub unsafe trait HopperZeroCopySealed {}

    // Framework-provided primitives. Every Rust-level `Pod` integer
    // and `[u8; N]` is Hopper-owned by virtue of being in the
    // substrate, so stamp the seal here. Users reading/writing these
    // via `ForeignLens::field::<T, OFFSET>` or equivalent paths get
    // `ZeroCopy` for free.
    unsafe impl HopperZeroCopySealed for u8 {}
    unsafe impl HopperZeroCopySealed for i8 {}
    unsafe impl<const N: usize> HopperZeroCopySealed for [u8; N] {}
    unsafe impl HopperZeroCopySealed for () {}
}

// ══════════════════════════════════════════════════════════════════════
//  ZeroCopy
// ══════════════════════════════════════════════════════════════════════

/// Canonical marker for types that may be overlaid on raw bytes.
///
/// # Safety
///
/// The contract is the same four-point obligation as [`Pod`]:
///
/// 1. Every `[u8; size_of::<T>()]` bit pattern decodes to a valid `T`.
/// 2. `align_of::<T>() == 1`.
/// 3. `T` contains no padding.
/// 4. `T` contains no internal pointers or references.
///
/// # Sealing
///
/// `ZeroCopy` is gated behind the doc-hidden
/// [`__sealed::HopperZeroCopySealed`] marker. Types authored through
/// `#[hopper::pod]`, `#[hopper::state]`, `hopper_layout!`, or one of
/// the framework's own primitive wire types (`WireU64`, `WireBool`,
/// `TypedAddress<T>`, etc.) stamp themselves with the seal
/// automatically. A user bypassing the macros with a bare
/// `unsafe impl Pod` does **not** get `ZeroCopy` for free. `ZeroCopy`
/// is implemented only through the framework-owned sealed path.
pub unsafe trait ZeroCopy: Pod + 'static + __sealed::HopperZeroCopySealed {}

// Blanket: any `Pod + 'static` type that also carries the seal gets
// `ZeroCopy`. Every framework-defined surface carries the seal; the
// blanket plus the seal together mean the trait is free for
// framework users and opaque to bypassing code.
unsafe impl<T> ZeroCopy for T where T: Pod + 'static + __sealed::HopperZeroCopySealed {}

// ══════════════════════════════════════════════════════════════════════
//  WireLayout
// ══════════════════════════════════════════════════════════════════════

/// A `ZeroCopy` type with a compile-time-known wire size.
///
/// The associated constant defaults to `size_of::<Self>()`. The blanket
/// implementation below applies that value to each `ZeroCopy` type.
pub trait WireLayout: ZeroCopy {
    /// Size of the on-wire representation, in bytes.
    const WIRE_SIZE: usize = core::mem::size_of::<Self>();
}

// Blanket: every `ZeroCopy` type gets `WireLayout` with the default
// `WIRE_SIZE`. Keeps the trait free for user code.
impl<T: ZeroCopy> WireLayout for T {}

// ══════════════════════════════════════════════════════════════════════
//  AccountLayout
// ══════════════════════════════════════════════════════════════════════

/// Hopper account layout identity, the top of the unified trait stack.
///
/// `WIRE_FINGERPRINT` is the first 8 bytes of the canonical SHA-256
/// wire descriptor emitted by the `#[hopper::state]` expansion in the
/// `hopper-derive` package, reinterpreted as a little-endian `u64`, so
/// the runtime can compare against the on-account header byte-for-byte.
///
/// `SCHEMA_EPOCH` defaults to `1`; programs that publish later epochs
/// via their on-chain manifest bump it to signal a version transition.
pub trait AccountLayout: WireLayout {
    /// On-chain discriminator (header byte 0).
    const DISC: u8;
    /// Layout version (header byte 1).
    const VERSION: u8;
    /// Canonical wire fingerprint (header bytes 4..12, little-endian).
    const WIRE_FINGERPRINT: u64;
    /// Schema-evolution epoch (header bytes 12..16).
    const SCHEMA_EPOCH: u32 = 1;
    /// Offset at which `Self` starts inside the account buffer.
    /// `0` for header-inclusive layouts, `HEADER_LEN` for body-only.
    const TYPE_OFFSET: usize;

    /// Total data length an account must carry to hold `Self`.
    #[inline(always)]
    fn required_len() -> usize {
        Self::TYPE_OFFSET + Self::WIRE_SIZE
    }
}

// Blanket: every `LayoutContract` type automatically is an
// `AccountLayout`. This makes the transition source-compatible -
// `#[hopper::state]` emits `LayoutContract` today; downstream can
// reach for either trait interchangeably.
//
// Fingerprint translation: `LayoutContract::LAYOUT_ID` is already a
// `[u8; 8]` produced by the canonical wire-descriptor hash. We reinterpret
// it as a little-endian `u64` for the `WIRE_FINGERPRINT` slot.
impl<T: LayoutContract + ZeroCopy> AccountLayout for T {
    const DISC: u8 = <T as LayoutContract>::DISC;
    const VERSION: u8 = <T as LayoutContract>::VERSION;
    const WIRE_FINGERPRINT: u64 = u64::from_le_bytes(<T as LayoutContract>::LAYOUT_ID);
    const SCHEMA_EPOCH: u32 = <T as LayoutContract>::SCHEMA_EPOCH;
    const TYPE_OFFSET: usize = <T as LayoutContract>::TYPE_OFFSET;
}

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

    fn require_zero_copy<T: ZeroCopy>() {}
    fn require_wire<T: WireLayout>() {}

    #[test]
    fn primitives_are_zero_copy_and_wire() {
        require_zero_copy::<u8>();
        require_zero_copy::<i8>();
        require_zero_copy::<[u8; 32]>();
        require_wire::<u8>();
        require_wire::<i8>();
        require_wire::<[u8; 32]>();
        assert_eq!(<i8 as WireLayout>::WIRE_SIZE, 1);
        assert_eq!(<[u8; 32] as WireLayout>::WIRE_SIZE, 32);
    }

    #[test]
    fn address_is_zero_copy() {
        require_zero_copy::<crate::address::Address>();
        assert_eq!(<crate::address::Address as WireLayout>::WIRE_SIZE, 32);
    }
}