Skip to main content

hopper_core/abi/
integers.rs

1//! Alignment-1 little-endian integer wire types.
2
3use core::{fmt, ops};
4
5/// Generate a little-endian wire integer type.
6///
7/// Each type is `#[repr(transparent)]` over `[u8; N]`, guaranteeing alignment 1.
8/// Checked assignment helpers are provided for common handler code; anything
9/// more complex should still convert to native, compute, then write back.
10macro_rules! wire_int {
11    (
12        $(#[$meta:meta])*
13        $name:ident, $native:ty, $size:literal, $canonical:literal
14    ) => {
15        $(#[$meta])*
16        #[derive(Clone, Copy, PartialEq, Eq, Default)]
17        #[repr(transparent)]
18        pub struct $name([u8; $size]);
19
20        // Compile-time guarantees
21        const _: () = assert!(core::mem::size_of::<$name>() == $size);
22        const _: () = assert!(core::mem::align_of::<$name>() == 1);
23
24        impl $name {
25            /// Zero value.
26            pub const ZERO: Self = Self([0u8; $size]);
27
28            /// Maximum value.
29            pub const MAX: Self = Self(<$native>::MAX.to_le_bytes());
30
31            /// Minimum value.
32            pub const MIN: Self = Self(<$native>::MIN.to_le_bytes());
33
34            /// Wrap a native value into wire format.
35            #[inline(always)]
36            pub const fn new(v: $native) -> Self {
37                Self(v.to_le_bytes())
38            }
39
40            /// Read the native value from wire format.
41            #[inline(always)]
42            pub const fn get(self) -> $native {
43                <$native>::from_le_bytes(self.0)
44            }
45
46            /// Write a native value into this wire slot.
47            #[inline(always)]
48            pub fn set(&mut self, v: $native) {
49                self.0 = v.to_le_bytes();
50            }
51
52            /// Checked addition in native form, written back on success.
53            #[inline(always)]
54            pub fn checked_add_assign(
55                &mut self,
56                rhs: $native,
57            ) -> ::core::result::Result<(), ::hopper_runtime::ProgramError> {
58                let next = self
59                    .get()
60                    .checked_add(rhs)
61                    .ok_or(::hopper_runtime::ProgramError::ArithmeticOverflow)?;
62                self.set(next);
63                Ok(())
64            }
65
66            /// Alias for [`Self::checked_add_assign`].
67            #[inline(always)]
68            pub fn add_assign_checked(
69                &mut self,
70                rhs: $native,
71            ) -> ::core::result::Result<(), ::hopper_runtime::ProgramError> {
72                self.checked_add_assign(rhs)
73            }
74
75            /// Checked subtraction in native form, written back on success.
76            #[inline(always)]
77            pub fn checked_sub_assign(
78                &mut self,
79                rhs: $native,
80            ) -> ::core::result::Result<(), ::hopper_runtime::ProgramError> {
81                let next = self
82                    .get()
83                    .checked_sub(rhs)
84                    .ok_or(::hopper_runtime::ProgramError::ArithmeticOverflow)?;
85                self.set(next);
86                Ok(())
87            }
88
89            /// Alias for [`Self::checked_sub_assign`].
90            #[inline(always)]
91            pub fn sub_assign_checked(
92                &mut self,
93                rhs: $native,
94            ) -> ::core::result::Result<(), ::hopper_runtime::ProgramError> {
95                self.checked_sub_assign(rhs)
96            }
97
98            /// Checked multiplication in native form, written back on success.
99            #[inline(always)]
100            pub fn checked_mul_assign(
101                &mut self,
102                rhs: $native,
103            ) -> ::core::result::Result<(), ::hopper_runtime::ProgramError> {
104                let next = self
105                    .get()
106                    .checked_mul(rhs)
107                    .ok_or(::hopper_runtime::ProgramError::ArithmeticOverflow)?;
108                self.set(next);
109                Ok(())
110            }
111
112            /// Alias for [`Self::checked_mul_assign`].
113            #[inline(always)]
114            pub fn mul_assign_checked(
115                &mut self,
116                rhs: $native,
117            ) -> ::core::result::Result<(), ::hopper_runtime::ProgramError> {
118                self.checked_mul_assign(rhs)
119            }
120
121            /// Raw byte access (immutable).
122            #[inline(always)]
123            pub const fn as_bytes(&self) -> &[u8; $size] {
124                &self.0
125            }
126
127            /// Raw byte access (mutable).
128            #[inline(always)]
129            pub fn as_bytes_mut(&mut self) -> &mut [u8; $size] {
130                &mut self.0
131            }
132        }
133
134        impl From<$native> for $name {
135            #[inline(always)]
136            fn from(v: $native) -> Self {
137                Self::new(v)
138            }
139        }
140
141        impl From<$name> for $native {
142            #[inline(always)]
143            fn from(w: $name) -> Self {
144                w.get()
145            }
146        }
147
148        impl ops::Add<$native> for $name {
149            type Output = Self;
150
151            #[inline(always)]
152            fn add(self, rhs: $native) -> Self::Output {
153                Self::new(self.get() + rhs)
154            }
155        }
156
157        impl ops::Add<Self> for $name {
158            type Output = Self;
159
160            #[inline(always)]
161            fn add(self, rhs: Self) -> Self::Output {
162                Self::new(self.get() + rhs.get())
163            }
164        }
165
166        impl ops::AddAssign<$native> for $name {
167            #[inline(always)]
168            fn add_assign(&mut self, rhs: $native) {
169                self.set(self.get() + rhs);
170            }
171        }
172
173        impl ops::AddAssign<Self> for $name {
174            #[inline(always)]
175            fn add_assign(&mut self, rhs: Self) {
176                self.set(self.get() + rhs.get());
177            }
178        }
179
180        impl ops::Sub<$native> for $name {
181            type Output = Self;
182
183            #[inline(always)]
184            fn sub(self, rhs: $native) -> Self::Output {
185                Self::new(self.get() - rhs)
186            }
187        }
188
189        impl ops::Sub<Self> for $name {
190            type Output = Self;
191
192            #[inline(always)]
193            fn sub(self, rhs: Self) -> Self::Output {
194                Self::new(self.get() - rhs.get())
195            }
196        }
197
198        impl ops::SubAssign<$native> for $name {
199            #[inline(always)]
200            fn sub_assign(&mut self, rhs: $native) {
201                self.set(self.get() - rhs);
202            }
203        }
204
205        impl ops::SubAssign<Self> for $name {
206            #[inline(always)]
207            fn sub_assign(&mut self, rhs: Self) {
208                self.set(self.get() - rhs.get());
209            }
210        }
211
212        impl ops::Mul<$native> for $name {
213            type Output = Self;
214
215            #[inline(always)]
216            fn mul(self, rhs: $native) -> Self::Output {
217                Self::new(self.get() * rhs)
218            }
219        }
220
221        impl ops::Mul<Self> for $name {
222            type Output = Self;
223
224            #[inline(always)]
225            fn mul(self, rhs: Self) -> Self::Output {
226                Self::new(self.get() * rhs.get())
227            }
228        }
229
230        impl ops::MulAssign<$native> for $name {
231            #[inline(always)]
232            fn mul_assign(&mut self, rhs: $native) {
233                self.set(self.get() * rhs);
234            }
235        }
236
237        impl ops::MulAssign<Self> for $name {
238            #[inline(always)]
239            fn mul_assign(&mut self, rhs: Self) {
240                self.set(self.get() * rhs.get());
241            }
242        }
243
244        impl PartialOrd for $name {
245            #[inline(always)]
246            fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
247                Some(self.cmp(other))
248            }
249        }
250
251        impl Ord for $name {
252            #[inline(always)]
253            fn cmp(&self, other: &Self) -> core::cmp::Ordering {
254                self.get().cmp(&other.get())
255            }
256        }
257
258        impl fmt::Debug for $name {
259            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260                write!(f, "{}({})", stringify!($name), self.get())
261            }
262        }
263
264        impl fmt::Display for $name {
265            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266                write!(f, "{}", self.get())
267            }
268        }
269
270        // SAFETY: align_of == 1, all bit patterns valid, Copy, no drop glue.
271        unsafe impl crate::abi::WireType for $name {
272            const WIRE_SIZE: usize = $size;
273            const CANONICAL_NAME: &'static str = $canonical;
274        }
275
276        // Bytemuck proof (Hopper Safety Audit Must-Fix #5): the Pod
277        // supertrait bound requires these impls. `#[repr(transparent)]`
278        // over `[u8; N]` satisfies every bytemuck obligation, all
279        // bit patterns valid, no padding, align-1 inherited from the
280        // inner array.
281        #[cfg(feature = "hopper-native-backend")]
282        unsafe impl ::hopper_runtime::__hopper_native::bytemuck::Zeroable for $name {}
283        #[cfg(feature = "hopper-native-backend")]
284        unsafe impl ::hopper_runtime::__hopper_native::bytemuck::Pod for $name {}
285
286        // SAFETY: #[repr(transparent)] over [u8; N], all bit patterns valid.
287        unsafe impl crate::account::Pod for $name {}
288
289        // Audit Step 5 seal: stamp the Hopper-authored marker so the
290        // blanket `ZeroCopy` impl picks this primitive up. A user
291        // bypassing the wire_int! path with their own bare
292        // `unsafe impl Pod` does not get the seal.
293        unsafe impl ::hopper_runtime::__sealed::HopperZeroCopySealed for $name {}
294
295        impl crate::account::FixedLayout for $name {
296            const SIZE: usize = $size;
297        }
298    };
299}
300
301wire_int!(
302    /// 16-bit unsigned little-endian wire integer.
303    WireU16, u16, 2, "u16"
304);
305
306wire_int!(
307    /// 32-bit unsigned little-endian wire integer.
308    WireU32, u32, 4, "u32"
309);
310
311wire_int!(
312    /// 64-bit unsigned little-endian wire integer.
313    WireU64, u64, 8, "u64"
314);
315
316wire_int!(
317    /// 128-bit unsigned little-endian wire integer.
318    WireU128, u128, 16, "u128"
319);
320
321wire_int!(
322    /// 16-bit signed little-endian wire integer.
323    WireI16, i16, 2, "i16"
324);
325
326wire_int!(
327    /// 32-bit signed little-endian wire integer.
328    WireI32, i32, 4, "i32"
329);
330
331wire_int!(
332    /// 64-bit signed little-endian wire integer.
333    WireI64, i64, 8, "i64"
334);
335
336wire_int!(
337    /// 128-bit signed little-endian wire integer.
338    WireI128, i128, 16, "i128"
339);
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344
345    #[test]
346    fn wire_u64_roundtrip() {
347        let w = WireU64::new(0xDEAD_BEEF_CAFE_BABE);
348        assert_eq!(w.get(), 0xDEAD_BEEF_CAFE_BABE);
349    }
350
351    #[test]
352    fn wire_i64_negative() {
353        let w = WireI64::new(-42);
354        assert_eq!(w.get(), -42);
355    }
356
357    #[test]
358    fn wire_u64_checked_assign_helpers() {
359        let mut w = WireU64::new(10);
360        w.checked_add_assign(5).unwrap();
361        assert_eq!(w.get(), 15);
362        w.checked_sub_assign(3).unwrap();
363        assert_eq!(w.get(), 12);
364        w.checked_mul_assign(2).unwrap();
365        assert_eq!(w.get(), 24);
366    }
367
368    #[test]
369    fn wire_u64_assignment_operators_accept_native_rhs() {
370        let mut w = WireU64::new(10);
371        w += 5;
372        assert_eq!(w.get(), 15);
373        w -= 3;
374        assert_eq!(w.get(), 12);
375        w *= 2;
376        assert_eq!(w.get(), 24);
377
378        assert_eq!((w + 1).get(), 25);
379        assert_eq!((w - 4).get(), 20);
380        assert_eq!((w * 3).get(), 72);
381    }
382
383    #[test]
384    fn wire_assignment_operators_accept_wire_rhs() {
385        let mut w = WireI64::new(10);
386        w += WireI64::new(-3);
387        assert_eq!(w.get(), 7);
388        w -= WireI64::new(2);
389        assert_eq!(w.get(), 5);
390        w *= WireI64::new(-2);
391        assert_eq!(w.get(), -10);
392    }
393
394    #[test]
395    fn wire_u64_checked_assign_overflow_is_reported() {
396        let mut w = WireU64::new(u64::MAX);
397        assert_eq!(
398            w.checked_add_assign(1),
399            Err(::hopper_runtime::ProgramError::ArithmeticOverflow)
400        );
401        assert_eq!(w.get(), u64::MAX);
402    }
403
404    #[test]
405    fn wire_ordering() {
406        let a = WireU32::new(10);
407        let b = WireU32::new(20);
408        assert!(a < b);
409    }
410
411    #[test]
412    fn wire_default_is_zero() {
413        let w = WireU64::default();
414        assert_eq!(w.get(), 0);
415    }
416}