Skip to main content

hopper_core/abi/
mod.rs

1//! Alignment-1 little-endian ABI wire types.
2//!
3//! All types are `#[repr(transparent)]` over `[u8; N]` with `align_of == 1`.
4//! This guarantees safe zero-copy overlay from any byte boundary in Solana
5//! account data without alignment padding.
6//!
7//! Unlike raw integer types (which have alignment requirements matching their
8//! size), these wire types can be placed at any offset in a `#[repr(C)]` struct
9//! and will never introduce padding bytes.
10
11mod boolean;
12mod field_ref;
13mod integers;
14mod typed_address;
15
16pub use boolean::WireBool;
17pub use field_ref::{FieldMut, FieldRef};
18pub use integers::{WireI128, WireI16, WireI32, WireI64, WireU128, WireU16, WireU32, WireU64};
19pub use typed_address::{
20    Authority, Mint, Program, Token, TokenAccount, TypedAddress, UntypedAddress,
21};
22
23/// Marker trait for types safe to use as zero-copy wire fields.
24///
25/// # Safety
26///
27/// Implementors must guarantee:
28/// - `align_of::<Self>() == 1`
29/// - `size_of::<Self>() == Self::WIRE_SIZE`
30/// - All bit patterns are valid (no invalid states)
31/// - The type is `Copy` and has no drop glue
32pub unsafe trait WireType: Copy + Sized {
33    /// Byte size of this wire type on the wire.
34    const WIRE_SIZE: usize;
35
36    /// The canonical type name for schema/fingerprint generation.
37    const CANONICAL_NAME: &'static str;
38}
39
40// -- Layout Fingerprint --
41
42/// An 8-byte deterministic layout fingerprint.
43///
44/// Generated from `SHA-256("hopper:v1:" + name + ":" + version + ":" + fields)[..8]`.
45/// Two layouts with identical fields, types, sizes, and ordering produce
46/// the same fingerprint. Any structural change produces a different one.
47///
48/// Use this to assert compatibility between layout versions at compile time,
49/// verify on-chain accounts match expected schemas, and detect schema drift
50/// in migration paths.
51#[derive(Clone, Copy, PartialEq, Eq)]
52pub struct LayoutFingerprint {
53    bytes: [u8; 8],
54}
55
56impl LayoutFingerprint {
57    /// Create a fingerprint from raw bytes.
58    #[inline(always)]
59    pub const fn from_bytes(bytes: [u8; 8]) -> Self {
60        Self { bytes }
61    }
62
63    /// Raw fingerprint bytes.
64    #[inline(always)]
65    pub const fn as_bytes(&self) -> &[u8; 8] {
66        &self.bytes
67    }
68
69    /// Check if two fingerprints match.
70    #[inline(always)]
71    pub const fn matches(&self, other: &LayoutFingerprint) -> bool {
72        let a = &self.bytes;
73        let b = &other.bytes;
74        a[0] == b[0]
75            && a[1] == b[1]
76            && a[2] == b[2]
77            && a[3] == b[3]
78            && a[4] == b[4]
79            && a[5] == b[5]
80            && a[6] == b[6]
81            && a[7] == b[7]
82    }
83
84    /// Check if two fingerprints differ (schema changed between versions).
85    #[inline(always)]
86    pub const fn differs_from(&self, other: &LayoutFingerprint) -> bool {
87        !self.matches(other)
88    }
89
90    /// Verify this fingerprint matches data read from an account header.
91    ///
92    /// Reads the layout_id from bytes 4..12 of the header and compares.
93    #[inline]
94    pub fn verify_header(&self, data: &[u8]) -> Result<(), hopper_runtime::error::ProgramError> {
95        if data.len() < 12 {
96            return Err(hopper_runtime::error::ProgramError::AccountDataTooSmall);
97        }
98        let mut id = [0u8; 8];
99        id.copy_from_slice(&data[4..12]);
100        if id != self.bytes {
101            return Err(hopper_runtime::error::ProgramError::InvalidAccountData);
102        }
103        Ok(())
104    }
105}
106
107/// Pair of fingerprints for asserting version transitions.
108///
109/// Used in migration paths to prove that a layout_id changed between
110/// the old and new version (required for safe append-only evolution).
111pub struct FingerprintTransition {
112    pub from: LayoutFingerprint,
113    pub to: LayoutFingerprint,
114}
115
116impl FingerprintTransition {
117    /// Create a transition pair.
118    #[inline(always)]
119    pub const fn new(from: LayoutFingerprint, to: LayoutFingerprint) -> Self {
120        Self { from, to }
121    }
122
123    /// Assert the transition is valid: fingerprints must differ.
124    /// Call this as a `const` assertion in your migration code.
125    #[inline(always)]
126    pub const fn assert_valid(&self) {
127        assert!(
128            self.from.differs_from(&self.to),
129            "Layout fingerprints must differ between versions"
130        );
131    }
132}