arbos/util/
serialization.rs

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