use ethabi::{Function, Param, ParamType, StateMutability, Token};
use tiny_keccak::{Hasher, Keccak};
use crate::error::{Result, WalletError};
pub fn function_selector(signature: &str) -> [u8; 4] {
let mut keccak = Keccak::v256();
let mut hash = [0u8; 32];
keccak.update(signature.as_bytes());
keccak.finalize(&mut hash);
let mut selector = [0u8; 4];
selector.copy_from_slice(&hash[..4]);
selector
}
pub fn encode_call(signature: &str, tokens: &[Token]) -> Result<Vec<u8>> {
let selector = function_selector(signature);
let encoded_params = ethabi::encode(tokens);
let mut calldata = Vec::with_capacity(4 + encoded_params.len());
calldata.extend_from_slice(&selector);
calldata.extend_from_slice(&encoded_params);
Ok(calldata)
}
pub fn encode_call_hex(signature: &str, tokens: &[Token]) -> Result<String> {
let calldata = encode_call(signature, tokens)?;
Ok(format!("0x{}", hex::encode(calldata)))
}
pub fn decode_output(types: &[ParamType], data: &[u8]) -> Result<Vec<Token>> {
ethabi::decode(types, data).map_err(|e| WalletError::AbiError(e.to_string()))
}
pub fn decode_output_hex(types: &[ParamType], hex_data: &str) -> Result<Vec<Token>> {
let stripped = hex_data.strip_prefix("0x").unwrap_or(hex_data);
let bytes =
hex::decode(stripped).map_err(|e| WalletError::AbiError(format!("bad hex: {e}")))?;
decode_output(types, &bytes)
}
pub fn build_function(
name: &str,
inputs: Vec<(&str, ParamType)>,
outputs: Vec<(&str, ParamType)>,
) -> Function {
Function {
name: name.to_string(),
inputs: inputs
.into_iter()
.map(|(n, t)| Param {
name: n.to_string(),
kind: t,
internal_type: None,
})
.collect(),
outputs: outputs
.into_iter()
.map(|(n, t)| Param {
name: n.to_string(),
kind: t,
internal_type: None,
})
.collect(),
#[allow(deprecated)]
constant: None,
state_mutability: StateMutability::NonPayable,
}
}
pub fn hex_to_uint256(hex_str: &str) -> Result<Token> {
let stripped = hex_str.strip_prefix("0x").unwrap_or(hex_str);
let padded = format!("{:0>64}", stripped);
let bytes =
hex::decode(&padded).map_err(|e| WalletError::AbiError(format!("bad hex: {e}")))?;
Ok(Token::Uint(ethabi::ethereum_types::U256::from_big_endian(
&bytes,
)))
}
pub fn hex_to_address(hex_str: &str) -> Result<Token> {
let stripped = hex_str.strip_prefix("0x").unwrap_or(hex_str);
let bytes =
hex::decode(stripped).map_err(|e| WalletError::AbiError(format!("bad hex: {e}")))?;
if bytes.len() != 20 {
return Err(WalletError::AbiError(format!(
"address must be 20 bytes, got {}",
bytes.len()
)));
}
Ok(Token::Address(ethabi::ethereum_types::H160::from_slice(
&bytes,
)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn selector_transfer() {
let sel = function_selector("transfer(address,uint256)");
assert_eq!(hex::encode(sel), "a9059cbb");
}
#[test]
fn selector_balance_of() {
let sel = function_selector("balanceOf(address)");
assert_eq!(hex::encode(sel), "70a08231");
}
#[test]
fn selector_approve() {
let sel = function_selector("approve(address,uint256)");
assert_eq!(hex::encode(sel), "095ea7b3");
}
#[test]
fn encode_call_produces_correct_length() {
let addr = hex_to_address("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045").unwrap();
let calldata = encode_call("balanceOf(address)", &[addr]).unwrap();
assert_eq!(calldata.len(), 36);
}
#[test]
fn encode_call_hex_has_prefix() {
let addr = hex_to_address("0x0000000000000000000000000000000000000001").unwrap();
let hex_data = encode_call_hex("balanceOf(address)", &[addr]).unwrap();
assert!(hex_data.starts_with("0x70a08231"));
}
#[test]
fn decode_uint256_output() {
let mut data = vec![0u8; 32];
data[31] = 42;
let tokens = decode_output(&[ParamType::Uint(256)], &data).unwrap();
assert_eq!(tokens.len(), 1);
let val = tokens[0].clone().into_uint().unwrap();
assert_eq!(val.as_u64(), 42);
}
#[test]
fn hex_to_address_validates_length() {
assert!(hex_to_address("0xaabb").is_err());
assert!(hex_to_address("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045").is_ok());
}
#[test]
fn hex_to_uint256_parses() {
let token = hex_to_uint256("0xff").unwrap();
let val = token.into_uint().unwrap();
assert_eq!(val.as_u64(), 255);
}
#[test]
fn build_function_descriptor() {
let f = build_function(
"transfer",
vec![("to", ParamType::Address), ("amount", ParamType::Uint(256))],
vec![("success", ParamType::Bool)],
);
assert_eq!(f.name, "transfer");
assert_eq!(f.inputs.len(), 2);
assert_eq!(f.outputs.len(), 1);
}
}