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