use alloy_dyn_abi::{DynSolType, DynSolValue, JsonAbiExt};
use alloy_json_abi::Function;
use alloy_primitives::{Address, Bytes, FixedBytes, I256, U256};
use eyre::{eyre, Result};
use std::collections::BTreeMap;
pub fn encode_function_call(
functions: &BTreeMap<String, Vec<Function>>,
call_text: &str,
) -> Result<Bytes> {
let (function_name, args_str) = parse_function_call_text(call_text)?;
let (function, args) = find_matching_function(functions, &function_name, &args_str)?;
encode_function_data(&function, args)
}
fn parse_function_call_text(call_text: &str) -> Result<(String, String)> {
let call_text = call_text.trim();
if let Some(open_paren) = call_text.find('(') {
if !call_text.ends_with(')') {
return Err(eyre!("Function call must end with ')': {}", call_text));
}
let function_name = call_text[..open_paren].trim().to_string();
let args_str = call_text[open_paren + 1..call_text.len() - 1].trim().to_string();
if function_name.is_empty() {
return Err(eyre!("Function name cannot be empty"));
}
Ok((function_name, args_str))
} else {
Err(eyre!("Invalid function call format. Expected format: functionName(arg1,arg2,...)"))
}
}
fn find_matching_function(
functions: &BTreeMap<String, Vec<Function>>,
function_name: &str,
args_str: &str,
) -> Result<(Function, Vec<DynSolValue>)> {
let function_overloads = functions
.get(function_name)
.ok_or_else(|| eyre!("Function '{}' not found in ABI", function_name))?;
if function_overloads.is_empty() {
return Err(eyre!("No function definitions found for '{}'", function_name));
}
let mut parse_errors = Vec::new();
for function in function_overloads {
match parse_function_arguments(function, args_str) {
Ok(args) => {
return Ok((function.clone(), args));
}
Err(e) => {
parse_errors.push(format!(
"Function '{}({})': {}",
function.name,
function
.inputs
.iter()
.map(|param| param.ty.clone())
.collect::<Vec<_>>()
.join(","),
e
));
}
}
}
let error_msg = if function_overloads.len() == 1 {
format!("Failed to parse arguments for function '{}': {}", function_name, parse_errors[0])
} else {
format!(
"Failed to match arguments with any overload of function '{}'. Tried:\n{}",
function_name,
parse_errors.join("\n")
)
};
Err(eyre!(error_msg))
}
fn parse_function_arguments(function: &Function, args_str: &str) -> Result<Vec<DynSolValue>> {
if args_str.trim().is_empty() {
if function.inputs.is_empty() {
return Ok(vec![]);
} else {
return Err(eyre!(
"Function '{}' expects {} arguments, but none provided",
function.name,
function.inputs.len()
));
}
}
let arg_strings = split_arguments(args_str)?;
if arg_strings.len() != function.inputs.len() {
return Err(eyre!(
"Function '{}' expects {} arguments, but {} provided",
function.name,
function.inputs.len(),
arg_strings.len()
));
}
let mut args = Vec::new();
for (i, (arg_str, param)) in arg_strings.iter().zip(&function.inputs).enumerate() {
let arg_value = parse_argument_value(arg_str.trim(), ¶m.ty)
.map_err(|e| eyre!("Failed to parse argument {}: {}", i + 1, e))?;
args.push(arg_value);
}
Ok(args)
}
fn split_arguments(args_str: &str) -> Result<Vec<String>> {
let mut args = Vec::new();
let mut current_arg = String::new();
let mut depth = 0; let mut in_string = false;
let mut escape_next = false;
let mut string_char = '\0';
for ch in args_str.chars() {
if escape_next {
current_arg.push(ch);
escape_next = false;
continue;
}
match ch {
'\\' if in_string => {
escape_next = true;
current_arg.push(ch);
}
'"' | '\'' => {
if !in_string {
in_string = true;
string_char = ch;
} else if ch == string_char {
in_string = false;
}
current_arg.push(ch);
}
'(' | '[' | '{' if !in_string => {
depth += 1;
current_arg.push(ch);
}
')' | ']' | '}' if !in_string => {
depth -= 1;
current_arg.push(ch);
}
',' if !in_string && depth == 0 => {
args.push(current_arg.trim().to_string());
current_arg.clear();
}
_ => {
current_arg.push(ch);
}
}
}
if !current_arg.trim().is_empty() {
args.push(current_arg.trim().to_string());
}
Ok(args)
}
fn parse_argument_value(arg_str: &str, param_type: &str) -> Result<DynSolValue> {
let arg_str = arg_str.trim();
let (cast_type, actual_value) = extract_type_cast(arg_str)?;
let value_to_parse = if let Some(cast_type) = cast_type {
validate_type_cast(&cast_type, param_type)?;
actual_value
} else {
arg_str
};
let sol_type = DynSolType::parse(param_type)
.map_err(|e| eyre!("Invalid parameter type '{}': {}", param_type, e))?;
match sol_type {
DynSolType::Address => {
let address = parse_address(value_to_parse)?;
Ok(DynSolValue::Address(address))
}
DynSolType::Uint(size) => {
let value = parse_uint(value_to_parse, size)?;
Ok(DynSolValue::Uint(value, size))
}
DynSolType::Int(size) => {
let value = parse_int(value_to_parse, size)?;
Ok(DynSolValue::Int(value, size))
}
DynSolType::Bool => {
let value = parse_bool(value_to_parse)?;
Ok(DynSolValue::Bool(value))
}
DynSolType::String => {
let value = parse_string(value_to_parse)?;
Ok(DynSolValue::String(value))
}
DynSolType::Bytes => {
let value = parse_bytes(value_to_parse)?;
Ok(DynSolValue::Bytes(value))
}
DynSolType::FixedBytes(size) => {
let value = parse_fixed_bytes(value_to_parse, size)?;
let mut word = [0u8; 32];
let copy_len = value.len().min(32);
word[..copy_len].copy_from_slice(&value[..copy_len]);
Ok(DynSolValue::FixedBytes(FixedBytes::from(word), size))
}
DynSolType::Array(ref inner) => parse_array(value_to_parse, inner),
DynSolType::FixedArray(ref inner, size) => parse_fixed_array(value_to_parse, inner, size),
DynSolType::Tuple(ref types) => parse_tuple(value_to_parse, types),
_ => Err(eyre!("Unsupported type: {}", param_type)),
}
}
fn extract_type_cast(s: &str) -> Result<(Option<String>, &str)> {
let s = s.trim();
let type_prefixes = [
"uint", "int", "address", "bool", "bytes", "string", "uint8", "uint16", "uint24", "uint32",
"uint40", "uint48", "uint56", "uint64", "uint72", "uint80", "uint88", "uint96", "uint104",
"uint112", "uint120", "uint128", "uint136", "uint144", "uint152", "uint160", "uint168",
"uint176", "uint184", "uint192", "uint200", "uint208", "uint216", "uint224", "uint232",
"uint240", "uint248", "uint256", "int8", "int16", "int24", "int32", "int40", "int48",
"int56", "int64", "int72", "int80", "int88", "int96", "int104", "int112", "int120",
"int128", "int136", "int144", "int152", "int160", "int168", "int176", "int184", "int192",
"int200", "int208", "int216", "int224", "int232", "int240", "int248", "int256", "bytes1",
"bytes2", "bytes3", "bytes4", "bytes5", "bytes6", "bytes7", "bytes8", "bytes9", "bytes10",
"bytes11", "bytes12", "bytes13", "bytes14", "bytes15", "bytes16", "bytes17", "bytes18",
"bytes19", "bytes20", "bytes21", "bytes22", "bytes23", "bytes24", "bytes25", "bytes26",
"bytes27", "bytes28", "bytes29", "bytes30", "bytes31", "bytes32",
];
for prefix in &type_prefixes {
if let Some(after_type) = s.strip_prefix(prefix) {
if after_type.starts_with('(') {
let mut depth = 0;
let mut end_idx = None;
for (i, ch) in after_type.chars().enumerate() {
match ch {
'(' => depth += 1,
')' => {
depth -= 1;
if depth == 0 {
end_idx = Some(i);
break;
}
}
_ => {}
}
}
if let Some(end) = end_idx {
let value = &after_type[1..end];
return Ok((Some(prefix.to_string()), value));
}
}
}
}
Ok((None, s))
}
fn validate_type_cast(cast_type: &str, param_type: &str) -> Result<()> {
let normalize_type = |t: &str| -> String {
if t == "uint" {
"uint256".to_string()
} else if t == "int" {
"int256".to_string()
} else {
t.to_string()
}
};
let cast_normalized = normalize_type(cast_type);
let param_normalized = normalize_type(param_type);
if cast_normalized == param_normalized {
return Ok(());
}
if (cast_normalized.starts_with("uint") && param_normalized.starts_with("uint"))
|| (cast_normalized.starts_with("int") && param_normalized.starts_with("int"))
{
return Ok(());
}
if cast_normalized.starts_with("bytes") && param_normalized.starts_with("bytes") {
return Ok(());
}
Err(eyre!(
"Type cast '{cast_type}' is not compatible with expected parameter type '{param_type}'"
))
}
fn parse_address(s: &str) -> Result<Address> {
let s = s.trim();
if s.starts_with("0x") || s.starts_with("0X") {
s.parse().map_err(|e| eyre!("Invalid address '{s}': {e}"))
} else {
format!("0x{s}").parse().map_err(|e| eyre!("Invalid address '{s}': {e}"))
}
}
fn parse_uint(s: &str, _size: usize) -> Result<U256> {
let s = s.trim();
if s.starts_with("0x") || s.starts_with("0X") {
U256::from_str_radix(&s[2..], 16).map_err(|e| eyre!("Invalid hex uint '{}': {}", s, e))
} else {
U256::from_str_radix(s, 10).map_err(|e| eyre!("Invalid decimal uint '{}': {}", s, e))
}
}
fn parse_int(s: &str, _size: usize) -> Result<I256> {
let s = s.trim();
if s.starts_with("0x") || s.starts_with("0X") {
let uint_val = U256::from_str_radix(&s[2..], 16)
.map_err(|e| eyre!("Invalid hex int '{}': {}", s, e))?;
Ok(I256::from_raw(uint_val))
} else {
if let Some(positive_part) = s.strip_prefix('-') {
let uint_val = U256::from_str_radix(positive_part, 10)
.map_err(|e| eyre!("Invalid decimal int '{}': {}", s, e))?;
Ok(-I256::from_raw(uint_val))
} else {
let uint_val = U256::from_str_radix(s, 10)
.map_err(|e| eyre!("Invalid decimal int '{}': {}", s, e))?;
Ok(I256::from_raw(uint_val))
}
}
}
fn parse_bool(s: &str) -> Result<bool> {
match s.trim().to_lowercase().as_str() {
"true" | "1" => Ok(true),
"false" | "0" => Ok(false),
_ => Err(eyre!("Invalid boolean value '{}'. Expected 'true', 'false', '1', or '0'", s)),
}
}
fn parse_string(s: &str) -> Result<String> {
let s = s.trim();
if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) {
Ok(s[1..s.len() - 1].to_string())
} else {
Ok(s.to_string())
}
}
fn parse_bytes(s: &str) -> Result<Vec<u8>> {
let s = s.trim();
if s.starts_with("0x") || s.starts_with("0X") {
hex::decode(&s[2..]).map_err(|e| eyre!("Invalid hex bytes '{}': {}", s, e))
} else {
hex::decode(s).map_err(|e| eyre!("Invalid hex bytes '{}': {}", s, e))
}
}
fn parse_fixed_bytes(s: &str, size: usize) -> Result<Vec<u8>> {
let bytes = parse_bytes(s)?;
if bytes.len() != size {
return Err(eyre!(
"Fixed bytes size mismatch: expected {} bytes, got {}",
size,
bytes.len()
));
}
Ok(bytes)
}
fn parse_array(s: &str, inner_type: &DynSolType) -> Result<DynSolValue> {
let s = s.trim();
if !s.starts_with('[') || !s.ends_with(']') {
return Err(eyre!("Array must be enclosed in square brackets: {}", s));
}
let inner_str = &s[1..s.len() - 1];
if inner_str.trim().is_empty() {
return Ok(DynSolValue::Array(vec![]));
}
let elements_str = split_arguments(inner_str)?;
let mut elements = Vec::new();
for element_str in elements_str {
let element = parse_argument_value(&element_str, &inner_type.to_string())?;
elements.push(element);
}
Ok(DynSolValue::Array(elements))
}
fn parse_fixed_array(s: &str, inner_type: &DynSolType, size: usize) -> Result<DynSolValue> {
if let DynSolValue::Array(elements) = parse_array(s, inner_type)? {
if elements.len() != size {
return Err(eyre!(
"Fixed array size mismatch: expected {} elements, got {}",
size,
elements.len()
));
}
Ok(DynSolValue::FixedArray(elements))
} else {
unreachable!("parse_array should always return Array variant")
}
}
fn parse_tuple(s: &str, types: &[DynSolType]) -> Result<DynSolValue> {
let s = s.trim();
let (is_struct_syntax, inner_str) = if s.starts_with('{') && s.ends_with('}') {
(true, &s[1..s.len() - 1])
} else if s.starts_with('(') && s.ends_with(')') {
(false, &s[1..s.len() - 1])
} else {
return Err(eyre!("Tuple/Struct must be enclosed in parentheses () or braces {{}}: {}", s));
};
if inner_str.trim().is_empty() {
if types.is_empty() {
return Ok(DynSolValue::Tuple(vec![]));
} else {
return Err(eyre!("Empty tuple/struct provided but {} elements expected", types.len()));
}
}
if is_struct_syntax {
parse_struct_syntax(inner_str, types)
} else {
parse_positional_syntax(inner_str, types)
}
}
fn parse_struct_syntax(inner_str: &str, types: &[DynSolType]) -> Result<DynSolValue> {
let elements_str = split_arguments(inner_str)?;
if elements_str.len() != types.len() {
return Err(eyre!(
"Struct element count mismatch: expected {} elements, got {}",
types.len(),
elements_str.len()
));
}
let mut elements = Vec::new();
for (element_str, element_type) in elements_str.iter().zip(types) {
let value_str = if element_str.contains(':') {
let parts: Vec<&str> = element_str.splitn(2, ':').collect();
if parts.len() == 2 {
parts[1].trim()
} else {
element_str.trim()
}
} else {
element_str.trim()
};
let element = parse_argument_value(value_str, &element_type.to_string())?;
elements.push(element);
}
Ok(DynSolValue::Tuple(elements))
}
fn parse_positional_syntax(inner_str: &str, types: &[DynSolType]) -> Result<DynSolValue> {
let elements_str = split_arguments(inner_str)?;
if elements_str.len() != types.len() {
return Err(eyre!(
"Tuple element count mismatch: expected {} elements, got {}",
types.len(),
elements_str.len()
));
}
let mut elements = Vec::new();
for (element_str, element_type) in elements_str.iter().zip(types) {
let element = parse_argument_value(element_str, &element_type.to_string())?;
elements.push(element);
}
Ok(DynSolValue::Tuple(elements))
}
fn encode_function_data(function: &Function, args: Vec<DynSolValue>) -> Result<Bytes> {
let encoded_args = if args.is_empty() {
Vec::new()
} else {
function
.abi_encode_input(&args)
.map_err(|e| eyre!("Failed to encode function arguments: {}", e))?
};
let selector = function.selector();
let mut result = selector.to_vec();
result.extend_from_slice(&encoded_args);
Ok(result.into())
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_json_abi::{Param, StateMutability};
fn create_test_function(name: &str, inputs: Vec<(&str, &str)>) -> Function {
Function {
name: name.to_string(),
inputs: inputs
.into_iter()
.map(|(name, ty)| Param {
name: name.to_string(),
ty: ty.to_string(),
internal_type: None, components: vec![],
})
.collect(),
outputs: vec![],
state_mutability: StateMutability::NonPayable,
}
}
#[test]
fn test_parse_function_call_text() {
assert_eq!(
parse_function_call_text("balanceOf(0x123)").unwrap(),
("balanceOf".to_string(), "0x123".to_string())
);
assert_eq!(
parse_function_call_text("transfer(0x123, 100)").unwrap(),
("transfer".to_string(), "0x123, 100".to_string())
);
assert_eq!(
parse_function_call_text("noArgs()").unwrap(),
("noArgs".to_string(), String::new())
);
}
#[test]
fn test_split_arguments() {
assert_eq!(split_arguments("0x123, 100").unwrap(), vec!["0x123", "100"]);
assert_eq!(
split_arguments("0x123, [1,2,3], \"hello, world\"").unwrap(),
vec!["0x123", "[1,2,3]", "\"hello, world\""]
);
assert_eq!(
split_arguments("{field1: 123, field2: \"test\"}, 456").unwrap(),
vec!["{field1: 123, field2: \"test\"}", "456"]
);
assert_eq!(
split_arguments("0x123, {inner: [1,2,3], value: 100}").unwrap(),
vec!["0x123", "{inner: [1,2,3], value: 100}"]
);
}
#[test]
fn test_parse_address() {
let addr = parse_address("0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1").unwrap();
assert_eq!(addr.to_string().to_lowercase(), "0x742d35cc6634c0532925a3b8d6ac6e89e86c6ad1");
}
#[test]
fn test_parse_uint() {
assert_eq!(parse_uint("123", 256).unwrap(), U256::from(123u64));
assert_eq!(parse_uint("0xff", 256).unwrap(), U256::from(255u64));
}
#[test]
fn test_encode_simple_function_call() {
let mut functions = BTreeMap::new();
let balance_of = create_test_function("balanceOf", vec![("account", "address")]);
functions.insert("balanceOf".to_string(), vec![balance_of]);
let encoded = encode_function_call(
&functions,
"balanceOf(0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1)",
)
.unwrap();
assert_eq!(&encoded[0..4], &[0x70, 0xa0, 0x82, 0x31]);
}
#[test]
fn test_function_overload_resolution() {
let mut functions = BTreeMap::new();
let transfer_address_uint =
create_test_function("transfer", vec![("to", "address"), ("amount", "uint256")]);
let transfer_uint_address =
create_test_function("transfer", vec![("tokenId", "uint256"), ("to", "address")]);
functions
.insert("transfer".to_string(), vec![transfer_address_uint, transfer_uint_address]);
let encoded1 = encode_function_call(
&functions,
"transfer(0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1, 100)",
)
.unwrap();
let encoded2 = encode_function_call(
&functions,
"transfer(123, 0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1)",
)
.unwrap();
assert_ne!(&encoded1[0..4], &encoded2[0..4]);
}
#[test]
fn test_overload_resolution_failure() {
let mut functions = BTreeMap::new();
let balance_of = create_test_function("balanceOf", vec![("account", "address")]);
functions.insert("balanceOf".to_string(), vec![balance_of]);
let result = encode_function_call(
&functions,
"balanceOf(123)", );
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Failed to parse arguments"));
}
#[test]
fn test_struct_syntax_parsing() {
let mut functions = BTreeMap::new();
let submit_data = create_test_function("submitData", vec![("data", "(address,uint256)")]);
functions.insert("submitData".to_string(), vec![submit_data]);
let encoded1 = encode_function_call(
&functions,
"submitData({user: 0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1, amount: 100})",
);
assert!(encoded1.is_ok(), "Struct syntax should work: {:?}", encoded1.err());
let encoded2 = encode_function_call(
&functions,
"submitData((0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1, 100))",
);
assert!(encoded2.is_ok(), "Tuple syntax should work: {:?}", encoded2.err());
assert_eq!(encoded1.unwrap(), encoded2.unwrap());
}
#[test]
fn test_nested_struct_parsing() {
let result = split_arguments("0x123, {field1: {nested: 456}, field2: [1,2,3]}");
assert!(result.is_ok());
let args = result.unwrap();
assert_eq!(args.len(), 2);
assert_eq!(args[0], "0x123");
assert_eq!(args[1], "{field1: {nested: 456}, field2: [1,2,3]}");
}
#[test]
fn test_type_cast_extraction() {
assert_eq!(
extract_type_cast("uint256(123)").unwrap(),
(Some("uint256".to_string()), "123")
);
assert_eq!(
extract_type_cast("address(0x123)").unwrap(),
(Some("address".to_string()), "0x123")
);
assert_eq!(extract_type_cast("uint(42)").unwrap(), (Some("uint".to_string()), "42"));
assert_eq!(
extract_type_cast("bytes32(0xabc)").unwrap(),
(Some("bytes32".to_string()), "0xabc")
);
assert_eq!(
extract_type_cast("uint256((1 + 2))").unwrap(),
(Some("uint256".to_string()), "(1 + 2)")
);
assert_eq!(extract_type_cast("123").unwrap(), (None, "123"));
assert_eq!(extract_type_cast("(123)").unwrap(), (None, "(123)"));
}
#[test]
fn test_type_cast_with_function_call() {
let mut functions = BTreeMap::new();
let transfer =
create_test_function("transfer", vec![("to", "address"), ("amount", "uint256")]);
functions.insert("transfer".to_string(), vec![transfer]);
let result = encode_function_call(
&functions,
"transfer(address(0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1), uint256(100))",
);
assert!(result.is_ok(), "Type casting should work: {:?}", result.err());
let result = encode_function_call(
&functions,
"transfer(address(0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1), uint(100))",
);
assert!(result.is_ok(), "Shorthand uint cast should work: {:?}", result.err());
}
#[test]
fn test_complex_nested_calls() {
let mut functions = BTreeMap::new();
let complex =
create_test_function("complexCall", vec![("data", "((address,uint256),bytes32[])")]);
functions.insert("complexCall".to_string(), vec![complex]);
let result = encode_function_call(
&functions,
"complexCall(((\
0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1, \
123\
), [\
0x0000000000000000000000000000000000000000000000000000000000000001,\
0x0000000000000000000000000000000000000000000000000000000000000002\
]))",
);
assert!(result.is_ok(), "Complex nested call should work: {:?}", result.err());
}
#[test]
fn test_array_parsing() {
let mut functions = BTreeMap::new();
let batch = create_test_function(
"batchTransfer",
vec![("recipients", "address[]"), ("amounts", "uint256[]")],
);
functions.insert("batchTransfer".to_string(), vec![batch]);
let result = encode_function_call(
&functions,
"batchTransfer(\
[0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1, 0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad2],\
[100, 200]\
)"
);
assert!(result.is_ok(), "Array parsing should work: {:?}", result.err());
let result = encode_function_call(
&functions,
"batchTransfer(\
[address(0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1), address(0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad2)],\
[uint256(100), uint256(200)]\
)"
);
assert!(result.is_ok(), "Arrays with type casts should work: {:?}", result.err());
}
#[test]
fn test_mixed_argument_types() {
let mut functions = BTreeMap::new();
let mixed = create_test_function(
"mixedTypes",
vec![("flag", "bool"), ("data", "bytes"), ("text", "string"), ("number", "int256")],
);
functions.insert("mixedTypes".to_string(), vec![mixed]);
let result =
encode_function_call(&functions, r#"mixedTypes(true, 0xabcdef, "hello world", -123)"#);
assert!(result.is_ok(), "Mixed types should work: {:?}", result.err());
let result = encode_function_call(
&functions,
r#"mixedTypes(bool(true), bytes(0xabcdef), string("hello world"), int256(-123))"#,
);
assert!(result.is_ok(), "Mixed types with casts should work: {:?}", result.err());
}
#[test]
fn test_edge_cases() {
let mut functions = BTreeMap::new();
let simple = create_test_function("test", vec![("value", "uint256")]);
functions.insert("test".to_string(), vec![simple]);
let result = encode_function_call(&functions, "test( 123 )");
assert!(result.is_ok());
let result = encode_function_call(&functions, "test(\t123\n)");
assert!(result.is_ok());
let result = encode_function_call(&functions, "test(0xff)");
assert!(result.is_ok());
let result = encode_function_call(&functions, "test(1e18)");
assert!(result.is_err());
let result = encode_function_call(&functions, "test(1_000_000)");
let _ = result;
}
#[test]
fn test_empty_and_single_arguments() {
let mut functions = BTreeMap::new();
let no_args = create_test_function("noArgs", vec![]);
functions.insert("noArgs".to_string(), vec![no_args]);
let single_arg = create_test_function("singleArg", vec![("value", "uint256")]);
functions.insert("singleArg".to_string(), vec![single_arg]);
assert!(encode_function_call(&functions, "noArgs()").is_ok());
assert!(encode_function_call(&functions, "noArgs( )").is_ok());
assert!(encode_function_call(&functions, "noArgs( )").is_ok());
assert!(encode_function_call(&functions, "singleArg(42)").is_ok());
assert!(encode_function_call(&functions, "singleArg( 42 )").is_ok());
assert!(encode_function_call(&functions, "singleArg(uint256(42))").is_ok());
}
#[test]
fn test_fixed_arrays() {
let mut functions = BTreeMap::new();
let fixed = create_test_function("fixedArray", vec![("values", "uint256[3]")]);
functions.insert("fixedArray".to_string(), vec![fixed]);
let result = encode_function_call(&functions, "fixedArray([1, 2, 3])");
assert!(result.is_ok());
let result = encode_function_call(&functions, "fixedArray([1, 2])");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Fixed array size mismatch"));
let result = encode_function_call(&functions, "fixedArray([1, 2, 3, 4])");
assert!(result.is_err());
}
#[test]
fn test_string_escaping() {
let mut functions = BTreeMap::new();
let string_fn = create_test_function("setString", vec![("text", "string")]);
functions.insert("setString".to_string(), vec![string_fn]);
let result = encode_function_call(&functions, r#"setString("hello \"world\"")"#);
assert!(result.is_ok());
let result = encode_function_call(&functions, r#"setString('hello world')"#);
assert!(result.is_ok());
let result = encode_function_call(&functions, r#"setString("it's working")"#);
assert!(result.is_ok());
let result = encode_function_call(&functions, r#"setString("hello, world")"#);
assert!(result.is_ok());
let result = encode_function_call(&functions, r#"setString("test(123)")"#);
assert!(result.is_ok());
let result = encode_function_call(&functions, r#"setString("{key: value}")"#);
assert!(result.is_ok());
}
#[test]
fn test_invalid_type_casts() {
let mut functions = BTreeMap::new();
let transfer =
create_test_function("transfer", vec![("to", "address"), ("amount", "uint256")]);
functions.insert("transfer".to_string(), vec![transfer]);
let result =
encode_function_call(&functions, r#"transfer(string("not an address"), uint256(100))"#);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not compatible"));
let result = encode_function_call(
&functions,
"transfer(0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1, bool(true))",
);
assert!(result.is_err());
}
#[test]
fn test_deeply_nested_structures() {
let mut functions = BTreeMap::new();
let deep = create_test_function(
"deeplyNested",
vec![("data", "(uint256,(address,(bytes32,bool)[]))")],
);
functions.insert("deeplyNested".to_string(), vec![deep]);
let result = encode_function_call(
&functions,
"deeplyNested((123, (\
0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1, \
[(\
0x0000000000000000000000000000000000000000000000000000000000000001, \
true\
), (\
0x0000000000000000000000000000000000000000000000000000000000000002, \
false\
)]\
)))",
);
assert!(result.is_ok(), "Deeply nested structure should work: {:?}", result.err());
}
#[test]
fn test_all_solidity_types() {
assert!(parse_uint("123", 256).is_ok());
assert!(parse_uint("0xff", 256).is_ok());
assert!(parse_uint("0", 256).is_ok());
assert!(parse_int("123", 256).is_ok());
assert!(parse_int("-123", 256).is_ok());
assert!(parse_int("0", 256).is_ok());
assert!(parse_bool("true").is_ok());
assert!(parse_bool("false").is_ok());
assert!(parse_bool("1").is_ok());
assert!(parse_bool("0").is_ok());
assert!(parse_address("0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1").is_ok());
assert!(parse_address("742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1").is_ok());
assert!(parse_bytes("0xabcdef").is_ok());
assert!(parse_bytes("abcdef").is_ok());
assert!(parse_bytes("0x").is_ok());
assert!(parse_string("hello world").is_ok());
assert!(parse_string("\"quoted string\"").is_ok());
assert!(parse_string("'single quoted'").is_ok());
}
#[test]
fn test_overload_with_structs() {
let mut functions = BTreeMap::new();
let process1 = create_test_function("process", vec![("data", "(address,uint256)")]);
let process2 = create_test_function("process", vec![("data", "(uint256,address)")]);
functions.insert("process".to_string(), vec![process1, process2]);
let result1 = encode_function_call(
&functions,
"process((0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1, 100))",
);
assert!(result1.is_ok());
let result2 = encode_function_call(
&functions,
"process((100, 0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1))",
);
assert!(result2.is_ok());
assert_ne!(result1.unwrap()[0..4], result2.unwrap()[0..4]);
}
#[test]
fn test_payable_and_nonpayable_functions() {
let mut functions = BTreeMap::new();
let send_eth = create_test_function("sendEth", vec![("to", "address")]);
functions.insert("sendEth".to_string(), vec![send_eth]);
let result =
encode_function_call(&functions, "sendEth(0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1)");
assert!(result.is_ok());
}
#[test]
fn test_max_values() {
let mut functions = BTreeMap::new();
let max_test =
create_test_function("maxTest", vec![("maxUint", "uint256"), ("maxInt", "int256")]);
functions.insert("maxTest".to_string(), vec![max_test]);
let result = encode_function_call(
&functions,
"maxTest(\
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, \
0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\
)",
);
assert!(result.is_ok(), "Max values should work: {:?}", result.err());
let result = encode_function_call(
&functions,
"maxTest(\
uint256(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff), \
int256(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)\
)",
);
assert!(result.is_ok());
}
#[test]
fn test_bytes_variations() {
let mut functions = BTreeMap::new();
let bytes_test = create_test_function(
"bytesTest",
vec![
("b1", "bytes1"),
("b2", "bytes2"),
("b4", "bytes4"),
("b8", "bytes8"),
("b16", "bytes16"),
("b32", "bytes32"),
],
);
functions.insert("bytesTest".to_string(), vec![bytes_test]);
let result = encode_function_call(
&functions,
"bytesTest(\
0x01, \
0x0102, \
0x01020304, \
0x0102030405060708, \
0x01020304050607080910111213141516, \
0x0102030405060708091011121314151617181920212223242526272829303132\
)",
);
assert!(result.is_ok(), "Different bytes sizes should work: {:?}", result.err());
}
#[test]
fn test_function_with_multiple_arrays() {
let mut functions = BTreeMap::new();
let multi_array = create_test_function(
"multiArray",
vec![("arr1", "uint256[]"), ("arr2", "address[]"), ("arr3", "bool[]")],
);
functions.insert("multiArray".to_string(), vec![multi_array]);
let result = encode_function_call(
&functions,
"multiArray(\
[1, 2, 3], \
[0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1], \
[true, false, true]\
)",
);
assert!(result.is_ok(), "Multiple arrays should work: {:?}", result.err());
}
#[test]
fn test_empty_arrays_and_strings() {
let mut functions = BTreeMap::new();
let empty_test = create_test_function(
"emptyTest",
vec![("emptyArr", "uint256[]"), ("emptyStr", "string"), ("emptyBytes", "bytes")],
);
functions.insert("emptyTest".to_string(), vec![empty_test]);
let result = encode_function_call(&functions, r#"emptyTest([], "", 0x)"#);
assert!(result.is_ok(), "Empty values should work: {:?}", result.err());
let result = encode_function_call(&functions, r#"emptyTest([], string(""), bytes(0x))"#);
assert!(result.is_ok());
}
#[test]
fn test_special_characters_in_strings() {
let mut functions = BTreeMap::new();
let special = create_test_function("specialChars", vec![("text", "string")]);
functions.insert("specialChars".to_string(), vec![special]);
let result =
encode_function_call(&functions, r#"specialChars("line1\nline2\ttab\r\nwindows")"#);
assert!(result.is_ok());
let result = encode_function_call(&functions, r#"specialChars("Hello 世界 🌍")"#);
assert!(result.is_ok());
let result = encode_function_call(&functions, r#"specialChars("path\\to\\file")"#);
assert!(result.is_ok());
}
#[test]
fn test_complex_overload_resolution() {
let mut functions = BTreeMap::new();
let func1 = create_test_function("complex", vec![("a", "uint256")]);
let func2 = create_test_function("complex", vec![("a", "uint256"), ("b", "uint256")]);
let func3 = create_test_function("complex", vec![("a", "uint256"), ("b", "address")]);
let func4 = create_test_function("complex", vec![("data", "(uint256,address)")]);
functions.insert("complex".to_string(), vec![func1, func2, func3, func4]);
assert!(encode_function_call(&functions, "complex(123)").is_ok());
assert!(encode_function_call(&functions, "complex(123, 456)").is_ok());
assert!(encode_function_call(
&functions,
"complex(123, 0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1)"
)
.is_ok());
assert!(encode_function_call(
&functions,
"complex((123, 0x742d35Cc6634C0532925a3b8D6Ac6E89e86C6Ad1))"
)
.is_ok());
}
#[test]
fn test_error_messages() {
let mut functions = BTreeMap::new();
let test_fn = create_test_function("test", vec![("value", "uint256")]);
functions.insert("test".to_string(), vec![test_fn]);
let result = encode_function_call(&functions, "nonexistent(123)");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
let result = encode_function_call(&functions, "test(0xnotahexvalue)");
assert!(result.is_err());
let result = encode_function_call(&functions, "test(true)"); assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Failed to parse"));
let result = encode_function_call(&functions, "test"); assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Invalid function call format"));
let result = encode_function_call(&functions, "test("); assert!(result.is_err());
}
#[test]
fn test_zero_and_negative_values() {
let mut functions = BTreeMap::new();
let zero_test = create_test_function(
"zeroTest",
vec![("uintZero", "uint256"), ("intNeg", "int256"), ("intZero", "int256")],
);
functions.insert("zeroTest".to_string(), vec![zero_test]);
let result = encode_function_call(&functions, "zeroTest(0, -1, 0)");
assert!(result.is_ok());
let result = encode_function_call(&functions, "zeroTest(0x0, -0x1, 0x00)");
assert!(result.is_ok());
let result = encode_function_call(
&functions,
"zeroTest(0, -57896044618658097711785492504343953926634992332820282019728792003956564819968, 0)"
);
assert!(result.is_ok());
}
}