zabi_rs/
decoder.rs

1use crate::error::ZError;
2use crate::types::{ZAddress, ZArray, ZBool, ZBytes, ZCallResult, ZRevert, ZString, ZU256};
3use core::convert::TryInto;
4use core::str;
5
6/// Read the 4-byte function selector from calldata.
7/// Returns a reference to the first 4 bytes.
8///
9/// # Example
10/// ```
11/// use zabi_rs::decoder::read_selector;
12///
13/// let calldata = [0xde, 0xad, 0xbe, 0xef, 0x00, 0x00];
14/// let selector = read_selector(&calldata).unwrap();
15/// assert_eq!(selector, &[0xde, 0xad, 0xbe, 0xef]);
16/// ```
17#[inline]
18pub fn read_selector(data: &[u8]) -> Result<&[u8; 4], ZError> {
19    if data.len() < 4 {
20        return Err(ZError::OutOfBounds(4, data.len()));
21    }
22    Ok(data[0..4].try_into().unwrap())
23}
24
25/// Returns the calldata without the 4-byte selector.
26/// Useful for passing the remaining data to tuple decoders.
27///
28/// # Example
29/// ```
30/// use zabi_rs::decoder::skip_selector;
31///
32/// let calldata = [0xde, 0xad, 0xbe, 0xef, 0x01, 0x02, 0x03];
33/// let params = skip_selector(&calldata).unwrap();
34/// assert_eq!(params, &[0x01, 0x02, 0x03]);
35/// ```
36#[inline]
37pub fn skip_selector(data: &[u8]) -> Result<&[u8], ZError> {
38    if data.len() < 4 {
39        return Err(ZError::OutOfBounds(4, data.len()));
40    }
41    Ok(&data[4..])
42}
43
44/// Helper to read a 32-byte word from a slice at a given offset.
45/// Returns reference to the array to avoid copying.
46#[inline(always)]
47pub fn peek_word(data: &[u8], offset: usize) -> Result<&[u8; 32], ZError> {
48    if offset + 32 > data.len() {
49        return Err(ZError::OutOfBounds(offset + 32, data.len()));
50    }
51    // SAFETY: We checked bounds above. The slice matches the size of [u8; 32].
52    // We cast the pointer to &[u8; 32].
53    // Note: slice.as_ptr() returns *const u8.
54    // We strictly use normal safe Rust usually, specifically `try_into`.
55    // But to ensure zero-copy and 'reference' semantics, we rely on slice conversion.
56
57    let slice = &data[offset..offset + 32];
58    let array_ref: &[u8; 32] = slice
59        .try_into()
60        .map_err(|_| ZError::Custom("Slice conversion failed"))?;
61    Ok(array_ref)
62}
63
64/// Helper to read a 32-byte word without bounds checking.
65///
66/// # Safety
67/// Caller must ensure `offset + 32 <= data.len()`.
68#[inline(always)]
69pub unsafe fn peek_word_unchecked(data: &[u8], offset: usize) -> &[u8; 32] {
70    // SAFETY: Caller guarantees offset + 32 <= data.len().
71    let slice = data.get_unchecked(offset..offset + 32);
72    // Transmute slice to array reference
73    // SAFETY: The slice has length 32, so it's valid to cast to &[u8; 32].
74    // Alignment of u8 is 1, so alignment requirements are met.
75    &*(slice.as_ptr() as *const [u8; 32])
76}
77
78/// Helper to read address (last 20 bytes of a 32-byte word).
79#[inline(always)]
80pub fn read_address_from_word(data: &[u8], offset: usize) -> Result<ZAddress<'_>, ZError> {
81    let word = peek_word(data, offset)?;
82    // Address is the last 20 bytes of the 32-byte word.
83    let addr_slice = &word[12..32];
84    let addr_ref: &[u8; 20] = addr_slice
85        .try_into()
86        .map_err(|_| ZError::Custom("Address slice conversion failed"))?;
87    Ok(ZAddress(addr_ref))
88}
89
90/// Read address without bounds checking.
91///
92/// # Safety
93/// Caller must ensure `offset + 32 <= data.len()`.
94#[inline(always)]
95pub unsafe fn read_address_unchecked(data: &[u8], offset: usize) -> ZAddress<'_> {
96    // SAFETY: Caller guarantees offset + 32 <= data.len().
97    let word = peek_word_unchecked(data, offset);
98    // SAFETY: 12..32 is within 0..32 bounds of the word.
99    let addr_slice = word.get_unchecked(12..32);
100    // SAFETY: Slice is length 20, cast to [u8; 20] is valid.
101    let addr_ref = &*(addr_slice.as_ptr() as *const [u8; 20]);
102    ZAddress(addr_ref)
103}
104
105#[inline(always)]
106pub fn read_u256(data: &[u8], offset: usize) -> Result<ZU256<'_>, ZError> {
107    let word = peek_word(data, offset)?;
108    Ok(ZU256(word))
109}
110
111/// Read uint256 without bounds checking.
112///
113/// # Safety
114/// Caller must ensure `offset + 32 <= data.len()`.
115#[inline(always)]
116pub unsafe fn read_u256_unchecked(data: &[u8], offset: usize) -> ZU256<'_> {
117    // SAFETY: Caller guarantees offset check.
118    let word = peek_word_unchecked(data, offset);
119    ZU256(word)
120}
121
122#[inline(always)]
123pub fn read_int256(data: &[u8], offset: usize) -> Result<crate::types::ZInt256<'_>, ZError> {
124    let word = peek_word(data, offset)?;
125    Ok(crate::types::ZInt256(word))
126}
127
128#[inline(always)]
129pub fn read_u8(data: &[u8], offset: usize) -> Result<u8, ZError> {
130    let word = peek_word(data, offset)?;
131    // Check padding (bytes 0..31 must be 0)
132    if word.iter().take(31).any(|&b| b != 0) {
133        return Err(ZError::Custom("u8 value invalid (high bits set)"));
134    }
135    Ok(word[31])
136}
137
138#[inline(always)]
139pub fn read_i8(data: &[u8], offset: usize) -> Result<i8, ZError> {
140    let word = peek_word(data, offset)?;
141    let val = word[31] as i8;
142    let padding_byte = if val < 0 { 0xff } else { 0x00 };
143    if word.iter().take(31).any(|&b| b != padding_byte) {
144        return Err(ZError::Custom("i8 value invalid (bad padding)"));
145    }
146    Ok(val)
147}
148
149#[inline(always)]
150pub fn read_u16(data: &[u8], offset: usize) -> Result<u16, ZError> {
151    let word = peek_word(data, offset)?;
152    if !word[0..30].iter().all(|&b| b == 0) {
153        return Err(ZError::Custom("u16 value invalid (high bits set)"));
154    }
155    Ok(u16::from_be_bytes([word[30], word[31]]))
156}
157
158#[inline(always)]
159pub fn read_i16(data: &[u8], offset: usize) -> Result<i16, ZError> {
160    let word = peek_word(data, offset)?;
161    let val = i16::from_be_bytes([word[30], word[31]]);
162    let padding_byte = if val < 0 { 0xff } else { 0x00 };
163    if !word[0..30].iter().all(|&b| b == padding_byte) {
164        return Err(ZError::Custom("i16 value invalid (bad padding)"));
165    }
166    Ok(val)
167}
168
169#[inline(always)]
170pub fn read_u32(data: &[u8], offset: usize) -> Result<u32, ZError> {
171    let word = peek_word(data, offset)?;
172    if !word[0..28].iter().all(|&b| b == 0) {
173        return Err(ZError::Custom("u32 value invalid (high bits set)"));
174    }
175    // Safe slice access
176    Ok(u32::from_be_bytes(word[28..32].try_into().unwrap()))
177}
178
179#[inline(always)]
180pub fn read_i32(data: &[u8], offset: usize) -> Result<i32, ZError> {
181    let word = peek_word(data, offset)?;
182    let val = i32::from_be_bytes(word[28..32].try_into().unwrap());
183    let padding_byte = if val < 0 { 0xff } else { 0x00 };
184    if !word[0..28].iter().all(|&b| b == padding_byte) {
185        return Err(ZError::Custom("i32 value invalid (bad padding)"));
186    }
187    Ok(val)
188}
189
190#[inline(always)]
191pub fn read_u64(data: &[u8], offset: usize) -> Result<u64, ZError> {
192    let word = peek_word(data, offset)?;
193    if !word[0..24].iter().all(|&b| b == 0) {
194        return Err(ZError::Custom("u64 value invalid (high bits set)"));
195    }
196    Ok(u64::from_be_bytes(word[24..32].try_into().unwrap()))
197}
198
199#[inline(always)]
200pub fn read_i64(data: &[u8], offset: usize) -> Result<i64, ZError> {
201    let word = peek_word(data, offset)?;
202    let val = i64::from_be_bytes(word[24..32].try_into().unwrap());
203    let padding_byte = if val < 0 { 0xff } else { 0x00 };
204    if !word[0..24].iter().all(|&b| b == padding_byte) {
205        return Err(ZError::Custom("i64 value invalid (bad padding)"));
206    }
207    Ok(val)
208}
209
210#[inline(always)]
211pub fn read_u128(data: &[u8], offset: usize) -> Result<u128, ZError> {
212    let word = peek_word(data, offset)?;
213    if !word[0..16].iter().all(|&b| b == 0) {
214        return Err(ZError::Custom("u128 value invalid (high bits set)"));
215    }
216    Ok(u128::from_be_bytes(word[16..32].try_into().unwrap()))
217}
218
219#[inline(always)]
220pub fn read_i128(data: &[u8], offset: usize) -> Result<i128, ZError> {
221    let word = peek_word(data, offset)?;
222    let val = i128::from_be_bytes(word[16..32].try_into().unwrap());
223    let padding_byte = if val < 0 { 0xff } else { 0x00 };
224    if !word[0..16].iter().all(|&b| b == padding_byte) {
225        return Err(ZError::Custom("i128 value invalid (bad padding)"));
226    }
227    Ok(val)
228}
229
230#[inline(always)]
231pub fn read_bool(data: &[u8], offset: usize) -> Result<ZBool, ZError> {
232    let word = peek_word(data, offset)?;
233    // Bool is uint256, last byte is 0 or 1.
234    // We should check that all other bytes are 0?
235    // Solidity requires clean high bits.
236
237    let is_zero = word[0..31].iter().all(|&b| b == 0);
238    if !is_zero {
239        return Err(ZError::Custom("Boolean value has dirty high bits"));
240    }
241
242    match word[31] {
243        0 => Ok(ZBool(false)),
244        1 => Ok(ZBool(true)),
245        _ => Err(ZError::Custom("Boolean value invalid (not 0 or 1)")),
246    }
247}
248
249/// Decodes dynamic bytes (length prefixed).
250/// The offset points to the 'Head' which contains the relative offset to the data.
251/// We follow the pointer to find the length word, then the data.
252pub fn read_bytes(data: &[u8], initial_offset: usize) -> Result<ZBytes<'_>, ZError> {
253    // 1. Read the relative offset from the head.
254    let offset_word = peek_word(data, initial_offset)?;
255    let data_offset_usize = usize::from_be_bytes(offset_word[24..32].try_into().unwrap()); // Last 8 bytes for usize is safe assumption for now < 2^64
256
257    // ABI encoding offsets are usually absolute from the start of the encoded tuple?
258    // Wait, in dynamic types, the value in the "static" part is the offset from the START of the current encoding.
259    // If we assume `data` is the full encoding block.
260
261    if data_offset_usize >= data.len() {
262        return Err(ZError::OutOfBounds(data_offset_usize, data.len()));
263    }
264
265    // 2. Read length of bytes at the data location.
266    let len_word = peek_word(data, data_offset_usize)?;
267    let length = usize::from_be_bytes(len_word[24..32].try_into().unwrap());
268
269    // 3. Read the actual data bytes.
270    let start = data_offset_usize + 32;
271    let end = start + length;
272
273    if end > data.len() {
274        return Err(ZError::OutOfBounds(end, data.len()));
275    }
276
277    Ok(ZBytes(&data[start..end]))
278}
279
280pub fn read_string(data: &[u8], initial_offset: usize) -> Result<ZString<'_>, ZError> {
281    let zbytes = read_bytes(data, initial_offset)?;
282    let s = str::from_utf8(zbytes.0).map_err(|_| ZError::Custom("Invalid UTF-8 string"))?;
283    Ok(ZString(s))
284}
285
286pub fn read_array_fixed<'a, T>(
287    data: &'a [u8],
288    offset: usize,
289    length: usize,
290) -> Result<ZArray<'a, T>, ZError> {
291    // Basic bounds check for the whole block
292    let end = offset + length * 32;
293    if end > data.len() {
294        return Err(ZError::OutOfBounds(end, data.len()));
295    }
296    Ok(ZArray::new(data, offset, length))
297}
298
299pub fn read_array_dyn<'a, T>(
300    data: &'a [u8],
301    initial_offset: usize,
302) -> Result<ZArray<'a, T>, ZError> {
303    // 1. Read offset to array (relative to current position in tuple, usually passed as offset 0?)
304    // No, initial_offset points to the 'Head' word containing the offset.
305    let offset_word = peek_word(data, initial_offset)?;
306    let data_offset_usize = usize::from_be_bytes(offset_word[24..32].try_into().unwrap());
307
308    if data_offset_usize >= data.len() {
309        return Err(ZError::OutOfBounds(data_offset_usize, data.len()));
310    }
311
312    // 2. Read length
313    let len_word = peek_word(data, data_offset_usize)?;
314    let length = usize::from_be_bytes(len_word[24..32].try_into().unwrap());
315
316    // 3. Start of data is 32 bytes after the length word
317    let start_offset = data_offset_usize + 32;
318
319    // Bounds check?
320    // start_offset + length * 32
321    if start_offset + length * 32 > data.len() {
322        return Err(ZError::OutOfBounds(start_offset + length * 32, data.len()));
323    }
324
325    Ok(ZArray::new(data, start_offset, length))
326}
327
328/// Decodes an Ethereum revert reason from raw data.
329pub fn decode_revert(data: &[u8]) -> Result<ZRevert<'_>, ZError> {
330    if data.is_empty() {
331        return Ok(ZRevert::Unknown);
332    }
333
334    if data.len() < 4 {
335        return Ok(ZRevert::Unknown);
336    }
337
338    let selector = read_selector(data)?;
339    let params = skip_selector(data)?;
340
341    match selector {
342        // Error(string) -> 0x08c379a0
343        [0x08, 0xc3, 0x79, 0xa0] => {
344            let s = read_string(params, 0)?;
345            Ok(ZRevert::Error(s))
346        }
347        // Panic(uint256) -> 0x4e487b71
348        [0x4e, 0x48, 0x7b, 0x71] => {
349            let p = read_u256(params, 0)?;
350            Ok(ZRevert::Panic(p))
351        }
352        _ => {
353            // Treat as custom error
354            Ok(ZRevert::Custom(selector, params))
355        }
356    }
357}
358
359/// Decodes the result of a function call.
360/// If the call was successful (not a revert), it decodes T from the data.
361/// If the data is identified as a revert, it returns ZCallResult::Revert.
362pub fn decode_call_result<'a, T>(data: &'a [u8]) -> Result<ZCallResult<'a, T>, ZError>
363where
364    T: crate::ZDecode<'a>,
365{
366    // A heuristic for identifying reverts:
367    // Reverts are usually short or have specific selectors.
368    // However, a successful return could also have those selectors if T is bytes or similar.
369    // In practice, we usually know if a call reverted from the RPC response.
370    // But for raw data parsing (e.g. from traces), we can try to guess.
371
372    if data.len() >= 4 {
373        let sel = &data[0..4];
374        if sel == [0x08, 0xc3, 0x79, 0xa0] || sel == [0x4e, 0x48, 0x7b, 0x71] {
375            return Ok(ZCallResult::Revert(decode_revert(data)?));
376        }
377    }
378
379    Ok(ZCallResult::Success(T::decode(data, 0)?))
380}