Skip to main content

pina_pod_primitives/
lib.rs

1#![no_std]
2
3//! Alignment-safe primitive wrappers that can be used in `Pod` structs.
4//!
5//! Pod types (`PodU64`, `PodU32`, etc.) wrap native integers in `[u8; N]`
6//! arrays, guaranteeing alignment 1. This allows direct pointer casts from
7//! account data without alignment concerns — critical for `#[repr(C)]`
8//! zero-copy structs on Solana.
9//!
10//! # Arithmetic
11//!
12//! Arithmetic operators (`+`, `-`, `*`) use **wrapping** semantics in release
13//! builds for CU efficiency and **panic on overflow** in debug builds. Use
14//! `checked_add`, `checked_sub`, `checked_mul`, `checked_div` where overflow
15//! must be detected in all build profiles.
16//!
17//! # Constants
18//!
19//! Each Pod integer type provides `ZERO`, `MIN`, and `MAX` constants.
20
21use core::fmt;
22use core::mem::align_of;
23use core::mem::size_of;
24
25use bytemuck::Pod;
26use bytemuck::Zeroable;
27
28/// The standard `bool` is not a `Pod`, define a replacement that is.
29#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Pod, Zeroable)]
30#[repr(transparent)]
31pub struct PodBool(pub u8);
32
33impl PodBool {
34	pub const fn from_bool(b: bool) -> Self {
35		Self(if b { 1 } else { 0 })
36	}
37
38	/// Returns `true` if the underlying byte is a canonical boolean value
39	/// (`0` or `1`).
40	///
41	/// Non-canonical values (2–255) are accepted by `bytemuck` deserialization
42	/// and convert to `true`, but two non-canonical `PodBool` values
43	/// representing the same logical boolean may fail `PartialEq` comparison.
44	/// Use this method to validate account data at deserialization boundaries.
45	pub const fn is_canonical(&self) -> bool {
46		self.0 == 0 || self.0 == 1
47	}
48}
49
50impl From<bool> for PodBool {
51	fn from(b: bool) -> Self {
52		Self::from_bool(b)
53	}
54}
55
56impl From<&bool> for PodBool {
57	fn from(b: &bool) -> Self {
58		Self(u8::from(*b))
59	}
60}
61
62impl From<&PodBool> for bool {
63	fn from(b: &PodBool) -> Self {
64		b.0 != 0
65	}
66}
67
68impl From<PodBool> for bool {
69	fn from(b: PodBool) -> Self {
70		b.0 != 0
71	}
72}
73
74impl core::ops::Not for PodBool {
75	type Output = Self;
76
77	#[inline]
78	fn not(self) -> Self {
79		Self::from_bool(!bool::from(self))
80	}
81}
82
83impl fmt::Display for PodBool {
84	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85		bool::from(*self).fmt(f)
86	}
87}
88
89/// Implements bidirectional conversion between a `Pod*` wrapper type and its
90/// corresponding standard integer.
91///
92/// For a given pair `($P, $I)`, this generates:
93/// - `$P::from_primitive($I) -> $P` (const)
94/// - `From<$I> for $P`
95/// - `From<$P> for $I`
96#[macro_export]
97macro_rules! impl_int_conversion {
98	($P:ty, $I:ty) => {
99		impl $P {
100			pub const fn from_primitive(n: $I) -> Self {
101				Self(n.to_le_bytes())
102			}
103
104			/// Returns the contained native value, converting from
105			/// little-endian bytes.
106			#[inline]
107			pub const fn get(&self) -> $I {
108				<$I>::from_le_bytes(self.0)
109			}
110		}
111
112		impl From<$I> for $P {
113			fn from(n: $I) -> Self {
114				Self::from_primitive(n)
115			}
116		}
117
118		impl From<$P> for $I {
119			fn from(pod: $P) -> Self {
120				pod.get()
121			}
122		}
123	};
124}
125
126/// Implements constants, ordering, display, checked/saturating arithmetic, and
127/// helper methods for a Pod integer type.
128macro_rules! impl_pod_common {
129	($name:ident, $native:ty, $size:expr) => {
130		impl $name {
131			/// The largest value representable by the underlying integer type.
132			pub const MAX: Self = Self(<$native>::MAX.to_le_bytes());
133			/// The smallest value representable by the underlying integer type.
134			pub const MIN: Self = Self(<$native>::MIN.to_le_bytes());
135			/// The zero value.
136			pub const ZERO: Self = Self([0u8; $size]);
137
138			/// Returns `true` if the value is zero.
139			#[inline]
140			#[must_use]
141			pub fn is_zero(&self) -> bool {
142				self.0 == [0u8; $size]
143			}
144
145			/// Checked addition. Returns `None` on overflow.
146			#[inline]
147			#[must_use]
148			pub fn checked_add(self, rhs: impl Into<$name>) -> Option<Self> {
149				self.get().checked_add(rhs.into().get()).map(Self::from)
150			}
151
152			/// Checked subtraction. Returns `None` on underflow.
153			#[inline]
154			#[must_use]
155			pub fn checked_sub(self, rhs: impl Into<$name>) -> Option<Self> {
156				self.get().checked_sub(rhs.into().get()).map(Self::from)
157			}
158
159			/// Checked multiplication. Returns `None` on overflow.
160			#[inline]
161			#[must_use]
162			pub fn checked_mul(self, rhs: impl Into<$name>) -> Option<Self> {
163				self.get().checked_mul(rhs.into().get()).map(Self::from)
164			}
165
166			/// Checked division. Returns `None` if `rhs` is zero.
167			#[inline]
168			#[must_use]
169			pub fn checked_div(self, rhs: impl Into<$name>) -> Option<Self> {
170				self.get().checked_div(rhs.into().get()).map(Self::from)
171			}
172
173			/// Saturating addition. Clamps at the numeric bounds instead of
174			/// overflowing.
175			#[inline]
176			#[must_use]
177			pub fn saturating_add(self, rhs: impl Into<$name>) -> Self {
178				Self::from(self.get().saturating_add(rhs.into().get()))
179			}
180
181			/// Saturating subtraction. Clamps at the numeric bound instead of
182			/// underflowing.
183			#[inline]
184			#[must_use]
185			pub fn saturating_sub(self, rhs: impl Into<$name>) -> Self {
186				Self::from(self.get().saturating_sub(rhs.into().get()))
187			}
188
189			/// Saturating multiplication. Clamps at the numeric bounds instead
190			/// of overflowing.
191			#[inline]
192			#[must_use]
193			pub fn saturating_mul(self, rhs: impl Into<$name>) -> Self {
194				Self::from(self.get().saturating_mul(rhs.into().get()))
195			}
196		}
197
198		impl PartialOrd for $name {
199			#[inline]
200			fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
201				Some(self.cmp(other))
202			}
203		}
204
205		impl Ord for $name {
206			#[inline]
207			fn cmp(&self, other: &Self) -> core::cmp::Ordering {
208				self.get().cmp(&other.get())
209			}
210		}
211
212		impl PartialEq<$native> for $name {
213			#[inline]
214			fn eq(&self, other: &$native) -> bool {
215				self.get() == *other
216			}
217		}
218
219		impl PartialOrd<$native> for $name {
220			#[inline]
221			fn partial_cmp(&self, other: &$native) -> Option<core::cmp::Ordering> {
222				self.get().partial_cmp(other)
223			}
224		}
225
226		impl fmt::Display for $name {
227			fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228				self.get().fmt(f)
229			}
230		}
231	};
232}
233
234/// Implements arithmetic operators for a Pod type.
235///
236/// In debug builds, operators panic on overflow via `checked_*`. In release
237/// builds, they use `wrapping_*` for CU efficiency on Solana.
238macro_rules! impl_pod_arithmetic {
239	($name:ident, $native:ty) => {
240		// --- Pod + native ---
241
242		impl core::ops::Add<$native> for $name {
243			type Output = Self;
244
245			#[inline]
246			fn add(self, rhs: $native) -> Self {
247				#[cfg(debug_assertions)]
248				{
249					Self::from(
250						self.get()
251							.checked_add(rhs)
252							.unwrap_or_else(|| panic!("attempt to add with overflow")),
253					)
254				}
255				#[cfg(not(debug_assertions))]
256				{
257					Self::from(self.get().wrapping_add(rhs))
258				}
259			}
260		}
261
262		impl core::ops::Sub<$native> for $name {
263			type Output = Self;
264
265			#[inline]
266			fn sub(self, rhs: $native) -> Self {
267				#[cfg(debug_assertions)]
268				{
269					Self::from(
270						self.get()
271							.checked_sub(rhs)
272							.unwrap_or_else(|| panic!("attempt to subtract with overflow")),
273					)
274				}
275				#[cfg(not(debug_assertions))]
276				{
277					Self::from(self.get().wrapping_sub(rhs))
278				}
279			}
280		}
281
282		impl core::ops::Mul<$native> for $name {
283			type Output = Self;
284
285			#[inline]
286			fn mul(self, rhs: $native) -> Self {
287				#[cfg(debug_assertions)]
288				{
289					Self::from(
290						self.get()
291							.checked_mul(rhs)
292							.unwrap_or_else(|| panic!("attempt to multiply with overflow")),
293					)
294				}
295				#[cfg(not(debug_assertions))]
296				{
297					Self::from(self.get().wrapping_mul(rhs))
298				}
299			}
300		}
301
302		impl core::ops::Div<$native> for $name {
303			type Output = Self;
304
305			#[inline]
306			fn div(self, rhs: $native) -> Self {
307				Self::from(self.get() / rhs)
308			}
309		}
310
311		impl core::ops::Rem<$native> for $name {
312			type Output = Self;
313
314			#[inline]
315			fn rem(self, rhs: $native) -> Self {
316				Self::from(self.get() % rhs)
317			}
318		}
319
320		// --- Pod + Pod ---
321
322		impl core::ops::Add for $name {
323			type Output = Self;
324
325			#[inline]
326			fn add(self, rhs: Self) -> Self {
327				self + rhs.get()
328			}
329		}
330
331		impl core::ops::Sub for $name {
332			type Output = Self;
333
334			#[inline]
335			fn sub(self, rhs: Self) -> Self {
336				self - rhs.get()
337			}
338		}
339
340		impl core::ops::Mul for $name {
341			type Output = Self;
342
343			#[inline]
344			fn mul(self, rhs: Self) -> Self {
345				self * rhs.get()
346			}
347		}
348
349		impl core::ops::Div for $name {
350			type Output = Self;
351
352			#[inline]
353			fn div(self, rhs: Self) -> Self {
354				self / rhs.get()
355			}
356		}
357
358		impl core::ops::Rem for $name {
359			type Output = Self;
360
361			#[inline]
362			fn rem(self, rhs: Self) -> Self {
363				self % rhs.get()
364			}
365		}
366
367		// --- Assign with native ---
368
369		impl core::ops::AddAssign<$native> for $name {
370			#[inline]
371			fn add_assign(&mut self, rhs: $native) {
372				*self = *self + rhs;
373			}
374		}
375
376		impl core::ops::SubAssign<$native> for $name {
377			#[inline]
378			fn sub_assign(&mut self, rhs: $native) {
379				*self = *self - rhs;
380			}
381		}
382
383		impl core::ops::MulAssign<$native> for $name {
384			#[inline]
385			fn mul_assign(&mut self, rhs: $native) {
386				*self = *self * rhs;
387			}
388		}
389
390		impl core::ops::DivAssign<$native> for $name {
391			#[inline]
392			fn div_assign(&mut self, rhs: $native) {
393				*self = *self / rhs;
394			}
395		}
396
397		impl core::ops::RemAssign<$native> for $name {
398			#[inline]
399			fn rem_assign(&mut self, rhs: $native) {
400				*self = *self % rhs;
401			}
402		}
403
404		// --- Assign with Pod ---
405
406		impl core::ops::AddAssign for $name {
407			#[inline]
408			fn add_assign(&mut self, rhs: Self) {
409				*self = *self + rhs;
410			}
411		}
412
413		impl core::ops::SubAssign for $name {
414			#[inline]
415			fn sub_assign(&mut self, rhs: Self) {
416				*self = *self - rhs;
417			}
418		}
419
420		impl core::ops::MulAssign for $name {
421			#[inline]
422			fn mul_assign(&mut self, rhs: Self) {
423				*self = *self * rhs;
424			}
425		}
426
427		impl core::ops::DivAssign for $name {
428			#[inline]
429			fn div_assign(&mut self, rhs: Self) {
430				*self = *self / rhs;
431			}
432		}
433
434		impl core::ops::RemAssign for $name {
435			#[inline]
436			fn rem_assign(&mut self, rhs: Self) {
437				*self = *self % rhs;
438			}
439		}
440
441		// --- Bitwise ---
442
443		impl core::ops::BitAnd<$native> for $name {
444			type Output = Self;
445
446			#[inline]
447			fn bitand(self, rhs: $native) -> Self {
448				Self::from(self.get() & rhs)
449			}
450		}
451
452		impl core::ops::BitOr<$native> for $name {
453			type Output = Self;
454
455			#[inline]
456			fn bitor(self, rhs: $native) -> Self {
457				Self::from(self.get() | rhs)
458			}
459		}
460
461		impl core::ops::BitXor<$native> for $name {
462			type Output = Self;
463
464			#[inline]
465			fn bitxor(self, rhs: $native) -> Self {
466				Self::from(self.get() ^ rhs)
467			}
468		}
469
470		impl core::ops::BitAnd for $name {
471			type Output = Self;
472
473			#[inline]
474			fn bitand(self, rhs: Self) -> Self {
475				self & rhs.get()
476			}
477		}
478
479		impl core::ops::BitOr for $name {
480			type Output = Self;
481
482			#[inline]
483			fn bitor(self, rhs: Self) -> Self {
484				self | rhs.get()
485			}
486		}
487
488		impl core::ops::BitXor for $name {
489			type Output = Self;
490
491			#[inline]
492			fn bitxor(self, rhs: Self) -> Self {
493				self ^ rhs.get()
494			}
495		}
496
497		impl core::ops::Shl<u32> for $name {
498			type Output = Self;
499
500			#[inline]
501			fn shl(self, rhs: u32) -> Self {
502				Self::from(self.get() << rhs)
503			}
504		}
505
506		impl core::ops::Shr<u32> for $name {
507			type Output = Self;
508
509			#[inline]
510			fn shr(self, rhs: u32) -> Self {
511				Self::from(self.get() >> rhs)
512			}
513		}
514
515		impl core::ops::Not for $name {
516			type Output = Self;
517
518			#[inline]
519			fn not(self) -> Self {
520				Self::from(!self.get())
521			}
522		}
523
524		// --- Bitwise assign with native ---
525
526		impl core::ops::BitAndAssign<$native> for $name {
527			#[inline]
528			fn bitand_assign(&mut self, rhs: $native) {
529				*self = *self & rhs;
530			}
531		}
532
533		impl core::ops::BitOrAssign<$native> for $name {
534			#[inline]
535			fn bitor_assign(&mut self, rhs: $native) {
536				*self = *self | rhs;
537			}
538		}
539
540		impl core::ops::BitXorAssign<$native> for $name {
541			#[inline]
542			fn bitxor_assign(&mut self, rhs: $native) {
543				*self = *self ^ rhs;
544			}
545		}
546
547		// --- Bitwise assign with Pod ---
548
549		impl core::ops::BitAndAssign for $name {
550			#[inline]
551			fn bitand_assign(&mut self, rhs: Self) {
552				*self = *self & rhs;
553			}
554		}
555
556		impl core::ops::BitOrAssign for $name {
557			#[inline]
558			fn bitor_assign(&mut self, rhs: Self) {
559				*self = *self | rhs;
560			}
561		}
562
563		impl core::ops::BitXorAssign for $name {
564			#[inline]
565			fn bitxor_assign(&mut self, rhs: Self) {
566				*self = *self ^ rhs;
567			}
568		}
569
570		impl core::ops::ShlAssign<u32> for $name {
571			#[inline]
572			fn shl_assign(&mut self, rhs: u32) {
573				*self = *self << rhs;
574			}
575		}
576
577		impl core::ops::ShrAssign<u32> for $name {
578			#[inline]
579			fn shr_assign(&mut self, rhs: u32) {
580				*self = *self >> rhs;
581			}
582		}
583	};
584}
585
586/// Implements `Neg` for signed Pod types.
587macro_rules! impl_pod_neg {
588	($name:ident, $native:ty) => {
589		impl core::ops::Neg for $name {
590			type Output = Self;
591
592			#[inline]
593			fn neg(self) -> Self {
594				#[cfg(debug_assertions)]
595				{
596					Self::from(
597						self.get()
598							.checked_neg()
599							.unwrap_or_else(|| panic!("attempt to negate with overflow")),
600					)
601				}
602				#[cfg(not(debug_assertions))]
603				{
604					Self::from(self.get().wrapping_neg())
605				}
606			}
607		}
608	};
609}
610
611/// Defines an unsigned Pod integer type with full operator support.
612macro_rules! define_pod_unsigned {
613	($name:ident, $native:ty, $size:expr, $doc:expr) => {
614		#[doc = $doc]
615		#[derive(Clone, Copy, Default, PartialEq, Eq, Pod, Zeroable)]
616		#[repr(transparent)]
617		pub struct $name(pub [u8; $size]);
618
619		impl_int_conversion!($name, $native);
620		impl_pod_common!($name, $native, $size);
621		impl_pod_arithmetic!($name, $native);
622
623		impl fmt::Debug for $name {
624			fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
625				write!(f, "{}({})", stringify!($name), self.get())
626			}
627		}
628	};
629}
630
631/// Defines a signed Pod integer type with full operator support.
632macro_rules! define_pod_signed {
633	($name:ident, $native:ty, $size:expr, $doc:expr) => {
634		#[doc = $doc]
635		#[derive(Clone, Copy, Default, PartialEq, Eq, Pod, Zeroable)]
636		#[repr(transparent)]
637		pub struct $name(pub [u8; $size]);
638
639		impl_int_conversion!($name, $native);
640		impl_pod_common!($name, $native, $size);
641		impl_pod_arithmetic!($name, $native);
642		impl_pod_neg!($name, $native);
643
644		impl fmt::Debug for $name {
645			fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
646				write!(f, "{}({})", stringify!($name), self.get())
647			}
648		}
649	};
650}
651
652define_pod_unsigned!(
653	PodU16,
654	u16,
655	2,
656	"An alignment-1 wrapper around `u16` stored as `[u8; 2]`.\n\nEnables safe zero-copy access \
657	 inside `#[repr(C)]` account structs."
658);
659
660define_pod_signed!(
661	PodI16,
662	i16,
663	2,
664	"An alignment-1 wrapper around `i16` stored as `[u8; 2]`.\n\nEnables safe zero-copy access \
665	 inside `#[repr(C)]` account structs."
666);
667
668define_pod_unsigned!(
669	PodU32,
670	u32,
671	4,
672	"An alignment-1 wrapper around `u32` stored as `[u8; 4]`.\n\nEnables safe zero-copy access \
673	 inside `#[repr(C)]` account structs."
674);
675
676define_pod_signed!(
677	PodI32,
678	i32,
679	4,
680	"An alignment-1 wrapper around `i32` stored as `[u8; 4]`.\n\nEnables safe zero-copy access \
681	 inside `#[repr(C)]` account structs."
682);
683
684define_pod_unsigned!(
685	PodU64,
686	u64,
687	8,
688	"An alignment-1 wrapper around `u64` stored as `[u8; 8]`.\n\nEnables safe zero-copy access \
689	 inside `#[repr(C)]` account structs."
690);
691
692define_pod_signed!(
693	PodI64,
694	i64,
695	8,
696	"An alignment-1 wrapper around `i64` stored as `[u8; 8]`.\n\nEnables safe zero-copy access \
697	 inside `#[repr(C)]` account structs."
698);
699
700define_pod_unsigned!(
701	PodU128,
702	u128,
703	16,
704	"An alignment-1 wrapper around `u128` stored as `[u8; 16]`.\n\nEnables safe zero-copy access \
705	 inside `#[repr(C)]` account structs."
706);
707
708define_pod_signed!(
709	PodI128,
710	i128,
711	16,
712	"An alignment-1 wrapper around `i128` stored as `[u8; 16]`.\n\nEnables safe zero-copy access \
713	 inside `#[repr(C)]` account structs."
714);
715
716// Compile-time invariant: all Pod types must have alignment 1 and correct
717// size. These assertions guard against future changes that could break
718// zero-copy access.
719const _: () = assert!(align_of::<PodU16>() == 1);
720const _: () = assert!(size_of::<PodU16>() == 2);
721const _: () = assert!(align_of::<PodI16>() == 1);
722const _: () = assert!(size_of::<PodI16>() == 2);
723const _: () = assert!(align_of::<PodU32>() == 1);
724const _: () = assert!(size_of::<PodU32>() == 4);
725const _: () = assert!(align_of::<PodI32>() == 1);
726const _: () = assert!(size_of::<PodI32>() == 4);
727const _: () = assert!(align_of::<PodU64>() == 1);
728const _: () = assert!(size_of::<PodU64>() == 8);
729const _: () = assert!(align_of::<PodI64>() == 1);
730const _: () = assert!(size_of::<PodI64>() == 8);
731const _: () = assert!(align_of::<PodU128>() == 1);
732const _: () = assert!(size_of::<PodU128>() == 16);
733const _: () = assert!(align_of::<PodI128>() == 1);
734const _: () = assert!(size_of::<PodI128>() == 16);
735const _: () = assert!(align_of::<PodBool>() == 1);
736const _: () = assert!(size_of::<PodBool>() == 1);
737
738#[cfg(test)]
739extern crate std;
740
741#[cfg(test)]
742mod tests {
743	use bytemuck::try_from_bytes;
744
745	use super::*;
746
747	// =======================================================================
748	// PodBool tests
749	// =======================================================================
750
751	#[test]
752	fn pod_bool_roundtrip() {
753		for i in 0..=u8::MAX {
754			let value = *try_from_bytes::<PodBool>(&[i]).unwrap();
755			assert_eq!(i != 0, bool::from(value));
756		}
757	}
758
759	/// Demonstrates that non-canonical PodBool values (2–255) convert to
760	/// `true` but fail `PartialEq` against `PodBool(1)`. Programs should
761	/// use `is_canonical()` to detect this at deserialization boundaries.
762	#[test]
763	fn pod_bool_non_canonical_equality_mismatch() {
764		let canonical_true = PodBool::from_bool(true);
765		let non_canonical_true = *try_from_bytes::<PodBool>(&[2]).unwrap();
766
767		// Both convert to `true`...
768		assert!(bool::from(canonical_true));
769		assert!(bool::from(non_canonical_true));
770
771		// ...but fail PartialEq because the raw bytes differ.
772		assert_ne!(canonical_true, non_canonical_true);
773
774		// `is_canonical` detects the non-standard encoding.
775		assert!(canonical_true.is_canonical());
776		assert!(!non_canonical_true.is_canonical());
777	}
778
779	#[test]
780	fn pod_bool_is_canonical_boundary_values() {
781		assert!(PodBool(0).is_canonical());
782		assert!(PodBool(1).is_canonical());
783		assert!(!PodBool(2).is_canonical());
784		assert!(!PodBool(127).is_canonical());
785		assert!(!PodBool(255).is_canonical());
786	}
787
788	#[test]
789	fn pod_bool_from_bool_produces_canonical() {
790		assert!(PodBool::from_bool(false).is_canonical());
791		assert!(PodBool::from_bool(true).is_canonical());
792		assert!(PodBool::from(false).is_canonical());
793		assert!(PodBool::from(true).is_canonical());
794	}
795
796	#[test]
797	fn pod_bool_from_ref() {
798		let t = true;
799		let f = false;
800		assert_eq!(PodBool::from(&t), PodBool(1));
801		assert_eq!(PodBool::from(&f), PodBool(0));
802	}
803
804	#[test]
805	fn pod_bool_from_ref_roundtrip() {
806		let pod = PodBool(1);
807		assert!(bool::from(&pod));
808		let pod = PodBool(0);
809		assert!(!bool::from(&pod));
810	}
811
812	#[test]
813	fn pod_bool_default_is_false() {
814		let default = PodBool::default();
815		assert_eq!(default.0, 0);
816		assert!(!bool::from(default));
817		assert!(default.is_canonical());
818	}
819
820	#[test]
821	fn pod_bool_not() {
822		assert_eq!(!PodBool::from_bool(true), PodBool::from_bool(false));
823		assert_eq!(!PodBool::from_bool(false), PodBool::from_bool(true));
824		// Non-canonical values treated as true
825		assert_eq!(!PodBool(42), PodBool::from_bool(false));
826	}
827
828	#[test]
829	fn pod_bool_display() {
830		assert_eq!(std::format!("{}", PodBool::from_bool(true)), "true");
831		assert_eq!(std::format!("{}", PodBool::from_bool(false)), "false");
832	}
833
834	// =======================================================================
835	// Conversion roundtrip tests
836	// =======================================================================
837
838	#[test]
839	fn pod_u16_roundtrip() {
840		assert_eq!(1u16, u16::from(PodU16::from_primitive(1)));
841	}
842
843	#[test]
844	fn pod_i16_roundtrip() {
845		assert_eq!(-1i16, i16::from(PodI16::from_primitive(-1)));
846	}
847
848	#[test]
849	fn pod_u32_roundtrip() {
850		assert_eq!(7u32, u32::from(PodU32::from_primitive(7)));
851	}
852
853	#[test]
854	fn pod_i32_roundtrip() {
855		assert_eq!(-7i32, i32::from(PodI32::from_primitive(-7)));
856	}
857
858	#[test]
859	fn pod_u64_roundtrip() {
860		assert_eq!(9u64, u64::from(PodU64::from_primitive(9)));
861	}
862
863	#[test]
864	fn pod_i64_roundtrip() {
865		assert_eq!(-9i64, i64::from(PodI64::from_primitive(-9)));
866	}
867
868	#[test]
869	fn pod_u128_roundtrip() {
870		assert_eq!(11u128, u128::from(PodU128::from_primitive(11)));
871	}
872
873	#[test]
874	fn pod_i128_roundtrip() {
875		assert_eq!(-11i128, i128::from(PodI128::from_primitive(-11)));
876	}
877
878	// =======================================================================
879	// Boundary value tests
880	// =======================================================================
881
882	#[test]
883	fn pod_u16_boundary_values() {
884		assert_eq!(0u16, u16::from(PodU16::from_primitive(0)));
885		assert_eq!(u16::MAX, u16::from(PodU16::from_primitive(u16::MAX)));
886	}
887
888	#[test]
889	fn pod_i16_boundary_values() {
890		assert_eq!(i16::MIN, i16::from(PodI16::from_primitive(i16::MIN)));
891		assert_eq!(i16::MAX, i16::from(PodI16::from_primitive(i16::MAX)));
892		assert_eq!(0i16, i16::from(PodI16::from_primitive(0)));
893	}
894
895	#[test]
896	fn pod_u32_boundary_values() {
897		assert_eq!(0u32, u32::from(PodU32::from_primitive(0)));
898		assert_eq!(u32::MAX, u32::from(PodU32::from_primitive(u32::MAX)));
899	}
900
901	#[test]
902	fn pod_i32_boundary_values() {
903		assert_eq!(i32::MIN, i32::from(PodI32::from_primitive(i32::MIN)));
904		assert_eq!(i32::MAX, i32::from(PodI32::from_primitive(i32::MAX)));
905	}
906
907	#[test]
908	fn pod_u64_boundary_values() {
909		assert_eq!(0u64, u64::from(PodU64::from_primitive(0)));
910		assert_eq!(u64::MAX, u64::from(PodU64::from_primitive(u64::MAX)));
911	}
912
913	#[test]
914	fn pod_i64_boundary_values() {
915		assert_eq!(i64::MIN, i64::from(PodI64::from_primitive(i64::MIN)));
916		assert_eq!(i64::MAX, i64::from(PodI64::from_primitive(i64::MAX)));
917	}
918
919	#[test]
920	fn pod_u128_boundary_values() {
921		assert_eq!(0u128, u128::from(PodU128::from_primitive(0)));
922		assert_eq!(u128::MAX, u128::from(PodU128::from_primitive(u128::MAX)));
923	}
924
925	#[test]
926	fn pod_i128_boundary_values() {
927		assert_eq!(i128::MIN, i128::from(PodI128::from_primitive(i128::MIN)));
928		assert_eq!(i128::MAX, i128::from(PodI128::from_primitive(i128::MAX)));
929	}
930
931	/// Verify that all Pod types store bytes in little-endian order, which
932	/// is the native byte order on Solana's BPF/SBF target.
933	#[test]
934	fn pod_types_use_little_endian_byte_order() {
935		let u16_val = PodU16::from_primitive(0x0102);
936		assert_eq!(u16_val.0, [0x02, 0x01]);
937
938		let u32_val = PodU32::from_primitive(0x01020304);
939		assert_eq!(u32_val.0, [0x04, 0x03, 0x02, 0x01]);
940
941		let u64_val = PodU64::from_primitive(0x0102030405060708);
942		assert_eq!(u64_val.0, [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
943	}
944
945	/// Verify that bytemuck deserialization of Pod types works correctly
946	/// from raw byte slices, simulating zero-copy account data access.
947	#[test]
948	fn pod_types_bytemuck_from_bytes() {
949		let bytes_u16 = [0x39, 0x05]; // 0x0539 = 1337
950		let val = try_from_bytes::<PodU16>(&bytes_u16).unwrap();
951		assert_eq!(u16::from(*val), 1337);
952
953		let bytes_u32 = [0xEF, 0xBE, 0xAD, 0xDE]; // 0xDEADBEEF
954		let val = try_from_bytes::<PodU32>(&bytes_u32).unwrap();
955		assert_eq!(u32::from(*val), 0xDEAD_BEEF);
956
957		let bytes_i16 = [0xFF, 0xFF]; // -1 in two's complement LE
958		let val = try_from_bytes::<PodI16>(&bytes_i16).unwrap();
959		assert_eq!(i16::from(*val), -1);
960	}
961
962	#[test]
963	fn pod_default_is_zero() {
964		assert_eq!(u16::from(PodU16::default()), 0);
965		assert_eq!(i16::from(PodI16::default()), 0);
966		assert_eq!(u32::from(PodU32::default()), 0);
967		assert_eq!(i32::from(PodI32::default()), 0);
968		assert_eq!(u64::from(PodU64::default()), 0);
969		assert_eq!(i64::from(PodI64::default()), 0);
970		assert_eq!(u128::from(PodU128::default()), 0);
971		assert_eq!(i128::from(PodI128::default()), 0);
972	}
973
974	// =======================================================================
975	// Constants tests
976	// =======================================================================
977
978	#[test]
979	fn pod_constants_zero() {
980		assert!(PodU16::ZERO.is_zero());
981		assert!(PodU32::ZERO.is_zero());
982		assert!(PodU64::ZERO.is_zero());
983		assert!(PodU128::ZERO.is_zero());
984		assert!(PodI16::ZERO.is_zero());
985		assert!(PodI32::ZERO.is_zero());
986		assert!(PodI64::ZERO.is_zero());
987		assert!(PodI128::ZERO.is_zero());
988	}
989
990	#[test]
991	fn pod_constants_min_max() {
992		assert_eq!(PodU16::MIN.get(), u16::MIN);
993		assert_eq!(PodU16::MAX.get(), u16::MAX);
994		assert_eq!(PodU32::MIN.get(), u32::MIN);
995		assert_eq!(PodU32::MAX.get(), u32::MAX);
996		assert_eq!(PodU64::MIN.get(), u64::MIN);
997		assert_eq!(PodU64::MAX.get(), u64::MAX);
998		assert_eq!(PodU128::MIN.get(), u128::MIN);
999		assert_eq!(PodU128::MAX.get(), u128::MAX);
1000		assert_eq!(PodI16::MIN.get(), i16::MIN);
1001		assert_eq!(PodI16::MAX.get(), i16::MAX);
1002		assert_eq!(PodI32::MIN.get(), i32::MIN);
1003		assert_eq!(PodI32::MAX.get(), i32::MAX);
1004		assert_eq!(PodI64::MIN.get(), i64::MIN);
1005		assert_eq!(PodI64::MAX.get(), i64::MAX);
1006		assert_eq!(PodI128::MIN.get(), i128::MIN);
1007		assert_eq!(PodI128::MAX.get(), i128::MAX);
1008	}
1009
1010	#[test]
1011	fn pod_is_zero_false_for_nonzero() {
1012		assert!(!PodU64::from_primitive(1).is_zero());
1013		assert!(!PodI64::from_primitive(-1).is_zero());
1014		assert!(!PodU128::MAX.is_zero());
1015	}
1016
1017	// =======================================================================
1018	// Arithmetic tests (Add, Sub, Mul, Div, Rem)
1019	// =======================================================================
1020
1021	#[test]
1022	fn pod_add_native() {
1023		assert_eq!((PodU64::from(10u64) + 5u64).get(), 15);
1024		assert_eq!((PodI32::from(10i32) + 5i32).get(), 15);
1025		assert_eq!((PodI32::from(-10i32) + 5i32).get(), -5);
1026	}
1027
1028	#[test]
1029	fn pod_add_pod() {
1030		let a = PodU64::from(10u64);
1031		let b = PodU64::from(20u64);
1032		assert_eq!((a + b).get(), 30);
1033	}
1034
1035	#[test]
1036	fn pod_sub_native() {
1037		assert_eq!((PodU64::from(10u64) - 5u64).get(), 5);
1038		assert_eq!((PodI32::from(-10i32) - 5i32).get(), -15);
1039	}
1040
1041	#[test]
1042	fn pod_sub_pod() {
1043		let a = PodU64::from(20u64);
1044		let b = PodU64::from(5u64);
1045		assert_eq!((a - b).get(), 15);
1046	}
1047
1048	#[test]
1049	fn pod_mul_native() {
1050		assert_eq!((PodU64::from(6u64) * 7u64).get(), 42);
1051		assert_eq!((PodI32::from(-3i32) * 4i32).get(), -12);
1052	}
1053
1054	#[test]
1055	fn pod_mul_pod() {
1056		let a = PodU32::from(6u32);
1057		let b = PodU32::from(7u32);
1058		assert_eq!((a * b).get(), 42);
1059	}
1060
1061	#[test]
1062	fn pod_div_native() {
1063		assert_eq!((PodU64::from(42u64) / 7u64).get(), 6);
1064		assert_eq!((PodI32::from(-12i32) / 4i32).get(), -3);
1065	}
1066
1067	#[test]
1068	fn pod_div_pod() {
1069		let a = PodU64::from(42u64);
1070		let b = PodU64::from(7u64);
1071		assert_eq!((a / b).get(), 6);
1072	}
1073
1074	#[test]
1075	fn pod_rem_native() {
1076		assert_eq!((PodU64::from(10u64) % 3u64).get(), 1);
1077		assert_eq!((PodI32::from(-10i32) % 3i32).get(), -1);
1078	}
1079
1080	#[test]
1081	fn pod_rem_pod() {
1082		let a = PodU64::from(10u64);
1083		let b = PodU64::from(3u64);
1084		assert_eq!((a % b).get(), 1);
1085	}
1086
1087	// =======================================================================
1088	// Assign operators
1089	// =======================================================================
1090
1091	#[test]
1092	fn pod_add_assign_native() {
1093		let mut v = PodU64::from(10u64);
1094		v += 5u64;
1095		assert_eq!(v.get(), 15);
1096	}
1097
1098	#[test]
1099	fn pod_add_assign_pod() {
1100		let mut v = PodU64::from(10u64);
1101		v += PodU64::from(5u64);
1102		assert_eq!(v.get(), 15);
1103	}
1104
1105	#[test]
1106	fn pod_sub_assign_native() {
1107		let mut v = PodU64::from(10u64);
1108		v -= 3u64;
1109		assert_eq!(v.get(), 7);
1110	}
1111
1112	#[test]
1113	fn pod_sub_assign_pod() {
1114		let mut v = PodU64::from(10u64);
1115		v -= PodU64::from(3u64);
1116		assert_eq!(v.get(), 7);
1117	}
1118
1119	#[test]
1120	fn pod_mul_assign_native() {
1121		let mut v = PodU32::from(5u32);
1122		v *= 4u32;
1123		assert_eq!(v.get(), 20);
1124	}
1125
1126	#[test]
1127	fn pod_mul_assign_pod() {
1128		let mut v = PodU32::from(5u32);
1129		v *= PodU32::from(4u32);
1130		assert_eq!(v.get(), 20);
1131	}
1132
1133	#[test]
1134	fn pod_div_assign_native() {
1135		let mut v = PodU64::from(20u64);
1136		v /= 5u64;
1137		assert_eq!(v.get(), 4);
1138	}
1139
1140	#[test]
1141	fn pod_div_assign_pod() {
1142		let mut v = PodU64::from(20u64);
1143		v /= PodU64::from(5u64);
1144		assert_eq!(v.get(), 4);
1145	}
1146
1147	#[test]
1148	fn pod_rem_assign_native() {
1149		let mut v = PodU64::from(10u64);
1150		v %= 3u64;
1151		assert_eq!(v.get(), 1);
1152	}
1153
1154	#[test]
1155	fn pod_rem_assign_pod() {
1156		let mut v = PodU64::from(10u64);
1157		v %= PodU64::from(3u64);
1158		assert_eq!(v.get(), 1);
1159	}
1160
1161	// =======================================================================
1162	// Bitwise tests
1163	// =======================================================================
1164
1165	#[test]
1166	fn pod_bitand_native() {
1167		assert_eq!((PodU32::from(0xFF00u32) & 0x0FF0u32).get(), 0x0F00);
1168	}
1169
1170	#[test]
1171	fn pod_bitand_pod() {
1172		let a = PodU32::from(0xFF00u32);
1173		let b = PodU32::from(0x0FF0u32);
1174		assert_eq!((a & b).get(), 0x0F00);
1175	}
1176
1177	#[test]
1178	fn pod_bitor_native() {
1179		assert_eq!((PodU32::from(0xFF00u32) | 0x00FFu32).get(), 0xFFFF);
1180	}
1181
1182	#[test]
1183	fn pod_bitor_pod() {
1184		let a = PodU32::from(0xFF00u32);
1185		let b = PodU32::from(0x00FFu32);
1186		assert_eq!((a | b).get(), 0xFFFF);
1187	}
1188
1189	#[test]
1190	fn pod_bitxor_native() {
1191		assert_eq!((PodU32::from(0xFFFFu32) ^ 0xFF00u32).get(), 0x00FF);
1192	}
1193
1194	#[test]
1195	fn pod_bitxor_pod() {
1196		let a = PodU32::from(0xFFFFu32);
1197		let b = PodU32::from(0xFF00u32);
1198		assert_eq!((a ^ b).get(), 0x00FF);
1199	}
1200
1201	#[test]
1202	fn pod_shl() {
1203		assert_eq!((PodU32::from(1u32) << 4).get(), 16);
1204	}
1205
1206	#[test]
1207	fn pod_shr() {
1208		assert_eq!((PodU32::from(16u32) >> 4).get(), 1);
1209	}
1210
1211	#[test]
1212	fn pod_not() {
1213		assert_eq!((!PodU16::from(0u16)).get(), u16::MAX);
1214		assert_eq!((!PodI16::from(0i16)).get(), -1i16);
1215	}
1216
1217	// --- Bitwise assign ---
1218
1219	#[test]
1220	fn pod_bitand_assign_native() {
1221		let mut v = PodU32::from(0xFF00u32);
1222		v &= 0x0FF0u32;
1223		assert_eq!(v.get(), 0x0F00);
1224	}
1225
1226	#[test]
1227	fn pod_bitand_assign_pod() {
1228		let mut v = PodU32::from(0xFF00u32);
1229		v &= PodU32::from(0x0FF0u32);
1230		assert_eq!(v.get(), 0x0F00);
1231	}
1232
1233	#[test]
1234	fn pod_bitor_assign_native() {
1235		let mut v = PodU32::from(0xFF00u32);
1236		v |= 0x00FFu32;
1237		assert_eq!(v.get(), 0xFFFF);
1238	}
1239
1240	#[test]
1241	fn pod_bitor_assign_pod() {
1242		let mut v = PodU32::from(0xFF00u32);
1243		v |= PodU32::from(0x00FFu32);
1244		assert_eq!(v.get(), 0xFFFF);
1245	}
1246
1247	#[test]
1248	fn pod_bitxor_assign_native() {
1249		let mut v = PodU32::from(0xFFFFu32);
1250		v ^= 0xFF00u32;
1251		assert_eq!(v.get(), 0x00FF);
1252	}
1253
1254	#[test]
1255	fn pod_bitxor_assign_pod() {
1256		let mut v = PodU32::from(0xFFFFu32);
1257		v ^= PodU32::from(0xFF00u32);
1258		assert_eq!(v.get(), 0x00FF);
1259	}
1260
1261	#[test]
1262	fn pod_shl_assign() {
1263		let mut v = PodU32::from(1u32);
1264		v <<= 4;
1265		assert_eq!(v.get(), 16);
1266	}
1267
1268	#[test]
1269	fn pod_shr_assign() {
1270		let mut v = PodU32::from(16u32);
1271		v >>= 4;
1272		assert_eq!(v.get(), 1);
1273	}
1274
1275	// =======================================================================
1276	// Neg for signed types
1277	// =======================================================================
1278
1279	#[test]
1280	fn pod_neg_i16() {
1281		assert_eq!((-PodI16::from(5i16)).get(), -5);
1282		assert_eq!((-PodI16::from(-5i16)).get(), 5);
1283		assert_eq!((-PodI16::from(0i16)).get(), 0);
1284	}
1285
1286	#[test]
1287	fn pod_neg_i32() {
1288		assert_eq!((-PodI32::from(42i32)).get(), -42);
1289	}
1290
1291	#[test]
1292	fn pod_neg_i64() {
1293		assert_eq!((-PodI64::from(100i64)).get(), -100);
1294	}
1295
1296	#[test]
1297	fn pod_neg_i128() {
1298		assert_eq!((-PodI128::from(999i128)).get(), -999);
1299	}
1300
1301	// =======================================================================
1302	// Checked arithmetic
1303	// =======================================================================
1304
1305	#[test]
1306	fn pod_checked_add_ok() {
1307		assert_eq!(
1308			PodU64::from(10u64).checked_add(5u64),
1309			Some(PodU64::from(15u64))
1310		);
1311	}
1312
1313	#[test]
1314	fn pod_checked_add_overflow() {
1315		assert_eq!(PodU64::MAX.checked_add(1u64), None);
1316	}
1317
1318	#[test]
1319	fn pod_checked_add_pod() {
1320		assert_eq!(
1321			PodU32::from(10u32).checked_add(PodU32::from(5u32)),
1322			Some(PodU32::from(15u32))
1323		);
1324	}
1325
1326	#[test]
1327	fn pod_checked_sub_ok() {
1328		assert_eq!(
1329			PodU64::from(10u64).checked_sub(5u64),
1330			Some(PodU64::from(5u64))
1331		);
1332	}
1333
1334	#[test]
1335	fn pod_checked_sub_underflow() {
1336		assert_eq!(PodU64::from(5u64).checked_sub(10u64), None);
1337	}
1338
1339	#[test]
1340	fn pod_checked_mul_ok() {
1341		assert_eq!(
1342			PodU64::from(6u64).checked_mul(7u64),
1343			Some(PodU64::from(42u64))
1344		);
1345	}
1346
1347	#[test]
1348	fn pod_checked_mul_overflow() {
1349		assert_eq!(PodU64::MAX.checked_mul(2u64), None);
1350	}
1351
1352	#[test]
1353	fn pod_checked_div_ok() {
1354		assert_eq!(
1355			PodU64::from(42u64).checked_div(7u64),
1356			Some(PodU64::from(6u64))
1357		);
1358	}
1359
1360	#[test]
1361	fn pod_checked_div_by_zero() {
1362		assert_eq!(PodU64::from(42u64).checked_div(0u64), None);
1363	}
1364
1365	#[test]
1366	fn pod_checked_signed_overflow() {
1367		assert_eq!(PodI64::MIN.checked_sub(1i64), None);
1368		assert_eq!(PodI64::MAX.checked_add(1i64), None);
1369	}
1370
1371	// =======================================================================
1372	// Saturating arithmetic
1373	// =======================================================================
1374
1375	#[test]
1376	fn pod_saturating_add() {
1377		assert_eq!(PodU64::MAX.saturating_add(100u64), PodU64::MAX);
1378		assert_eq!(
1379			PodU64::from(10u64).saturating_add(5u64),
1380			PodU64::from(15u64)
1381		);
1382	}
1383
1384	#[test]
1385	fn pod_saturating_sub() {
1386		assert_eq!(PodU64::from(5u64).saturating_sub(10u64), PodU64::ZERO);
1387		assert_eq!(PodU64::from(10u64).saturating_sub(5u64), PodU64::from(5u64));
1388	}
1389
1390	#[test]
1391	fn pod_saturating_mul() {
1392		assert_eq!(PodU64::MAX.saturating_mul(2u64), PodU64::MAX);
1393		assert_eq!(PodU64::from(6u64).saturating_mul(7u64), PodU64::from(42u64));
1394	}
1395
1396	#[test]
1397	fn pod_saturating_signed() {
1398		assert_eq!(PodI64::MAX.saturating_add(100i64), PodI64::MAX);
1399		assert_eq!(PodI64::MIN.saturating_sub(100i64), PodI64::MIN);
1400		assert_eq!(PodI64::MAX.saturating_mul(2i64), PodI64::MAX);
1401		assert_eq!(PodI64::MIN.saturating_mul(2i64), PodI64::MIN);
1402	}
1403
1404	// =======================================================================
1405	// Ordering tests
1406	// =======================================================================
1407
1408	#[test]
1409	fn pod_ordering() {
1410		assert!(PodU64::from(10u64) > PodU64::from(5u64));
1411		assert!(PodU64::from(5u64) < PodU64::from(10u64));
1412		assert!(PodU64::from(5u64) == PodU64::from(5u64));
1413
1414		assert!(PodI64::from(-10i64) < PodI64::from(5i64));
1415		assert!(PodI64::from(5i64) > PodI64::from(-10i64));
1416	}
1417
1418	#[test]
1419	fn pod_partial_eq_native() {
1420		assert!(PodU64::from(42u64) == 42u64);
1421		assert!(PodI32::from(-5i32) == -5i32);
1422		assert!(PodU64::from(42u64) != 43u64);
1423	}
1424
1425	#[test]
1426	fn pod_partial_ord_native() {
1427		assert!(PodU64::from(10u64) > 5u64);
1428		assert!(PodU64::from(5u64) < 10u64);
1429		assert!(PodI32::from(-10i32) < 0i32);
1430	}
1431
1432	// =======================================================================
1433	// Display / Debug tests
1434	// =======================================================================
1435
1436	#[test]
1437	fn pod_display() {
1438		assert_eq!(std::format!("{}", PodU64::from(42u64)), "42");
1439		assert_eq!(std::format!("{}", PodI32::from(-7i32)), "-7");
1440		assert_eq!(std::format!("{}", PodU128::from(0u128)), "0");
1441	}
1442
1443	#[test]
1444	fn pod_debug() {
1445		assert_eq!(std::format!("{:?}", PodU64::from(42u64)), "PodU64(42)");
1446		assert_eq!(std::format!("{:?}", PodI32::from(-7i32)), "PodI32(-7)");
1447	}
1448
1449	// =======================================================================
1450	// Get method tests
1451	// =======================================================================
1452
1453	#[test]
1454	fn pod_get_method() {
1455		assert_eq!(PodU16::from(1337u16).get(), 1337);
1456		assert_eq!(PodI16::from(-42i16).get(), -42);
1457		assert_eq!(PodU32::from(0xDEAD_BEEFu32).get(), 0xDEAD_BEEF);
1458		assert_eq!(PodI32::from(i32::MIN).get(), i32::MIN);
1459		assert_eq!(PodU64::from(u64::MAX).get(), u64::MAX);
1460		assert_eq!(PodI64::from(i64::MAX).get(), i64::MAX);
1461		assert_eq!(PodU128::from(u128::MAX).get(), u128::MAX);
1462		assert_eq!(PodI128::from(i128::MIN).get(), i128::MIN);
1463	}
1464
1465	// =======================================================================
1466	// Ergonomic usage pattern: counter increment (the motivating use case)
1467	// =======================================================================
1468
1469	#[test]
1470	fn ergonomic_counter_increment() {
1471		// Simulates struct field usage: my_account.count += 1;
1472		let mut count = PodU64::from(0u64);
1473		count += 1u64;
1474		assert_eq!(count.get(), 1);
1475		count += 1u64;
1476		assert_eq!(count.get(), 2);
1477	}
1478
1479	#[test]
1480	fn ergonomic_balance_arithmetic() {
1481		let mut balance = PodU64::from(1000u64);
1482		let fee = PodU64::from(25u64);
1483		balance -= fee;
1484		assert_eq!(balance.get(), 975);
1485	}
1486}