Skip to main content

truthlinked_sdk/
abi.rs

1//! ABI (Application Binary Interface) helpers for function dispatch and calldata parsing.
2//!
3//! This module provides utilities for:
4//! - Computing function selectors from names
5//! - Extracting selectors from calldata
6//! - Reading typed values from calldata at specific offsets
7//!
8//! # Function Selectors
9//!
10//! TruthLinked uses FNV-1a 32-bit hashing for function selectors (4 bytes).
11//! This is deterministic, no-std compatible, and provides good distribution.
12//!
13//! # Example
14//!
15//! ```ignore
16//! use truthlinked_sdk::abi;
17//!
18//! // Compute selector for "transfer" function
19//! let sel = abi::selector_of("transfer");
20//!
21//! // Parse selector from calldata
22//! let calldata = context::calldata()?;
23//! let selector = abi::selector(&calldata)?;
24//!
25//! // Read arguments
26//! let amount = abi::read_u64(&calldata, 4)?;
27//! let recipient = abi::read_account(&calldata, 12)?;
28//! ```
29
30use crate::error::{Error, Result};
31
32/// Error code for malformed or insufficient calldata.
33pub const ERR_BAD_CALLDATA: i32 = 10;
34
35/// Computes a 4-byte function selector from a function name.
36///
37/// Uses FNV-1a 32-bit hash algorithm for deterministic, no-std-friendly hashing.
38/// The selector is returned in little-endian byte order.
39///
40/// # Arguments
41///
42/// * `name` - Function name (e.g., "transfer", "increment")
43///
44/// # Returns
45///
46/// A 4-byte selector that uniquely identifies the function.
47///
48/// # Example
49///
50/// ```ignore
51/// let sel = abi::selector_of("transfer");
52/// // sel is a deterministic 4-byte array
53/// ```
54pub fn selector_of(name: &str) -> [u8; 4] {
55    // FNV-1a 32-bit hash: deterministic, no-std-friendly selector hash
56    let mut hash: u32 = 0x811c9dc5;
57    for b in name.as_bytes() {
58        hash ^= *b as u32;
59        hash = hash.wrapping_mul(0x01000193);
60    }
61    hash.to_le_bytes()
62}
63
64/// Extracts the 4-byte function selector from calldata.
65///
66/// The selector is expected to be the first 4 bytes of the calldata.
67/// Returns an error if calldata is shorter than 4 bytes.
68///
69/// # Arguments
70///
71/// * `calldata` - Raw calldata bytes (selector + arguments)
72///
73/// # Returns
74///
75/// * `Ok([u8; 4])` - The extracted selector
76/// * `Err(Error)` - If calldata is too short
77///
78/// # Example
79///
80/// ```ignore
81/// let calldata = context::calldata()?;
82/// let selector = abi::selector(&calldata)?;
83///
84/// if selector == abi::selector_of("transfer") {
85///     // Handle transfer function
86/// }
87/// ```
88pub fn selector(calldata: &[u8]) -> Result<[u8; 4]> {
89    if calldata.len() < 4 {
90        return Err(Error::new(ERR_BAD_CALLDATA));
91    }
92    let mut out = [0u8; 4];
93    out.copy_from_slice(&calldata[..4]);
94    Ok(out)
95}
96
97/// Reads a `u64` value from calldata at the specified byte offset.
98///
99/// Interprets 8 bytes starting at `offset` as a little-endian `u64`.
100/// Returns an error if there aren't enough bytes available.
101///
102/// # Arguments
103///
104/// * `calldata` - Raw calldata bytes
105/// * `offset` - Byte offset where the u64 starts (typically 4+ for arguments after selector)
106///
107/// # Returns
108///
109/// * `Ok(u64)` - The decoded value
110/// * `Err(Error)` - If offset is out of bounds or calldata is too short
111///
112/// # Example
113///
114/// ```ignore
115/// let calldata = context::calldata()?;
116/// let amount = abi::read_u64(&calldata, 4)?; // Read first argument after selector
117/// ```
118pub fn read_u64(calldata: &[u8], offset: usize) -> Result<u64> {
119    let end = offset
120        .checked_add(8)
121        .ok_or_else(|| Error::new(ERR_BAD_CALLDATA))?;
122    if calldata.len() < end {
123        return Err(Error::new(ERR_BAD_CALLDATA));
124    }
125    let mut out = [0u8; 8];
126    out.copy_from_slice(&calldata[offset..end]);
127    Ok(u64::from_le_bytes(out))
128}
129
130/// Reads a `u128` value from calldata at the specified byte offset.
131///
132/// Interprets 16 bytes starting at `offset` as a little-endian `u128`.
133/// Useful for reading large token amounts or 128-bit integers.
134///
135/// # Arguments
136///
137/// * `calldata` - Raw calldata bytes
138/// * `offset` - Byte offset where the u128 starts
139///
140/// # Returns
141///
142/// * `Ok(u128)` - The decoded value
143/// * `Err(Error)` - If offset is out of bounds or calldata is too short
144///
145/// # Example
146///
147/// ```ignore
148/// let calldata = context::calldata()?;
149/// let token_amount = abi::read_u128(&calldata, 4)?;
150/// ```
151pub fn read_u128(calldata: &[u8], offset: usize) -> Result<u128> {
152    let end = offset
153        .checked_add(16)
154        .ok_or_else(|| Error::new(ERR_BAD_CALLDATA))?;
155    if calldata.len() < end {
156        return Err(Error::new(ERR_BAD_CALLDATA));
157    }
158    let mut out = [0u8; 16];
159    out.copy_from_slice(&calldata[offset..end]);
160    Ok(u128::from_le_bytes(out))
161}
162
163/// Reads a 32-byte account ID from calldata at the specified byte offset.
164///
165/// Account IDs in TruthLinked are 32-byte arrays (typically BLAKE3 hashes of public keys).
166///
167/// # Arguments
168///
169/// * `calldata` - Raw calldata bytes
170/// * `offset` - Byte offset where the account ID starts
171///
172/// # Returns
173///
174/// * `Ok([u8; 32])` - The account ID
175/// * `Err(Error)` - If offset is out of bounds or calldata is too short
176///
177/// # Example
178///
179/// ```ignore
180/// let calldata = context::calldata()?;
181/// let recipient = abi::read_account(&calldata, 4)?;
182/// let sender = context::caller()?;
183/// ```
184pub fn read_account(calldata: &[u8], offset: usize) -> Result<[u8; 32]> {
185    let end = offset
186        .checked_add(32)
187        .ok_or_else(|| Error::new(ERR_BAD_CALLDATA))?;
188    if calldata.len() < end {
189        return Err(Error::new(ERR_BAD_CALLDATA));
190    }
191    let mut out = [0u8; 32];
192    out.copy_from_slice(&calldata[offset..end]);
193    Ok(out)
194}