alloy_primitives/bits/
fixed.rs

1use crate::aliases;
2use core::{fmt, iter, ops, str};
3use derive_more::{Deref, DerefMut, From, Index, IndexMut, IntoIterator};
4use hex::FromHex;
5
6/// A byte array of fixed length (`[u8; N]`).
7///
8/// This type allows us to more tightly control serialization, deserialization.
9/// rlp encoding, decoding, and other type-level attributes for fixed-length
10/// byte arrays.
11///
12/// Users looking to prevent type-confusion between byte arrays of different
13/// lengths should use the [`wrap_fixed_bytes!`](crate::wrap_fixed_bytes) macro
14/// to create a new fixed-length byte array type.
15#[derive(
16    Clone,
17    Copy,
18    PartialEq,
19    Eq,
20    PartialOrd,
21    Ord,
22    Hash,
23    Deref,
24    DerefMut,
25    From,
26    Index,
27    IndexMut,
28    IntoIterator,
29)]
30#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary, proptest_derive::Arbitrary))]
31#[cfg_attr(feature = "allocative", derive(allocative::Allocative))]
32#[cfg_attr(feature = "diesel", derive(diesel::AsExpression, diesel::FromSqlRow))]
33#[cfg_attr(feature = "diesel", diesel(sql_type = diesel::sql_types::Binary))]
34#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
35#[repr(transparent)]
36pub struct FixedBytes<const N: usize>(#[into_iterator(owned, ref, ref_mut)] pub [u8; N]);
37
38crate::impl_fb_traits!(FixedBytes<N>, N, const);
39
40impl<const N: usize> Default for FixedBytes<N> {
41    #[inline]
42    fn default() -> Self {
43        Self::ZERO
44    }
45}
46
47impl<const N: usize> Default for &FixedBytes<N> {
48    #[inline]
49    fn default() -> Self {
50        &FixedBytes::ZERO
51    }
52}
53
54impl<const N: usize> From<&[u8; N]> for FixedBytes<N> {
55    #[inline]
56    fn from(bytes: &[u8; N]) -> Self {
57        Self(*bytes)
58    }
59}
60
61impl<const N: usize> From<&mut [u8; N]> for FixedBytes<N> {
62    #[inline]
63    fn from(bytes: &mut [u8; N]) -> Self {
64        Self(*bytes)
65    }
66}
67
68/// Tries to create a `FixedBytes<N>` by copying from a slice `&[u8]`. Succeeds
69/// if `slice.len() == N`.
70impl<const N: usize> TryFrom<&[u8]> for FixedBytes<N> {
71    type Error = core::array::TryFromSliceError;
72
73    #[inline]
74    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
75        <&Self>::try_from(slice).copied()
76    }
77}
78
79/// Tries to create a `FixedBytes<N>` by copying from a mutable slice `&mut
80/// [u8]`. Succeeds if `slice.len() == N`.
81impl<const N: usize> TryFrom<&mut [u8]> for FixedBytes<N> {
82    type Error = core::array::TryFromSliceError;
83
84    #[inline]
85    fn try_from(slice: &mut [u8]) -> Result<Self, Self::Error> {
86        Self::try_from(&*slice)
87    }
88}
89
90/// Tries to create a ref `FixedBytes<N>` by copying from a slice `&[u8]`.
91/// Succeeds if `slice.len() == N`.
92impl<'a, const N: usize> TryFrom<&'a [u8]> for &'a FixedBytes<N> {
93    type Error = core::array::TryFromSliceError;
94
95    #[inline]
96    fn try_from(slice: &'a [u8]) -> Result<&'a FixedBytes<N>, Self::Error> {
97        // SAFETY: `FixedBytes<N>` is `repr(transparent)` for `[u8; N]`
98        <&[u8; N]>::try_from(slice).map(|array_ref| unsafe { core::mem::transmute(array_ref) })
99    }
100}
101
102/// Tries to create a ref `FixedBytes<N>` by copying from a mutable slice `&mut
103/// [u8]`. Succeeds if `slice.len() == N`.
104impl<'a, const N: usize> TryFrom<&'a mut [u8]> for &'a mut FixedBytes<N> {
105    type Error = core::array::TryFromSliceError;
106
107    #[inline]
108    fn try_from(slice: &'a mut [u8]) -> Result<&'a mut FixedBytes<N>, Self::Error> {
109        // SAFETY: `FixedBytes<N>` is `repr(transparent)` for `[u8; N]`
110        <&mut [u8; N]>::try_from(slice).map(|array_ref| unsafe { core::mem::transmute(array_ref) })
111    }
112}
113
114// Ideally this would be:
115// `impl<const N: usize> From<FixedBytes<N>> for Uint<N * 8>`
116// `impl<const N: usize> From<Uint<N / 8>> for FixedBytes<N>`
117macro_rules! fixed_bytes_uint_conversions {
118    ($($int:ty => $fb:ty),* $(,)?) => {$(
119        impl From<$int> for $fb {
120            /// Converts a fixed-width unsigned integer into a fixed byte array
121            /// by interpreting the bytes as big-endian.
122            #[inline]
123            fn from(value: $int) -> Self {
124                Self(value.to_be_bytes())
125            }
126        }
127
128        impl From<$fb> for $int {
129            /// Converts a fixed byte array into a fixed-width unsigned integer
130            /// by interpreting the bytes as big-endian.
131            #[inline]
132            fn from(value: $fb) -> Self {
133                Self::from_be_bytes(value.0)
134            }
135        }
136
137        const _: () = assert!(<$int>::BITS as usize == <$fb>::len_bytes() * 8);
138    )*};
139}
140
141fixed_bytes_uint_conversions! {
142    u8            => aliases::B8,
143    aliases::U8   => aliases::B8,
144    i8            => aliases::B8,
145    aliases::I8   => aliases::B8,
146
147    u16           => aliases::B16,
148    aliases::U16  => aliases::B16,
149    i16           => aliases::B16,
150    aliases::I16  => aliases::B16,
151
152    u32           => aliases::B32,
153    aliases::U32  => aliases::B32,
154    i32           => aliases::B32,
155    aliases::I32  => aliases::B32,
156
157    u64           => aliases::B64,
158    aliases::U64  => aliases::B64,
159    i64           => aliases::B64,
160    aliases::I64  => aliases::B64,
161
162    u128          => aliases::B128,
163    aliases::U128 => aliases::B128,
164    i128          => aliases::B128,
165    aliases::I128 => aliases::B128,
166
167    aliases::U160 => aliases::B160,
168    aliases::I160 => aliases::B160,
169
170    aliases::U256 => aliases::B256,
171    aliases::I256 => aliases::B256,
172
173    aliases::U512 => aliases::B512,
174    aliases::I512 => aliases::B512,
175
176}
177
178impl<const N: usize> From<FixedBytes<N>> for [u8; N] {
179    #[inline]
180    fn from(s: FixedBytes<N>) -> Self {
181        s.0
182    }
183}
184
185impl<const N: usize> AsRef<[u8; N]> for FixedBytes<N> {
186    #[inline]
187    fn as_ref(&self) -> &[u8; N] {
188        &self.0
189    }
190}
191
192impl<const N: usize> AsMut<[u8; N]> for FixedBytes<N> {
193    #[inline]
194    fn as_mut(&mut self) -> &mut [u8; N] {
195        &mut self.0
196    }
197}
198
199impl<const N: usize> AsRef<[u8]> for FixedBytes<N> {
200    #[inline]
201    fn as_ref(&self) -> &[u8] {
202        &self.0
203    }
204}
205
206impl<const N: usize> AsMut<[u8]> for FixedBytes<N> {
207    #[inline]
208    fn as_mut(&mut self) -> &mut [u8] {
209        &mut self.0
210    }
211}
212
213impl<const N: usize> fmt::Debug for FixedBytes<N> {
214    #[inline]
215    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216        self.fmt_hex::<false>(f, true)
217    }
218}
219
220impl<const N: usize> fmt::Display for FixedBytes<N> {
221    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222        // If the alternate flag is NOT set, we write the full hex.
223        if N <= 4 || !f.alternate() {
224            return self.fmt_hex::<false>(f, true);
225        }
226
227        // If the alternate flag is set, we use middle-out compression.
228        const SEP_LEN: usize = '…'.len_utf8();
229        let mut buf = [0; 2 + 4 + SEP_LEN + 4];
230        buf[0] = b'0';
231        buf[1] = b'x';
232        hex::encode_to_slice(&self.0[0..2], &mut buf[2..6]).unwrap();
233        '…'.encode_utf8(&mut buf[6..]);
234        hex::encode_to_slice(&self.0[N - 2..N], &mut buf[6 + SEP_LEN..]).unwrap();
235
236        // SAFETY: always valid UTF-8
237        f.write_str(unsafe { str::from_utf8_unchecked(&buf) })
238    }
239}
240
241impl<const N: usize> fmt::LowerHex for FixedBytes<N> {
242    #[inline]
243    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244        self.fmt_hex::<false>(f, f.alternate())
245    }
246}
247
248impl<const N: usize> fmt::UpperHex for FixedBytes<N> {
249    #[inline]
250    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251        self.fmt_hex::<true>(f, f.alternate())
252    }
253}
254
255impl<const N: usize> ops::BitAndAssign for FixedBytes<N> {
256    #[inline]
257    fn bitand_assign(&mut self, rhs: Self) {
258        *self &= &rhs;
259    }
260}
261
262impl<const N: usize> ops::BitOrAssign for FixedBytes<N> {
263    #[inline]
264    fn bitor_assign(&mut self, rhs: Self) {
265        *self |= &rhs;
266    }
267}
268
269impl<const N: usize> ops::BitXorAssign for FixedBytes<N> {
270    #[inline]
271    fn bitxor_assign(&mut self, rhs: Self) {
272        *self ^= &rhs;
273    }
274}
275
276impl<const N: usize> ops::BitAndAssign<&Self> for FixedBytes<N> {
277    #[inline]
278    fn bitand_assign(&mut self, rhs: &Self) {
279        iter::zip(self, rhs).for_each(|(a, b)| *a &= *b);
280    }
281}
282
283impl<const N: usize> ops::BitOrAssign<&Self> for FixedBytes<N> {
284    #[inline]
285    fn bitor_assign(&mut self, rhs: &Self) {
286        iter::zip(self, rhs).for_each(|(a, b)| *a |= *b);
287    }
288}
289
290impl<const N: usize> ops::BitXorAssign<&Self> for FixedBytes<N> {
291    #[inline]
292    fn bitxor_assign(&mut self, rhs: &Self) {
293        iter::zip(self, rhs).for_each(|(a, b)| *a ^= *b);
294    }
295}
296
297impl<const N: usize> ops::BitAnd for FixedBytes<N> {
298    type Output = Self;
299
300    #[inline]
301    fn bitand(mut self, rhs: Self) -> Self::Output {
302        self &= &rhs;
303        self
304    }
305}
306
307impl<const N: usize> ops::BitOr for FixedBytes<N> {
308    type Output = Self;
309
310    #[inline]
311    fn bitor(mut self, rhs: Self) -> Self::Output {
312        self |= &rhs;
313        self
314    }
315}
316
317impl<const N: usize> ops::BitXor for FixedBytes<N> {
318    type Output = Self;
319
320    #[inline]
321    fn bitxor(mut self, rhs: Self) -> Self::Output {
322        self ^= &rhs;
323        self
324    }
325}
326
327impl<const N: usize> ops::BitAnd<&Self> for FixedBytes<N> {
328    type Output = Self;
329
330    #[inline]
331    fn bitand(mut self, rhs: &Self) -> Self::Output {
332        self &= rhs;
333        self
334    }
335}
336
337impl<const N: usize> ops::BitOr<&Self> for FixedBytes<N> {
338    type Output = Self;
339
340    #[inline]
341    fn bitor(mut self, rhs: &Self) -> Self::Output {
342        self |= rhs;
343        self
344    }
345}
346
347impl<const N: usize> ops::BitXor<&Self> for FixedBytes<N> {
348    type Output = Self;
349
350    #[inline]
351    fn bitxor(mut self, rhs: &Self) -> Self::Output {
352        self ^= rhs;
353        self
354    }
355}
356
357impl<const N: usize> ops::Not for FixedBytes<N> {
358    type Output = Self;
359
360    #[inline]
361    fn not(mut self) -> Self::Output {
362        self.iter_mut().for_each(|byte| *byte = !*byte);
363        self
364    }
365}
366
367impl<const N: usize> str::FromStr for FixedBytes<N> {
368    type Err = hex::FromHexError;
369
370    #[inline]
371    fn from_str(s: &str) -> Result<Self, Self::Err> {
372        Self::from_hex(s)
373    }
374}
375
376#[cfg(feature = "rand")]
377impl<const N: usize> rand::distr::Distribution<FixedBytes<N>> for rand::distr::StandardUniform {
378    #[inline]
379    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> FixedBytes<N> {
380        FixedBytes::random_with(rng)
381    }
382}
383
384impl<const N: usize> FixedBytes<N> {
385    /// Array of Zero bytes.
386    pub const ZERO: Self = Self([0u8; N]);
387
388    /// Wraps the given byte array in [`FixedBytes`].
389    #[inline]
390    pub const fn new(bytes: [u8; N]) -> Self {
391        Self(bytes)
392    }
393
394    /// Creates a new [`FixedBytes`] with the last byte set to `x`.
395    #[inline]
396    pub const fn with_last_byte(x: u8) -> Self {
397        let mut bytes = [0u8; N];
398        if N > 0 {
399            bytes[N - 1] = x;
400        }
401        Self(bytes)
402    }
403
404    /// Creates a new [`FixedBytes`] where all bytes are set to `byte`.
405    #[inline]
406    pub const fn repeat_byte(byte: u8) -> Self {
407        Self([byte; N])
408    }
409
410    /// Returns the size of this byte array (`N`).
411    #[inline(always)]
412    pub const fn len_bytes() -> usize {
413        N
414    }
415
416    /// Creates a new [`FixedBytes`] with the default cryptographic random number generator.
417    ///
418    /// This is `rand::thread_rng` if the "rand" and "std" features are enabled, otherwise
419    /// it uses `getrandom::getrandom`. Both are cryptographically secure.
420    #[cfg(feature = "getrandom")]
421    #[inline]
422    #[track_caller]
423    pub fn random() -> Self {
424        let mut bytes = Self::ZERO;
425        bytes.randomize();
426        bytes
427    }
428
429    /// Tries to create a new [`FixedBytes`] with the default cryptographic random number
430    /// generator.
431    ///
432    /// See [`random`](Self::random) for more details.
433    #[cfg(feature = "getrandom")]
434    #[inline]
435    pub fn try_random() -> Result<Self, getrandom::Error> {
436        let mut bytes = Self::ZERO;
437        bytes.try_randomize()?;
438        Ok(bytes)
439    }
440
441    /// Creates a new [`FixedBytes`] with the given random number generator.
442    ///
443    /// See [`random`](Self::random) for more details.
444    #[cfg(feature = "rand")]
445    #[inline]
446    #[doc(alias = "random_using")]
447    pub fn random_with<R: rand::RngCore + ?Sized>(rng: &mut R) -> Self {
448        let mut bytes = Self::ZERO;
449        bytes.randomize_with(rng);
450        bytes
451    }
452
453    /// Tries to create a new [`FixedBytes`] with the given random number generator.
454    #[cfg(feature = "rand")]
455    #[inline]
456    pub fn try_random_with<R: rand::TryRngCore + ?Sized>(rng: &mut R) -> Result<Self, R::Error> {
457        let mut bytes = Self::ZERO;
458        bytes.try_randomize_with(rng)?;
459        Ok(bytes)
460    }
461
462    /// Fills this [`FixedBytes`] with the default cryptographic random number generator.
463    ///
464    /// See [`random`](Self::random) for more details.
465    #[cfg(feature = "getrandom")]
466    #[inline]
467    #[track_caller]
468    pub fn randomize(&mut self) {
469        self.try_randomize().unwrap_or_else(|e| panic!("failed to fill with random bytes: {e}"));
470    }
471
472    /// Tries to fill this [`FixedBytes`] with the default cryptographic random number
473    /// generator.
474    ///
475    /// See [`random`](Self::random) for more details.
476    #[inline]
477    #[cfg(feature = "getrandom")]
478    pub fn try_randomize(&mut self) -> Result<(), getrandom::Error> {
479        #[cfg(all(feature = "rand", feature = "std"))]
480        {
481            self.randomize_with(&mut rand::rng());
482            Ok(())
483        }
484        #[cfg(not(all(feature = "rand", feature = "std")))]
485        {
486            getrandom::fill(&mut self.0)
487        }
488    }
489
490    /// Fills this [`FixedBytes`] with the given random number generator.
491    #[cfg(feature = "rand")]
492    #[inline]
493    #[doc(alias = "randomize_using")]
494    pub fn randomize_with<R: rand::RngCore + ?Sized>(&mut self, rng: &mut R) {
495        rng.fill_bytes(&mut self.0);
496    }
497
498    /// Tries to fill this [`FixedBytes`] with the given random number generator.
499    #[inline]
500    #[cfg(feature = "rand")]
501    pub fn try_randomize_with<R: rand::TryRngCore + ?Sized>(
502        &mut self,
503        rng: &mut R,
504    ) -> Result<(), R::Error> {
505        rng.try_fill_bytes(&mut self.0)
506    }
507
508    /// Concatenate two `FixedBytes`.
509    ///
510    /// Due to constraints in the language, the user must specify the value of
511    /// the output size `Z`.
512    ///
513    /// # Panics
514    ///
515    /// Panics if `Z` is not equal to `N + M`.
516    pub const fn concat_const<const M: usize, const Z: usize>(
517        self,
518        other: FixedBytes<M>,
519    ) -> FixedBytes<Z> {
520        assert!(N + M == Z, "Output size `Z` must equal the sum of the input sizes `N` and `M`");
521
522        let mut result = [0u8; Z];
523        let mut i = 0;
524        while i < Z {
525            result[i] = if i >= N { other.0[i - N] } else { self.0[i] };
526            i += 1;
527        }
528        FixedBytes(result)
529    }
530
531    /// Create a new [`FixedBytes`] from the given slice `src`.
532    ///
533    /// For a fallible version, use the `TryFrom<&[u8]>` implementation.
534    ///
535    /// # Note
536    ///
537    /// The given bytes are interpreted in big endian order.
538    ///
539    /// # Panics
540    ///
541    /// If the length of `src` and the number of bytes in `Self` do not match.
542    #[inline]
543    #[track_caller]
544    pub fn from_slice(src: &[u8]) -> Self {
545        match Self::try_from(src) {
546            Ok(x) => x,
547            Err(_) => panic!("cannot convert a slice of length {} to FixedBytes<{N}>", src.len()),
548        }
549    }
550
551    /// Create a new [`FixedBytes`] from the given slice `src`, left-padding it
552    /// with zeroes if necessary.
553    ///
554    /// # Note
555    ///
556    /// The given bytes are interpreted in big endian order.
557    ///
558    /// # Panics
559    ///
560    /// Panics if `src.len() > N`.
561    #[inline]
562    #[track_caller]
563    pub fn left_padding_from(value: &[u8]) -> Self {
564        let len = value.len();
565        assert!(len <= N, "slice is too large. Expected <={N} bytes, got {len}");
566        let mut bytes = Self::ZERO;
567        bytes[N - len..].copy_from_slice(value);
568        bytes
569    }
570
571    /// Create a new [`FixedBytes`] from the given slice `src`, right-padding it
572    /// with zeroes if necessary.
573    ///
574    /// # Note
575    ///
576    /// The given bytes are interpreted in big endian order.
577    ///
578    /// # Panics
579    ///
580    /// Panics if `src.len() > N`.
581    #[inline]
582    #[track_caller]
583    pub fn right_padding_from(value: &[u8]) -> Self {
584        let len = value.len();
585        assert!(len <= N, "slice is too large. Expected <={N} bytes, got {len}");
586        let mut bytes = Self::ZERO;
587        bytes[..len].copy_from_slice(value);
588        bytes
589    }
590
591    /// Returns a slice containing the entire array. Equivalent to `&s[..]`.
592    #[inline]
593    pub const fn as_slice(&self) -> &[u8] {
594        &self.0
595    }
596
597    /// Returns a mutable slice containing the entire array. Equivalent to
598    /// `&mut s[..]`.
599    #[inline]
600    pub const fn as_mut_slice(&mut self) -> &mut [u8] {
601        &mut self.0
602    }
603
604    /// Returns `true` if all bits set in `self` are also set in `b`.
605    #[inline]
606    pub fn covers(&self, other: &Self) -> bool {
607        (*self & *other) == *other
608    }
609
610    /// Returns `true` if all bits set in `self` are also set in `b`.
611    pub const fn const_covers(self, other: Self) -> bool {
612        // (self & other) == other
613        other.const_eq(&self.bit_and(other))
614    }
615
616    /// Compile-time equality. NOT constant-time equality.
617    pub const fn const_eq(&self, other: &Self) -> bool {
618        let mut i = 0;
619        while i < N {
620            if self.0[i] != other.0[i] {
621                return false;
622            }
623            i += 1;
624        }
625        true
626    }
627
628    /// Returns `true` if no bits are set.
629    #[inline]
630    pub fn is_zero(&self) -> bool {
631        *self == Self::ZERO
632    }
633
634    /// Returns `true` if no bits are set.
635    #[inline]
636    pub const fn const_is_zero(&self) -> bool {
637        self.const_eq(&Self::ZERO)
638    }
639
640    /// Computes the bitwise AND of two `FixedBytes`.
641    pub const fn bit_and(self, rhs: Self) -> Self {
642        let mut ret = Self::ZERO;
643        let mut i = 0;
644        while i < N {
645            ret.0[i] = self.0[i] & rhs.0[i];
646            i += 1;
647        }
648        ret
649    }
650
651    /// Computes the bitwise OR of two `FixedBytes`.
652    pub const fn bit_or(self, rhs: Self) -> Self {
653        let mut ret = Self::ZERO;
654        let mut i = 0;
655        while i < N {
656            ret.0[i] = self.0[i] | rhs.0[i];
657            i += 1;
658        }
659        ret
660    }
661
662    /// Computes the bitwise XOR of two `FixedBytes`.
663    pub const fn bit_xor(self, rhs: Self) -> Self {
664        let mut ret = Self::ZERO;
665        let mut i = 0;
666        while i < N {
667            ret.0[i] = self.0[i] ^ rhs.0[i];
668            i += 1;
669        }
670        ret
671    }
672
673    fn fmt_hex<const UPPER: bool>(&self, f: &mut fmt::Formatter<'_>, prefix: bool) -> fmt::Result {
674        let mut buf = hex::Buffer::<N, true>::new();
675        let s = if UPPER { buf.format_upper(self) } else { buf.format(self) };
676        // SAFETY: The buffer is guaranteed to be at least 2 bytes in length.
677        f.write_str(unsafe { s.get_unchecked((!prefix as usize) * 2..) })
678    }
679}
680
681#[cfg(test)]
682mod tests {
683    use super::*;
684
685    macro_rules! test_fmt {
686        ($($fmt:literal, $hex:literal => $expected:literal;)+) => {$(
687            assert_eq!(
688                format!($fmt, fixed_bytes!($hex)),
689                $expected
690            );
691        )+};
692    }
693
694    #[test]
695    fn concat_const() {
696        const A: FixedBytes<2> = fixed_bytes!("0x0123");
697        const B: FixedBytes<2> = fixed_bytes!("0x4567");
698        const EXPECTED: FixedBytes<4> = fixed_bytes!("0x01234567");
699        const ACTUAL: FixedBytes<4> = A.concat_const(B);
700
701        assert_eq!(ACTUAL, EXPECTED);
702    }
703
704    #[test]
705    fn display() {
706        test_fmt! {
707            "{}", "0123456789abcdef" => "0x0123456789abcdef";
708            "{:#}", "0123" => "0x0123";
709            "{:#}", "01234567" => "0x01234567";
710            "{:#}", "0123456789" => "0x0123…6789";
711        }
712    }
713
714    #[test]
715    fn debug() {
716        test_fmt! {
717            "{:?}", "0123456789abcdef" => "0x0123456789abcdef";
718            "{:#?}", "0123456789abcdef" => "0x0123456789abcdef";
719        }
720    }
721
722    #[test]
723    fn lower_hex() {
724        test_fmt! {
725            "{:x}", "0123456789abcdef" => "0123456789abcdef";
726            "{:#x}", "0123456789abcdef" => "0x0123456789abcdef";
727        }
728    }
729
730    #[test]
731    fn upper_hex() {
732        test_fmt! {
733            "{:X}", "0123456789abcdef" => "0123456789ABCDEF";
734            "{:#X}", "0123456789abcdef" => "0x0123456789ABCDEF";
735        }
736    }
737
738    #[test]
739    fn left_padding_from() {
740        assert_eq!(FixedBytes::<4>::left_padding_from(&[0x01, 0x23]), fixed_bytes!("0x00000123"));
741
742        assert_eq!(
743            FixedBytes::<4>::left_padding_from(&[0x01, 0x23, 0x45, 0x67]),
744            fixed_bytes!("0x01234567")
745        );
746    }
747
748    #[test]
749    #[should_panic(expected = "slice is too large. Expected <=4 bytes, got 5")]
750    fn left_padding_from_too_large() {
751        FixedBytes::<4>::left_padding_from(&[0x01, 0x23, 0x45, 0x67, 0x89]);
752    }
753
754    #[test]
755    fn right_padding_from() {
756        assert_eq!(FixedBytes::<4>::right_padding_from(&[0x01, 0x23]), fixed_bytes!("0x01230000"));
757
758        assert_eq!(
759            FixedBytes::<4>::right_padding_from(&[0x01, 0x23, 0x45, 0x67]),
760            fixed_bytes!("0x01234567")
761        );
762    }
763
764    #[test]
765    #[should_panic(expected = "slice is too large. Expected <=4 bytes, got 5")]
766    fn right_padding_from_too_large() {
767        FixedBytes::<4>::right_padding_from(&[0x01, 0x23, 0x45, 0x67, 0x89]);
768    }
769}