ruint 1.18.0

Unsigned integer type with const-generic bit length
Documentation
//! Support for the [`fastrlp`](https://crates.io/crates/fastrlp) crate.

#![cfg(feature = "fastrlp-04")]
#![cfg_attr(docsrs, doc(cfg(feature = "fastrlp-04")))]

use crate::Uint;
use fastrlp_04::{
    BufMut, Decodable, DecodeError, EMPTY_STRING_CODE, Encodable, Header, MaxEncodedLen,
    MaxEncodedLenAssoc, length_of_length,
};

const MAX_BITS: usize = 55 * 8;

/// Allows a [`Uint`] to be serialized as RLP.
///
/// See <https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/>
impl<const BITS: usize, const LIMBS: usize> Encodable for Uint<BITS, LIMBS> {
    #[inline]
    fn length(&self) -> usize {
        let bits = self.bit_len();
        if bits <= 7 {
            1
        } else {
            let bytes = bits.div_ceil(8);
            bytes + length_of_length(bytes)
        }
    }

    #[inline]
    fn encode(&self, out: &mut dyn BufMut) {
        // fast paths, avoiding allocation due to `to_be_bytes_vec`
        match LIMBS {
            0 => return out.put_u8(EMPTY_STRING_CODE),
            1 => return self.limbs[0].encode(out),
            #[allow(clippy::cast_lossless)]
            2 => return (self.limbs[0] as u128 | ((self.limbs[1] as u128) << 64)).encode(out),
            _ => {}
        }

        match self.bit_len() {
            0 => out.put_u8(EMPTY_STRING_CODE),
            1..=7 => {
                #[allow(clippy::cast_possible_truncation)] // self < 128
                out.put_u8(self.limbs[0] as u8);
            }
            bits => {
                // avoid heap allocation in `to_be_bytes_vec`
                // SAFETY: we don't re-use `copy`
                #[cfg(target_endian = "little")]
                let mut copy = *self;
                #[cfg(target_endian = "little")]
                let bytes = unsafe { copy.as_le_slice_mut() };
                #[cfg(target_endian = "little")]
                bytes.reverse();

                #[cfg(target_endian = "big")]
                let bytes = self.to_be_bytes_vec();

                let leading_zero_bytes = Self::BYTES - bits.div_ceil(8);
                let trimmed = &bytes[leading_zero_bytes..];
                if bits > MAX_BITS {
                    trimmed.encode(out);
                } else {
                    #[allow(clippy::cast_possible_truncation)] // bytes.len() < 56 < 256
                    out.put_u8(EMPTY_STRING_CODE + trimmed.len() as u8);
                    out.put_slice(trimmed);
                }
            }
        }
    }
}

/// Allows a [`Uint`] to be deserialized from RLP.
///
/// See <https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/>
impl<const BITS: usize, const LIMBS: usize> Decodable for Uint<BITS, LIMBS> {
    #[inline]
    fn decode(buf: &mut &[u8]) -> Result<Self, DecodeError> {
        // let bytes = Header::decode_bytes(buf, false)?;
        let header = Header::decode(buf)?;
        if header.list {
            return Err(DecodeError::UnexpectedList);
        }

        let bytes = &buf[..header.payload_length];
        *buf = &buf[header.payload_length..];

        // The RLP spec states that deserialized positive integers with leading zeroes
        // get treated as invalid.
        //
        // See:
        // https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/
        //
        // To check this, we only need to check if the first byte is zero to make sure
        // there are no leading zeros
        if !bytes.is_empty() && bytes[0] == 0 {
            return Err(DecodeError::LeadingZero);
        }

        Self::try_from_be_slice(bytes).ok_or(DecodeError::Overflow)
    }
}

#[cfg(feature = "generic_const_exprs")]
unsafe impl<const BITS: usize, const LIMBS: usize>
    MaxEncodedLen<{ Self::BYTES + length_of_length(Self::BYTES) }> for Uint<BITS, LIMBS>
{
}

#[cfg(not(feature = "generic_const_exprs"))]
const _: () = {
    crate::const_for!(BITS in [0, 1, 2, 8, 16, 32, 64, 128, 160, 192, 256, 384, 512, 4096] {
        const LIMBS: usize = crate::nlimbs(BITS);
        const BYTES: usize = Uint::<BITS, LIMBS>::BYTES;
        unsafe impl MaxEncodedLen<{ BYTES + length_of_length(BYTES) }> for Uint<BITS, LIMBS> {}
    });
};

unsafe impl<const BITS: usize, const LIMBS: usize> MaxEncodedLenAssoc for Uint<BITS, LIMBS> {
    const LEN: usize = Self::BYTES + length_of_length(Self::BYTES);
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::{
        aliases::{U0, U256},
        const_for, nlimbs,
    };
    use hex_literal::hex;
    use proptest::proptest;

    fn encode<T: Encodable>(value: T) -> Vec<u8> {
        let mut buf = vec![];
        value.encode(&mut buf);
        buf
    }

    #[test]
    fn test_rlp() {
        // See <https://github.com/paritytech/parity-common/blob/436cb0827f0e3238ccb80d7d453f756d126c0615/rlp/tests/tests.rs#L214>
        assert_eq!(encode(U0::from(0))[..], hex!("80"));
        assert_eq!(encode(U256::from(0))[..], hex!("80"));
        assert_eq!(encode(U256::from(15))[..], hex!("0f"));
        assert_eq!(encode(U256::from(1024))[..], hex!("820400"));
        assert_eq!(encode(U256::from(0x1234_5678))[..], hex!("8412345678"));
    }

    #[test]
    fn test_roundtrip() {
        const_for!(BITS in SIZES {
            const LIMBS: usize = nlimbs(BITS);
            proptest!(|(value: Uint<BITS, LIMBS>)| {
                let serialized = encode(value);

                #[cfg(feature = "rlp")]
                {
                    use rlp::Encodable as _;
                    let serialized_rlp = value.rlp_bytes();
                    assert_eq!(serialized, serialized_rlp.freeze()[..]);
                }

                assert_eq!(serialized.len(), value.length());
                let mut reader = &serialized[..];
                let deserialized = Uint::decode(&mut reader).unwrap();
                assert_eq!(reader.len(), 0);
                assert_eq!(value, deserialized);
            });
        });
    }
}