Skip to main content

hopper_native/
wire.rs

1//! Alignment-safe wire types for zero-copy account data.
2//!
3//! Solana account data buffers have alignment 1. Casting a `*const u8`
4//! to `*const u64` causes undefined behavior when the pointer is not
5//! 8-byte aligned. Every framework that does zero-copy must solve this.
6//!
7//! Quasar solves it with `PodU64([u8; 8])` -- wrapping arithmetic by
8//! default and implicit conversion. Hopper takes a different approach:
9//!
10//! - **Explicit endianness**: Types are named `LeU64` ("little-endian u64"),
11//!   making the wire representation unambiguous at every call site.
12//! - **Checked arithmetic by default**: `+`, `-`, `*` return `Option` via
13//!   `checked_add` etc. This is safer than wrapping overflow silently
14//!   (Quasar's default) and matches Rust's principled stance on UB.
15//! - **`const fn` constructors**: `LeU64::new(42)` works in const context,
16//!   enabling compile-time constants for discriminators, seeds, etc.
17//! - **`Projectable`**: All wire types implement `Projectable`, so you can
18//!   use `project::<LeU64>(account, offset, None)` to read them directly
19//!   from account data without alignment issues.
20//!
21//! These types are the foundation for safe zero-copy account structs.
22//! Any `#[repr(C)]` struct composed entirely of wire types + `[u8; N]`
23//! arrays is alignment-1-safe and can be projected from account data.
24
25use crate::project::Projectable;
26
27// ---- Macro to generate integer wire types ----------------------------
28
29macro_rules! le_integer {
30    (
31        $(#[$meta:meta])*
32        $name:ident, $native:ty, $size:expr, unsigned
33    ) => {
34        $(#[$meta])*
35        #[repr(transparent)]
36        #[derive(Clone, Copy, Default, Eq, PartialEq, Hash)]
37        pub struct $name([u8; $size]);
38
39        impl $name {
40            /// Zero value.
41            pub const ZERO: Self = Self([0; $size]);
42
43            /// Maximum representable value.
44            pub const MAX: Self = Self(<$native>::MAX.to_le_bytes());
45
46            /// Construct from a native integer (const-safe).
47            #[inline(always)]
48            pub const fn new(v: $native) -> Self {
49                Self(v.to_le_bytes())
50            }
51
52            /// Read the native integer value.
53            #[inline(always)]
54            pub const fn get(self) -> $native {
55                <$native>::from_le_bytes(self.0)
56            }
57
58            /// Raw little-endian bytes.
59            #[inline(always)]
60            pub const fn to_le_bytes(self) -> [u8; $size] {
61                self.0
62            }
63
64            /// Construct from raw little-endian bytes.
65            #[inline(always)]
66            pub const fn from_le_bytes(bytes: [u8; $size]) -> Self {
67                Self(bytes)
68            }
69
70            /// Checked addition. Returns `None` on overflow.
71            #[inline(always)]
72            pub const fn checked_add(self, rhs: Self) -> Option<Self> {
73                match self.get().checked_add(rhs.get()) {
74                    Some(v) => Some(Self::new(v)),
75                    None => None,
76                }
77            }
78
79            /// Checked subtraction. Returns `None` on underflow.
80            #[inline(always)]
81            pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
82                match self.get().checked_sub(rhs.get()) {
83                    Some(v) => Some(Self::new(v)),
84                    None => None,
85                }
86            }
87
88            /// Checked multiplication. Returns `None` on overflow.
89            #[inline(always)]
90            pub const fn checked_mul(self, rhs: Self) -> Option<Self> {
91                match self.get().checked_mul(rhs.get()) {
92                    Some(v) => Some(Self::new(v)),
93                    None => None,
94                }
95            }
96
97            /// Checked division. Returns `None` on divide-by-zero.
98            #[inline(always)]
99            pub const fn checked_div(self, rhs: Self) -> Option<Self> {
100                match self.get().checked_div(rhs.get()) {
101                    Some(v) => Some(Self::new(v)),
102                    None => None,
103                }
104            }
105
106            /// Saturating addition (clamps at MAX instead of wrapping).
107            #[inline(always)]
108            pub const fn saturating_add(self, rhs: Self) -> Self {
109                Self::new(self.get().saturating_add(rhs.get()))
110            }
111
112            /// Saturating subtraction (clamps at 0 instead of wrapping).
113            #[inline(always)]
114            pub const fn saturating_sub(self, rhs: Self) -> Self {
115                Self::new(self.get().saturating_sub(rhs.get()))
116            }
117
118            /// Wrapping addition (use explicitly when wrapping is intended).
119            #[inline(always)]
120            pub const fn wrapping_add(self, rhs: Self) -> Self {
121                Self::new(self.get().wrapping_add(rhs.get()))
122            }
123
124            /// Wrapping subtraction.
125            #[inline(always)]
126            pub const fn wrapping_sub(self, rhs: Self) -> Self {
127                Self::new(self.get().wrapping_sub(rhs.get()))
128            }
129
130            /// Whether the value is zero.
131            #[inline(always)]
132            pub const fn is_zero(self) -> bool {
133                self.get() == 0
134            }
135        }
136
137        impl From<$native> for $name {
138            #[inline(always)]
139            fn from(v: $native) -> Self { Self::new(v) }
140        }
141
142        impl From<$name> for $native {
143            #[inline(always)]
144            fn from(v: $name) -> Self { v.get() }
145        }
146
147        impl PartialOrd for $name {
148            #[inline(always)]
149            fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
150                Some(self.cmp(other))
151            }
152        }
153
154        impl Ord for $name {
155            #[inline(always)]
156            fn cmp(&self, other: &Self) -> core::cmp::Ordering {
157                self.get().cmp(&other.get())
158            }
159        }
160
161        impl core::fmt::Debug for $name {
162            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
163                write!(f, "{}({})", stringify!($name), self.get())
164            }
165        }
166
167        impl core::fmt::Display for $name {
168            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
169                write!(f, "{}", self.get())
170            }
171        }
172
173        // SAFETY: $name is #[repr(transparent)] over [u8; N].
174        // All bit patterns are valid (no padding, no alignment requirement).
175        unsafe impl Projectable for $name {}
176
177        $crate::__wire_arith_ops!($name, $native);
178    };
179
180    // Signed variant -- same API but with signed native type.
181    (
182        $(#[$meta:meta])*
183        $name:ident, $native:ty, $size:expr, signed
184    ) => {
185        $(#[$meta])*
186        #[repr(transparent)]
187        #[derive(Clone, Copy, Default, Eq, PartialEq, Hash)]
188        pub struct $name([u8; $size]);
189
190        impl $name {
191            /// Zero value.
192            pub const ZERO: Self = Self([0; $size]);
193
194            /// Maximum representable value.
195            pub const MAX: Self = Self(<$native>::MAX.to_le_bytes());
196
197            /// Minimum representable value.
198            pub const MIN: Self = Self(<$native>::MIN.to_le_bytes());
199
200            /// Construct from a native integer (const-safe).
201            #[inline(always)]
202            pub const fn new(v: $native) -> Self {
203                Self(v.to_le_bytes())
204            }
205
206            /// Read the native integer value.
207            #[inline(always)]
208            pub const fn get(self) -> $native {
209                <$native>::from_le_bytes(self.0)
210            }
211
212            /// Raw little-endian bytes.
213            #[inline(always)]
214            pub const fn to_le_bytes(self) -> [u8; $size] {
215                self.0
216            }
217
218            /// Construct from raw little-endian bytes.
219            #[inline(always)]
220            pub const fn from_le_bytes(bytes: [u8; $size]) -> Self {
221                Self(bytes)
222            }
223
224            /// Checked addition.
225            #[inline(always)]
226            pub const fn checked_add(self, rhs: Self) -> Option<Self> {
227                match self.get().checked_add(rhs.get()) {
228                    Some(v) => Some(Self::new(v)),
229                    None => None,
230                }
231            }
232
233            /// Checked subtraction.
234            #[inline(always)]
235            pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
236                match self.get().checked_sub(rhs.get()) {
237                    Some(v) => Some(Self::new(v)),
238                    None => None,
239                }
240            }
241
242            /// Checked multiplication.
243            #[inline(always)]
244            pub const fn checked_mul(self, rhs: Self) -> Option<Self> {
245                match self.get().checked_mul(rhs.get()) {
246                    Some(v) => Some(Self::new(v)),
247                    None => None,
248                }
249            }
250
251            /// Checked division.
252            #[inline(always)]
253            pub const fn checked_div(self, rhs: Self) -> Option<Self> {
254                match self.get().checked_div(rhs.get()) {
255                    Some(v) => Some(Self::new(v)),
256                    None => None,
257                }
258            }
259
260            /// Saturating addition.
261            #[inline(always)]
262            pub const fn saturating_add(self, rhs: Self) -> Self {
263                Self::new(self.get().saturating_add(rhs.get()))
264            }
265
266            /// Saturating subtraction.
267            #[inline(always)]
268            pub const fn saturating_sub(self, rhs: Self) -> Self {
269                Self::new(self.get().saturating_sub(rhs.get()))
270            }
271
272            /// Whether the value is zero.
273            #[inline(always)]
274            pub const fn is_zero(self) -> bool {
275                self.get() == 0
276            }
277
278            /// Whether the value is negative.
279            #[inline(always)]
280            pub const fn is_negative(self) -> bool {
281                self.get() < 0
282            }
283
284            /// Absolute value (wraps on MIN).
285            #[inline(always)]
286            pub const fn abs(self) -> Self {
287                Self::new(self.get().wrapping_abs())
288            }
289        }
290
291        impl From<$native> for $name {
292            #[inline(always)]
293            fn from(v: $native) -> Self { Self::new(v) }
294        }
295
296        impl From<$name> for $native {
297            #[inline(always)]
298            fn from(v: $name) -> Self { v.get() }
299        }
300
301        impl PartialOrd for $name {
302            #[inline(always)]
303            fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
304                Some(self.cmp(other))
305            }
306        }
307
308        impl Ord for $name {
309            #[inline(always)]
310            fn cmp(&self, other: &Self) -> core::cmp::Ordering {
311                self.get().cmp(&other.get())
312            }
313        }
314
315        impl core::fmt::Debug for $name {
316            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
317                write!(f, "{}({})", stringify!($name), self.get())
318            }
319        }
320
321        impl core::fmt::Display for $name {
322            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
323                write!(f, "{}", self.get())
324            }
325        }
326
327        unsafe impl Projectable for $name {}
328
329        $crate::__wire_arith_ops!($name, $native);
330    };
331}
332
333/// Internal: emit arithmetic operator impls for a wire integer type.
334///
335/// Mirrors Rust's native integer behavior: panic on overflow in debug,
336/// wrap in release. Programs that need explicit semantics should use the
337/// `checked_*`, `saturating_*`, or `wrapping_*` inherent methods.
338#[doc(hidden)]
339#[macro_export]
340macro_rules! __wire_arith_ops {
341    ($name:ident, $native:ty) => {
342        impl core::ops::Add for $name {
343            type Output = Self;
344            #[inline(always)]
345            fn add(self, rhs: Self) -> Self {
346                Self::new(self.get() + rhs.get())
347            }
348        }
349        impl core::ops::Sub for $name {
350            type Output = Self;
351            #[inline(always)]
352            fn sub(self, rhs: Self) -> Self {
353                Self::new(self.get() - rhs.get())
354            }
355        }
356        impl core::ops::Mul for $name {
357            type Output = Self;
358            #[inline(always)]
359            fn mul(self, rhs: Self) -> Self {
360                Self::new(self.get() * rhs.get())
361            }
362        }
363        impl core::ops::Div for $name {
364            type Output = Self;
365            #[inline(always)]
366            fn div(self, rhs: Self) -> Self {
367                Self::new(self.get() / rhs.get())
368            }
369        }
370        impl core::ops::Rem for $name {
371            type Output = Self;
372            #[inline(always)]
373            fn rem(self, rhs: Self) -> Self {
374                Self::new(self.get() % rhs.get())
375            }
376        }
377        impl core::ops::Add<$native> for $name {
378            type Output = Self;
379            #[inline(always)]
380            fn add(self, rhs: $native) -> Self {
381                Self::new(self.get() + rhs)
382            }
383        }
384        impl core::ops::Sub<$native> for $name {
385            type Output = Self;
386            #[inline(always)]
387            fn sub(self, rhs: $native) -> Self {
388                Self::new(self.get() - rhs)
389            }
390        }
391        impl core::ops::Mul<$native> for $name {
392            type Output = Self;
393            #[inline(always)]
394            fn mul(self, rhs: $native) -> Self {
395                Self::new(self.get() * rhs)
396            }
397        }
398        impl core::ops::Div<$native> for $name {
399            type Output = Self;
400            #[inline(always)]
401            fn div(self, rhs: $native) -> Self {
402                Self::new(self.get() / rhs)
403            }
404        }
405        impl core::ops::Rem<$native> for $name {
406            type Output = Self;
407            #[inline(always)]
408            fn rem(self, rhs: $native) -> Self {
409                Self::new(self.get() % rhs)
410            }
411        }
412        impl core::ops::AddAssign for $name {
413            #[inline(always)]
414            fn add_assign(&mut self, rhs: Self) {
415                *self = *self + rhs;
416            }
417        }
418        impl core::ops::SubAssign for $name {
419            #[inline(always)]
420            fn sub_assign(&mut self, rhs: Self) {
421                *self = *self - rhs;
422            }
423        }
424        impl core::ops::MulAssign for $name {
425            #[inline(always)]
426            fn mul_assign(&mut self, rhs: Self) {
427                *self = *self * rhs;
428            }
429        }
430        impl core::ops::DivAssign for $name {
431            #[inline(always)]
432            fn div_assign(&mut self, rhs: Self) {
433                *self = *self / rhs;
434            }
435        }
436        impl core::ops::RemAssign for $name {
437            #[inline(always)]
438            fn rem_assign(&mut self, rhs: Self) {
439                *self = *self % rhs;
440            }
441        }
442        impl core::ops::AddAssign<$native> for $name {
443            #[inline(always)]
444            fn add_assign(&mut self, rhs: $native) {
445                *self = *self + rhs;
446            }
447        }
448        impl core::ops::SubAssign<$native> for $name {
449            #[inline(always)]
450            fn sub_assign(&mut self, rhs: $native) {
451                *self = *self - rhs;
452            }
453        }
454        impl core::ops::MulAssign<$native> for $name {
455            #[inline(always)]
456            fn mul_assign(&mut self, rhs: $native) {
457                *self = *self * rhs;
458            }
459        }
460        impl core::ops::DivAssign<$native> for $name {
461            #[inline(always)]
462            fn div_assign(&mut self, rhs: $native) {
463                *self = *self / rhs;
464            }
465        }
466        impl core::ops::RemAssign<$native> for $name {
467            #[inline(always)]
468            fn rem_assign(&mut self, rhs: $native) {
469                *self = *self % rhs;
470            }
471        }
472        impl PartialEq<$native> for $name {
473            #[inline(always)]
474            fn eq(&self, other: &$native) -> bool {
475                self.get() == *other
476            }
477        }
478        impl PartialOrd<$native> for $name {
479            #[inline(always)]
480            fn partial_cmp(&self, other: &$native) -> Option<core::cmp::Ordering> {
481                Some(self.get().cmp(other))
482            }
483        }
484    };
485}
486
487// ---- Unsigned wire types ---------------------------------------------
488
489le_integer! {
490    /// 64-bit unsigned little-endian integer. Alignment 1.
491    ///
492    /// The workhorse type for token amounts, lamport balances, timestamps,
493    /// and most on-chain numeric fields. Use this instead of `u64` in any
494    /// `#[repr(C)]` struct that will be projected from account data.
495    LeU64, u64, 8, unsigned
496}
497
498le_integer! {
499    /// 32-bit unsigned little-endian integer. Alignment 1.
500    LeU32, u32, 4, unsigned
501}
502
503le_integer! {
504    /// 16-bit unsigned little-endian integer. Alignment 1.
505    LeU16, u16, 2, unsigned
506}
507
508// ---- Signed wire types -----------------------------------------------
509
510le_integer! {
511    /// 64-bit signed little-endian integer. Alignment 1.
512    ///
513    /// Used for timestamps (unix_timestamp is i64), deltas, and any
514    /// signed arithmetic in account data.
515    LeI64, i64, 8, signed
516}
517
518le_integer! {
519    /// 32-bit signed little-endian integer. Alignment 1.
520    LeI32, i32, 4, signed
521}
522
523le_integer! {
524    /// 16-bit signed little-endian integer. Alignment 1.
525    LeI16, i16, 2, signed
526}
527
528// ---- LeBool ----------------------------------------------------------
529
530/// Boolean wire type. Alignment 1.
531///
532/// Stored as a single byte: 0 = false, nonzero = true.
533/// `is_valid()` returns true only for 0 or 1, catching
534/// corrupted data that other frameworks would silently accept.
535#[repr(transparent)]
536#[derive(Clone, Copy, Default, Eq, PartialEq, Hash)]
537pub struct LeBool(u8);
538
539impl LeBool {
540    /// Canonical true value.
541    pub const TRUE: Self = Self(1);
542
543    /// Canonical false value.
544    pub const FALSE: Self = Self(0);
545
546    /// Construct from a Rust bool.
547    #[inline(always)]
548    pub const fn new(v: bool) -> Self {
549        Self(v as u8)
550    }
551
552    /// Read as a Rust bool (0 = false, anything else = true).
553    #[inline(always)]
554    pub const fn get(self) -> bool {
555        self.0 != 0
556    }
557
558    /// Raw byte value.
559    #[inline(always)]
560    pub const fn raw(self) -> u8 {
561        self.0
562    }
563
564    /// Whether the byte is strictly 0 or 1 (canonical representation).
565    ///
566    /// Non-canonical values (2..=255) are technically "true" but may
567    /// indicate data corruption or an incompatible writer.
568    #[inline(always)]
569    pub const fn is_canonical(self) -> bool {
570        self.0 == 0 || self.0 == 1
571    }
572}
573
574impl From<bool> for LeBool {
575    #[inline(always)]
576    fn from(v: bool) -> Self {
577        Self::new(v)
578    }
579}
580
581impl From<LeBool> for bool {
582    #[inline(always)]
583    fn from(v: LeBool) -> Self {
584        v.get()
585    }
586}
587
588impl core::fmt::Debug for LeBool {
589    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
590        write!(f, "LeBool({})", self.get())
591    }
592}
593
594impl core::fmt::Display for LeBool {
595    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
596        write!(f, "{}", self.get())
597    }
598}
599
600// SAFETY: LeBool is #[repr(transparent)] over u8. All bit patterns valid.
601unsafe impl Projectable for LeBool {}
602
603// ---- LeU128 ----------------------------------------------------------
604
605/// 128-bit unsigned little-endian integer. Alignment 1.
606///
607/// Useful for large amounts (e.g., total supply tracking) where u64
608/// would overflow. Stored as 16 bytes in account data.
609#[repr(transparent)]
610#[derive(Clone, Copy, Default, Eq, PartialEq, Hash)]
611pub struct LeU128([u8; 16]);
612
613impl LeU128 {
614    pub const ZERO: Self = Self([0; 16]);
615    pub const MAX: Self = Self(u128::MAX.to_le_bytes());
616
617    #[inline(always)]
618    pub const fn new(v: u128) -> Self {
619        Self(v.to_le_bytes())
620    }
621
622    #[inline(always)]
623    pub const fn get(self) -> u128 {
624        u128::from_le_bytes(self.0)
625    }
626
627    #[inline(always)]
628    pub const fn to_le_bytes(self) -> [u8; 16] {
629        self.0
630    }
631
632    #[inline(always)]
633    pub const fn checked_add(self, rhs: Self) -> Option<Self> {
634        match self.get().checked_add(rhs.get()) {
635            Some(v) => Some(Self::new(v)),
636            None => None,
637        }
638    }
639
640    #[inline(always)]
641    pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
642        match self.get().checked_sub(rhs.get()) {
643            Some(v) => Some(Self::new(v)),
644            None => None,
645        }
646    }
647
648    #[inline(always)]
649    pub const fn checked_mul(self, rhs: Self) -> Option<Self> {
650        match self.get().checked_mul(rhs.get()) {
651            Some(v) => Some(Self::new(v)),
652            None => None,
653        }
654    }
655
656    #[inline(always)]
657    pub const fn saturating_add(self, rhs: Self) -> Self {
658        Self::new(self.get().saturating_add(rhs.get()))
659    }
660
661    #[inline(always)]
662    pub const fn saturating_sub(self, rhs: Self) -> Self {
663        Self::new(self.get().saturating_sub(rhs.get()))
664    }
665
666    #[inline(always)]
667    pub const fn is_zero(self) -> bool {
668        self.get() == 0
669    }
670}
671
672impl From<u128> for LeU128 {
673    #[inline(always)]
674    fn from(v: u128) -> Self {
675        Self::new(v)
676    }
677}
678
679impl From<LeU128> for u128 {
680    #[inline(always)]
681    fn from(v: LeU128) -> Self {
682        v.get()
683    }
684}
685
686impl PartialOrd for LeU128 {
687    #[inline(always)]
688    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
689        Some(self.cmp(other))
690    }
691}
692
693impl Ord for LeU128 {
694    #[inline(always)]
695    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
696        self.get().cmp(&other.get())
697    }
698}
699
700impl core::fmt::Debug for LeU128 {
701    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
702        write!(f, "LeU128({})", self.get())
703    }
704}
705
706impl core::fmt::Display for LeU128 {
707    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
708        write!(f, "{}", self.get())
709    }
710}
711
712unsafe impl Projectable for LeU128 {}
713
714__wire_arith_ops!(LeU128, u128);