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}