arbos/util/
serialization.rs

1use std::io::{self, Read, Write};
2
3use alloy_primitives::{Address, B256, U256};
4
5/// Reads a 32-byte hash from a reader.
6pub fn hash_from_reader<R: Read>(r: &mut R) -> io::Result<B256> {
7    let mut buf = [0u8; 32];
8    r.read_exact(&mut buf)?;
9    Ok(B256::from(buf))
10}
11
12/// Writes a 32-byte hash to a writer.
13pub fn hash_to_writer<W: Write>(w: &mut W, hash: &B256) -> io::Result<()> {
14    w.write_all(hash.as_slice())
15}
16
17/// Reads a U256 from a reader (big-endian).
18pub fn uint256_from_reader<R: Read>(r: &mut R) -> io::Result<U256> {
19    let hash = hash_from_reader(r)?;
20    Ok(U256::from_be_bytes(hash.0))
21}
22
23/// Reads a 20-byte address from a reader.
24pub fn address_from_reader<R: Read>(r: &mut R) -> io::Result<Address> {
25    let mut buf = [0u8; 20];
26    r.read_exact(&mut buf)?;
27    Ok(Address::from(buf))
28}
29
30/// Writes a 20-byte address to a writer.
31pub fn address_to_writer<W: Write>(w: &mut W, addr: &Address) -> io::Result<()> {
32    w.write_all(addr.as_slice())
33}
34
35/// Reads a 32-byte padded address from a reader (last 20 bytes are the address).
36pub fn address_from_256_from_reader<R: Read>(r: &mut R) -> io::Result<Address> {
37    let hash = hash_from_reader(r)?;
38    Ok(Address::from_slice(&hash[12..]))
39}
40
41/// Writes a 32-byte padded address to a writer (left-padded with zeros).
42pub fn address_to_256_to_writer<W: Write>(w: &mut W, addr: &Address) -> io::Result<()> {
43    let mut buf = [0u8; 32];
44    buf[12..].copy_from_slice(addr.as_slice());
45    w.write_all(&buf)
46}
47
48/// Reads a uint64 from a reader (big-endian).
49pub fn uint64_from_reader<R: Read>(r: &mut R) -> io::Result<u64> {
50    let mut buf = [0u8; 8];
51    r.read_exact(&mut buf)?;
52    Ok(u64::from_be_bytes(buf))
53}
54
55/// Writes a uint64 to a writer (big-endian).
56pub fn uint64_to_writer<W: Write>(w: &mut W, val: u64) -> io::Result<()> {
57    w.write_all(&val.to_be_bytes())
58}
59
60/// Reads a length-prefixed byte string from a reader, rejecting lengths
61/// above `max_bytes_to_read`. Every caller must pass an explicit cap
62/// sized to the protocol context (typically `MAX_L2_MESSAGE_SIZE`) so
63/// an attacker-controlled length prefix can't trigger a huge pre-allocation.
64pub fn bytestring_from_reader<R: Read>(r: &mut R, max_bytes_to_read: u64) -> io::Result<Vec<u8>> {
65    let len_u64 = uint64_from_reader(r)?;
66    if len_u64 > max_bytes_to_read {
67        return Err(io::Error::new(
68            io::ErrorKind::InvalidData,
69            format!("byte-string length {len_u64} exceeds max {max_bytes_to_read}"),
70        ));
71    }
72    let len = len_u64 as usize;
73    let mut buf = vec![0u8; len];
74    r.read_exact(&mut buf)?;
75    Ok(buf)
76}
77
78/// Writes a length-prefixed byte string to a writer.
79pub fn bytestring_to_writer<W: Write>(w: &mut W, data: &[u8]) -> io::Result<()> {
80    uint64_to_writer(w, data.len() as u64)?;
81    w.write_all(data)
82}
83
84/// Converts a signed integer to a B256 hash (big-endian).
85pub fn int_to_hash(val: i64) -> B256 {
86    let mut buf = [0u8; 32];
87    if val >= 0 {
88        buf[24..].copy_from_slice(&(val as u64).to_be_bytes());
89    } else {
90        // Two's complement for negative values: fill with 0xFF.
91        buf.fill(0xFF);
92        buf[24..].copy_from_slice(&(val as u64).to_be_bytes());
93    }
94    B256::from(buf)
95}
96
97/// Converts an unsigned integer to a B256 hash (big-endian).
98pub fn uint_to_hash(val: u64) -> B256 {
99    let mut buf = [0u8; 32];
100    buf[24..].copy_from_slice(&val.to_be_bytes());
101    B256::from(buf)
102}
103
104/// Converts an address to a B256 hash (left-padded with zeros).
105pub fn address_to_hash(addr: &Address) -> B256 {
106    let mut buf = [0u8; 32];
107    buf[12..].copy_from_slice(addr.as_slice());
108    B256::from(buf)
109}