use alloy_json_abi::Param;
use alloy_primitives::Bytes;
use color_eyre::eyre::{Result, eyre};
pub(super) fn hex_to_bytes(value: Option<&str>) -> Result<Bytes> {
if let Some(raw) = value {
if raw.trim().is_empty() {
return Ok(Bytes::new());
}
let trimmed = raw.trim();
if trimmed.starts_with('[') {
return encode_json_schema_to_tlv(trimmed);
}
let hex_trimmed = trimmed.strip_prefix("0x").unwrap_or(trimmed);
if hex_trimmed.starts_with("5b") || hex_trimmed.starts_with("5B") {
if let Ok(decoded) = hex::decode(hex_trimmed) {
if let Ok(json_str) = std::str::from_utf8(&decoded) {
if json_str.trim().starts_with('[') {
return encode_json_schema_to_tlv(json_str);
}
}
}
}
let decoded = hex::decode(hex_trimmed)
.map_err(|e| eyre!("failed to decode hex schema {raw}: {e}"))?;
Ok(Bytes::from(decoded))
} else {
Ok(Bytes::new())
}
}
const SCHEMA_VERSION_2: u8 = 0x02;
fn encode_json_schema_to_tlv(json_str: &str) -> Result<Bytes> {
let params: Vec<Param> =
serde_json::from_str(json_str).map_err(|e| eyre!("failed to parse JSON schema: {e}"))?;
if params.is_empty() {
return Ok(Bytes::new());
}
let field_count = params.len();
if field_count > u16::MAX as usize {
return Err(eyre!("schema has too many fields (max {})", u16::MAX));
}
let total_size: usize = 1 + 2 + params.iter().map(calculate_field_size).sum::<usize>();
let mut buffer = vec![0u8; total_size];
buffer[0] = SCHEMA_VERSION_2;
buffer[1] = (field_count >> 8) as u8;
buffer[2] = (field_count & 0xFF) as u8;
let mut cursor = 3;
for param in ¶ms {
cursor = write_field(&mut buffer, cursor, param)?;
}
Ok(Bytes::from(buffer))
}
fn calculate_field_size(param: &Param) -> usize {
let name_len = param.name.len();
let mut size = 5 + compact_length_size(name_len) + name_len;
if param.ty.ends_with(']') && param.components.is_empty() {
if let Some(bracket_pos) = param.ty.rfind('[') {
let base_type = ¶m.ty[..bracket_pos];
if !base_type.is_empty() {
let element_param = Param {
name: String::new(),
ty: base_type.to_string(),
components: vec![],
internal_type: None,
};
size += calculate_field_size(&element_param);
return size;
}
}
}
for child in ¶m.components {
size += calculate_field_size(child);
}
size
}
fn compact_length_size(len: usize) -> usize {
if len < 0x80 {
1
} else if len < 0x4000 {
2
} else if len < 0x200000 {
3
} else {
4
}
}
fn write_field(buffer: &mut [u8], cursor: usize, param: &Param) -> Result<usize> {
let (kind, array_length) = parse_solidity_type(¶m.ty)?;
let (child_count, synthetic_child) =
if (kind == 20 || kind == 21) && param.components.is_empty() && param.ty.ends_with(']') {
if let Some(bracket_pos) = param.ty.rfind('[') {
let base_type = ¶m.ty[..bracket_pos];
if !base_type.is_empty() {
let element_param = Param {
name: String::new(),
ty: base_type.to_string(),
components: vec![],
internal_type: None,
};
(1, Some(element_param))
} else {
(param.components.len(), None)
}
} else {
(param.components.len(), None)
}
} else {
(param.components.len(), None)
};
if child_count > u16::MAX as usize {
return Err(eyre!("field has too many children (max {})", u16::MAX));
}
buffer[cursor] = kind;
buffer[cursor + 1] = (array_length >> 8) as u8;
buffer[cursor + 2] = (array_length & 0xFF) as u8;
buffer[cursor + 3] = (child_count >> 8) as u8;
buffer[cursor + 4] = (child_count & 0xFF) as u8;
let mut next_cursor = cursor + 5;
next_cursor = write_compact_string(buffer, next_cursor, ¶m.name);
if let Some(ref element) = synthetic_child {
next_cursor = write_field(buffer, next_cursor, element)?;
}
for child in ¶m.components {
next_cursor = write_field(buffer, next_cursor, child)?;
}
Ok(next_cursor)
}
fn write_compact_string(buffer: &mut [u8], cursor: usize, s: &str) -> usize {
let bytes = s.as_bytes();
let len = bytes.len();
let cursor = write_compact_length(buffer, cursor, len);
buffer[cursor..cursor + len].copy_from_slice(bytes);
cursor + len
}
fn write_compact_length(buffer: &mut [u8], cursor: usize, len: usize) -> usize {
if len < 0x80 {
buffer[cursor] = len as u8;
cursor + 1
} else if len < 0x4000 {
buffer[cursor] = (0x80 | (len >> 8)) as u8;
buffer[cursor + 1] = (len & 0xFF) as u8;
cursor + 2
} else if len < 0x200000 {
buffer[cursor] = (0xC0 | (len >> 16)) as u8;
buffer[cursor + 1] = ((len >> 8) & 0xFF) as u8;
buffer[cursor + 2] = (len & 0xFF) as u8;
cursor + 3
} else {
buffer[cursor] = (0xE0 | (len >> 24)) as u8;
buffer[cursor + 1] = ((len >> 16) & 0xFF) as u8;
buffer[cursor + 2] = ((len >> 8) & 0xFF) as u8;
buffer[cursor + 3] = (len & 0xFF) as u8;
cursor + 4
}
}
fn parse_solidity_type(ty: &str) -> Result<(u8, u16)> {
let ty = ty.trim();
if ty.ends_with(']') {
if let Some(bracket_pos) = ty.rfind('[') {
let _base_type = &ty[..bracket_pos];
let size_str = &ty[bracket_pos + 1..ty.len() - 1];
if size_str.is_empty() {
return Ok((21, 0)); } else {
let size: u16 = size_str
.parse()
.map_err(|_| eyre!("invalid array size in type '{ty}'"))?;
return Ok((20, size)); }
}
}
if ty == "tuple" {
return Ok((22, 0)); }
let kind = match ty {
"bool" => 1,
"uint8" => 2,
"int8" => 3,
"uint16" => 4,
"int16" => 5,
"uint32" => 6,
"int32" => 7,
"uint64" => 8,
"int64" => 9,
"uint128" => 10,
"int128" => 11,
"uint256" | "uint" => 12,
"int256" | "int" => 13,
"address" => 14,
"bytes32" => 15,
"string" => 17,
"bytes" => 18,
_ => {
if let Some(size_str) = ty.strip_prefix("bytes") {
if let Ok(size) = size_str.parse::<u16>() {
if size >= 1 && size <= 32 {
if size == 32 {
return Ok((15, 0)); }
return Ok((16, size)); }
}
}
if let Some(size_str) = ty.strip_prefix("uint") {
if let Ok(bits) = size_str.parse::<u16>() {
return Ok((
match bits {
8 => 2,
16 => 4,
32 => 6,
64 => 8,
128 => 10,
256 => 12,
_ => return Err(eyre!("unsupported uint size: uint{bits}")),
},
0,
));
}
}
if let Some(size_str) = ty.strip_prefix("int") {
if let Ok(bits) = size_str.parse::<u16>() {
return Ok((
match bits {
8 => 3,
16 => 5,
32 => 7,
64 => 9,
128 => 11,
256 => 13,
_ => return Err(eyre!("unsupported int size: int{bits}")),
},
0,
));
}
}
return Err(eyre!("unsupported Solidity type: '{ty}'"));
}
};
Ok((kind, 0))
}