use sha3::{Digest, Keccak256};
pub(crate) fn selector(signature: &str) -> [u8; 4] {
let mut h = Keccak256::new();
h.update(signature.as_bytes());
let digest = h.finalize();
let mut out = [0u8; 4];
out.copy_from_slice(&digest[..4]);
out
}
pub(crate) fn addr_word(a: &[u8; 20]) -> [u8; 32] {
let mut w = [0u8; 32];
w[12..].copy_from_slice(a);
w
}
pub(crate) fn push_dynamic_bytes(buf: &mut Vec<u8>, bytes: &[u8]) {
let len = bytes.len();
buf.extend_from_slice(&u256_be(len as u128));
buf.extend_from_slice(bytes);
buf.resize(buf.len() + (len.div_ceil(32) * 32 - len), 0);
}
pub(crate) fn encode_dynamic_call_hex(signature: &str, value: &[u8]) -> String {
let mut buf = Vec::with_capacity(4 + 64 + value.len().div_ceil(32) * 32);
buf.extend_from_slice(&selector(signature));
buf.extend_from_slice(&u256_be(0x20));
push_dynamic_bytes(&mut buf, value);
format!("0x{}", bytes_to_hex(&buf))
}
pub(crate) fn encode_id_of_name(name: &str) -> String {
encode_dynamic_call_hex("idOfName(string)", name.as_bytes())
}
pub(crate) fn encode_register(name: &str) -> String {
encode_dynamic_call_hex("register(string)", name.as_bytes())
}
pub(crate) fn u256_be(value: u128) -> [u8; 32] {
let mut out = [0u8; 32];
out[16..].copy_from_slice(&value.to_be_bytes());
out
}
pub(crate) fn encode_call_hex(sel: [u8; 4], words: &[[u8; 32]]) -> String {
let mut data = Vec::with_capacity(4 + 32 * words.len());
data.extend_from_slice(&sel);
for w in words {
data.extend_from_slice(w);
}
format!("0x{}", bytes_to_hex(&data))
}
pub(crate) fn encode_set_metadata(token_id: u64, key: [u8; 32], payload: &[u8]) -> Vec<u8> {
let mut buf = Vec::with_capacity(4 + 96 + 32 + payload.len().div_ceil(32) * 32);
buf.extend_from_slice(&selector("setMetadata(uint256,bytes32,bytes)"));
buf.extend_from_slice(&u256_be(token_id as u128));
buf.extend_from_slice(&key);
buf.extend_from_slice(&u256_be(0x60)); push_dynamic_bytes(&mut buf, payload);
buf
}
pub(crate) fn decode_abi_bytes(result_hex: &str) -> Option<Vec<u8>> {
let bytes = hex_to_bytes(result_hex).ok()?;
if bytes.len() < 64 {
return None;
}
let mut len_buf = [0u8; 8];
len_buf.copy_from_slice(&bytes[56..64]); let len = u64::from_be_bytes(len_buf) as usize;
if len == 0 {
return None;
}
len.checked_add(64)
.and_then(|end| bytes.get(64..end))
.map(|s| s.to_vec())
}
pub(crate) fn decode_u256_as_u64(hex: &str) -> Result<u64, String> {
let stripped = hex.trim().trim_start_matches("0x");
if stripped.is_empty() {
return Ok(0);
}
if stripped.len() > 64 {
return Err(format!("u256 hex too long: {}", stripped.len()));
}
let high_end = stripped.len().saturating_sub(16);
if stripped[..high_end].chars().any(|c| c != '0') {
return Err("u256 exceeds u64 range".into());
}
let tail = &stripped[high_end..];
u64::from_str_radix(tail, 16).map_err(|e| e.to_string())
}
pub(crate) fn zero_address() -> &'static str {
"0x0000000000000000000000000000000000000000"
}
pub(crate) fn u64_low(w: &[u8]) -> u64 {
let mut b = [0u8; 8];
b.copy_from_slice(&w[24..32]);
u64::from_be_bytes(b)
}
pub(crate) fn u128_low(w: &[u8]) -> u128 {
let mut b = [0u8; 16];
b.copy_from_slice(&w[16..32]);
u128::from_be_bytes(b)
}
pub(crate) fn decode_u64_array(bytes: &[u8]) -> Vec<u64> {
if bytes.len() < 64 {
return Vec::new();
}
let mut len_buf = [0u8; 8];
len_buf.copy_from_slice(&bytes[56..64]); let len = u64::from_be_bytes(len_buf) as usize;
let mut out = Vec::new();
for i in 0..len {
let start = match i.checked_mul(32).and_then(|o| o.checked_add(64)) {
Some(s) => s,
None => break,
};
let Some(word) = start.checked_add(32).and_then(|end| bytes.get(start + 24..end)) else {
break;
};
let mut id_buf = [0u8; 8];
id_buf.copy_from_slice(word);
out.push(u64::from_be_bytes(id_buf));
}
out
}
pub(crate) fn decode_address_array(bytes: &[u8]) -> Vec<String> {
if bytes.len() < 64 {
return Vec::new();
}
let mut len_buf = [0u8; 8];
len_buf.copy_from_slice(&bytes[56..64]); let len = u64::from_be_bytes(len_buf) as usize;
let mut out = Vec::new();
for i in 0..len {
let start = match i.checked_mul(32).and_then(|o| o.checked_add(64)) {
Some(s) => s,
None => break,
};
let Some(word) = start
.checked_add(32)
.and_then(|end| bytes.get(start + 12..end))
else {
break;
};
out.push(format!("0x{}", bytes_to_hex(word)));
}
out
}
pub(crate) fn address_to_hex(addr: &[u8; 20]) -> String {
let mut s = String::with_capacity(42);
s.push_str("0x");
for b in addr {
s.push_str(&format!("{b:02x}"));
}
s
}
pub(crate) use crate::encoding::{bytes_to_hex, hex_to_bytes, parse_hex_quantity};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn selector_matches_known_value() {
let sel = selector("idOfName(string)");
let hex: String = sel.iter().map(|b| format!("{b:02x}")).collect();
assert_eq!(hex, "127c388a");
}
#[test]
fn encode_short_name_layout() {
let cd = encode_id_of_name("abc");
assert!(cd.starts_with("0x127c388a"));
assert_eq!(cd.len(), 2 + (4 + 32 + 32 + 32) * 2);
}
#[test]
fn decode_zero_means_available() {
let z = format!("0x{}", "0".repeat(64));
assert_eq!(decode_u256_as_u64(&z).unwrap(), 0);
}
#[test]
fn decode_normal_id() {
let mut s = "0".repeat(63);
s.push('7');
let hex = format!("0x{s}");
assert_eq!(decode_u256_as_u64(&hex).unwrap(), 7);
}
#[test]
fn decode_oversize_errors() {
let mut s = String::from("1");
s.push_str(&"0".repeat(63));
let hex = format!("0x{s}");
assert!(decode_u256_as_u64(&hex).is_err());
}
#[test]
fn hex_to_bytes_rejects_malformed_without_panic() {
assert!(hex_to_bytes("0xabc").is_err()); assert!(hex_to_bytes("0xzz").is_err()); assert!(hex_to_bytes("0x").unwrap().is_empty()); assert_eq!(hex_to_bytes("0xAaBb").unwrap(), vec![0xAA, 0xBB]); assert_eq!(hex_to_bytes("deadbeef").unwrap(), vec![0xDE, 0xAD, 0xBE, 0xEF]); }
#[test]
fn word_low_extractors_read_right_aligned_values() {
let w = u256_be(0x1234_5678_9ABC_DEF0_u64 as u128);
assert_eq!(u64_low(&w), 0x1234_5678_9ABC_DEF0);
let big = u256_be(u128::MAX);
assert_eq!(u128_low(&big), u128::MAX);
let mut noisy = u256_be(7);
noisy[0] = 0xFF;
assert_eq!(u64_low(&noisy), 7);
assert_eq!(u128_low(&noisy), 7);
}
#[test]
fn decode_u64_array_roundtrip_and_hostile() {
let mut bytes = Vec::new();
bytes.extend_from_slice(&u256_be(0x20));
bytes.extend_from_slice(&u256_be(3));
for id in [5u64, 8, 13] {
bytes.extend_from_slice(&u256_be(id as u128));
}
assert_eq!(decode_u64_array(&bytes), vec![5, 8, 13]);
assert!(decode_u64_array(&[]).is_empty());
assert!(decode_u64_array(&[0u8; 32]).is_empty());
let mut lying = Vec::new();
lying.extend_from_slice(&u256_be(0x20));
lying.extend_from_slice(&u64::MAX.to_be_bytes().repeat(4)); lying.extend_from_slice(&u256_be(7));
assert_eq!(decode_u64_array(&lying), vec![7]);
}
#[test]
fn decode_address_array_roundtrip_and_hostile() {
let addr = [0xABu8; 20];
let mut word = [0u8; 32];
word[12..].copy_from_slice(&addr);
let mut bytes = Vec::new();
bytes.extend_from_slice(&u256_be(0x20));
bytes.extend_from_slice(&u256_be(1));
bytes.extend_from_slice(&word);
let out = decode_address_array(&bytes);
assert_eq!(out, vec![format!("0x{}", "ab".repeat(20))]);
assert!(decode_address_array(&[]).is_empty());
assert!(decode_address_array(&[0u8; 63]).is_empty());
let mut lying = Vec::new();
lying.extend_from_slice(&u256_be(0x20));
lying.extend_from_slice(&u256_be(1000)); lying.extend_from_slice(&word); assert_eq!(decode_address_array(&lying).len(), 1);
}
}