ruint/support/
alloy_rlp.rs

1//! Support for the [`alloy-rlp`](https://crates.io/crates/alloy-rlp) crate.
2
3#![cfg(feature = "alloy-rlp")]
4#![cfg_attr(docsrs, doc(cfg(feature = "alloy-rlp")))]
5
6use crate::Uint;
7use alloy_rlp::{
8    length_of_length, BufMut, Decodable, Encodable, Error, Header, MaxEncodedLen,
9    MaxEncodedLenAssoc, EMPTY_STRING_CODE,
10};
11
12const MAX_BITS: usize = 55 * 8;
13
14/// Allows a [`Uint`] to be serialized as RLP.
15///
16/// See <https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/>
17impl<const BITS: usize, const LIMBS: usize> Encodable for Uint<BITS, LIMBS> {
18    #[inline]
19    fn length(&self) -> usize {
20        let bits = self.bit_len();
21        if bits <= 7 {
22            1
23        } else {
24            let bytes = (bits + 7) / 8;
25            bytes + length_of_length(bytes)
26        }
27    }
28
29    #[inline]
30    fn encode(&self, out: &mut dyn BufMut) {
31        // fast paths, avoiding allocation due to `to_be_bytes_vec`
32        match LIMBS {
33            0 => return out.put_u8(EMPTY_STRING_CODE),
34            1 => return self.limbs[0].encode(out),
35            #[allow(clippy::cast_lossless)]
36            2 => return (self.limbs[0] as u128 | ((self.limbs[1] as u128) << 64)).encode(out),
37            _ => {}
38        }
39
40        match self.bit_len() {
41            0 => out.put_u8(EMPTY_STRING_CODE),
42            1..=7 => {
43                #[allow(clippy::cast_possible_truncation)] // self < 128
44                out.put_u8(self.limbs[0] as u8);
45            }
46            bits => {
47                // avoid heap allocation in `to_be_bytes_vec`
48                // SAFETY: we don't re-use `copy`
49                #[cfg(target_endian = "little")]
50                let mut copy = *self;
51                #[cfg(target_endian = "little")]
52                let bytes = unsafe { copy.as_le_slice_mut() };
53                #[cfg(target_endian = "little")]
54                bytes.reverse();
55
56                #[cfg(target_endian = "big")]
57                let bytes = self.to_be_bytes_vec();
58
59                let leading_zero_bytes = Self::BYTES - (bits + 7) / 8;
60                let trimmed = &bytes[leading_zero_bytes..];
61                if bits > MAX_BITS {
62                    trimmed.encode(out);
63                } else {
64                    #[allow(clippy::cast_possible_truncation)] // bytes.len() < 56 < 256
65                    out.put_u8(EMPTY_STRING_CODE + trimmed.len() as u8);
66                    out.put_slice(trimmed);
67                }
68            }
69        }
70    }
71}
72
73/// Allows a [`Uint`] to be deserialized from RLP.
74///
75/// See <https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/>
76impl<const BITS: usize, const LIMBS: usize> Decodable for Uint<BITS, LIMBS> {
77    #[inline]
78    fn decode(buf: &mut &[u8]) -> Result<Self, Error> {
79        let bytes = Header::decode_bytes(buf, false)?;
80
81        // The RLP spec states that deserialized positive integers with leading zeroes
82        // get treated as invalid.
83        //
84        // See:
85        // https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/
86        //
87        // To check this, we only need to check if the first byte is zero to make sure
88        // there are no leading zeros
89        if !bytes.is_empty() && bytes[0] == 0 {
90            return Err(Error::LeadingZero);
91        }
92
93        Self::try_from_be_slice(bytes).ok_or(Error::Overflow)
94    }
95}
96
97#[cfg(feature = "generic_const_exprs")]
98unsafe impl<const BITS: usize, const LIMBS: usize>
99    MaxEncodedLen<{ Self::BYTES + length_of_length(Self::BYTES) }> for Uint<BITS, LIMBS>
100{
101}
102
103#[cfg(not(feature = "generic_const_exprs"))]
104const _: () = {
105    crate::const_for!(BITS in [0, 1, 2, 8, 16, 32, 64, 128, 160, 192, 256, 384, 512, 4096] {
106        const LIMBS: usize = crate::nlimbs(BITS);
107        const BYTES: usize = Uint::<BITS, LIMBS>::BYTES;
108        unsafe impl MaxEncodedLen<{ BYTES + length_of_length(BYTES) }> for Uint<BITS, LIMBS> {}
109    });
110};
111
112unsafe impl<const BITS: usize, const LIMBS: usize> MaxEncodedLenAssoc for Uint<BITS, LIMBS> {
113    const LEN: usize = Self::BYTES + length_of_length(Self::BYTES);
114}
115
116#[cfg(test)]
117mod test {
118    use super::*;
119    use crate::{
120        aliases::{U0, U256},
121        const_for, nlimbs,
122    };
123    use hex_literal::hex;
124    use proptest::proptest;
125
126    fn encode<T: Encodable>(value: T) -> Vec<u8> {
127        let mut buf = vec![];
128        value.encode(&mut buf);
129        buf
130    }
131
132    #[test]
133    fn test_rlp() {
134        // See <https://github.com/paritytech/parity-common/blob/436cb0827f0e3238ccb80d7d453f756d126c0615/rlp/tests/tests.rs#L214>
135        assert_eq!(encode(U0::from(0))[..], hex!("80"));
136        assert_eq!(encode(U256::from(0))[..], hex!("80"));
137        assert_eq!(encode(U256::from(15))[..], hex!("0f"));
138        assert_eq!(encode(U256::from(1024))[..], hex!("820400"));
139        assert_eq!(encode(U256::from(0x1234_5678))[..], hex!("8412345678"));
140    }
141
142    #[test]
143    fn test_roundtrip() {
144        const_for!(BITS in SIZES {
145            const LIMBS: usize = nlimbs(BITS);
146            proptest!(|(value: Uint<BITS, LIMBS>)| {
147                let serialized = encode(value);
148
149                #[cfg(feature = "rlp")]
150                {
151                    use rlp::Encodable as _;
152                    let serialized_rlp = value.rlp_bytes();
153                    assert_eq!(serialized, serialized_rlp.freeze()[..]);
154                }
155
156                assert_eq!(serialized.len(), value.length());
157                let mut reader = &serialized[..];
158                let deserialized = Uint::decode(&mut reader).unwrap();
159                assert_eq!(reader.len(), 0);
160                assert_eq!(value, deserialized);
161            });
162        });
163    }
164
165    #[test]
166    fn test_invalid_uints() {
167        // these are non-canonical because they have leading zeros
168        assert_eq!(
169            U256::decode(&mut &hex!("820000")[..]),
170            Err(Error::LeadingZero)
171        );
172        // 00 is not a valid uint
173        // See https://github.com/ethereum/go-ethereum/blob/cd2953567268777507b1ec29269315324fb5aa9c/rlp/decode_test.go#L118
174        assert_eq!(U256::decode(&mut &hex!("00")[..]), Err(Error::LeadingZero));
175        // these are non-canonical because they can fit in a single byte, i.e.
176        // 0x7f, 0x33
177        assert_eq!(
178            U256::decode(&mut &hex!("8100")[..]),
179            Err(Error::NonCanonicalSingleByte)
180        );
181        assert_eq!(
182            U256::decode(&mut &hex!("817f")[..]),
183            Err(Error::NonCanonicalSingleByte)
184        );
185        assert_eq!(
186            U256::decode(&mut &hex!("8133")[..]),
187            Err(Error::NonCanonicalSingleByte)
188        );
189    }
190}