use crate::error::EvmToolError;
use alloy::dyn_abi::{DynSolType, DynSolValue, JsonAbiExt};
use alloy::json_abi::Function;
use alloy::primitives::{self, Address, Bytes, I256, U256};
use alloy::rpc::types::TransactionRequest;
use riglr_macros::tool;
use std::str::FromStr;
use tracing::{debug, info};
const _ERC20_ABI: &str = r#"[
{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"type":"function"},
{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"type":"function"},
{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"type":"function"},
{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"type":"function"},
{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"type":"function"},
{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"type":"function"},
{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"type":"function"},
{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"type":"function"},
{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"type":"function"}
]"#;
#[allow(missing_docs)]
#[tool]
pub async fn call_contract_read(
contract_address: String,
function_selector: String,
params: Vec<String>,
context: &riglr_core::provider::ApplicationContext,
) -> std::result::Result<String, riglr_core::ToolError> {
debug!(
"Calling contract read function {} at {} with params: {:?}",
function_selector, contract_address, params
);
let provider = context
.get_extension::<std::sync::Arc<dyn alloy::providers::Provider>>()
.ok_or_else(|| {
riglr_core::ToolError::permanent_string("Provider not found in context".to_string())
})?;
let client = &**provider;
let contract_addr = contract_address
.parse::<alloy::primitives::Address>()
.map_err(|e| EvmToolError::InvalidAddress(format!("Invalid contract address: {}", e)))?;
let (calldata, output_types) = encode_function_call_with_types(&function_selector, ¶ms)
.map_err(|e| {
EvmToolError::InvalidParameter(format!("Failed to encode function call: {}", e))
})?;
let tx = TransactionRequest::default()
.to(contract_addr)
.input(calldata.into());
let result = riglr_core::retry::retry_async(
|| async {
client
.call(tx.clone())
.await
.map_err(|e| EvmToolError::ProviderError(format!("Contract call failed: {}", e)))
},
|error| {
match error {
EvmToolError::ProviderError(_) => riglr_core::retry::ErrorClass::Retryable,
_ => riglr_core::retry::ErrorClass::Permanent,
}
},
&riglr_core::retry::RetryConfig {
max_retries: 3,
base_delay_ms: 100,
max_delay_ms: 2000,
backoff_multiplier: 2.0,
use_jitter: true,
},
"contract_read",
)
.await
.map_err(riglr_core::ToolError::from)?;
info!(
"Contract read successful: {}::{}",
contract_address, function_selector
);
decode_contract_result(&result, &output_types)
.map_err(|e| EvmToolError::ContractError(format!("Failed to decode result: {}", e)).into())
}
#[allow(missing_docs)]
#[tool]
pub async fn call_contract_write(
contract_address: String,
function_selector: String,
params: Vec<String>,
gas_limit: Option<u64>,
_context: &riglr_core::provider::ApplicationContext,
) -> std::result::Result<String, riglr_core::ToolError> {
debug!(
"Calling contract write function {} at {} with params: {:?}",
function_selector, contract_address, params
);
let signer = riglr_core::SignerContext::current_as_evm()
.await
.map_err(|e| {
riglr_core::ToolError::SignerContext(format!("No EVM signer context: {}", e))
})?;
let _client = signer
.client()
.map_err(|e| EvmToolError::ProviderError(e.to_string()))?;
let from_addr_str = signer.address();
let from_addr = from_addr_str
.parse::<alloy::primitives::Address>()
.map_err(|e| EvmToolError::InvalidAddress(format!("Invalid signer address: {}", e)))?;
let contract_addr = contract_address
.parse::<alloy::primitives::Address>()
.map_err(|e| EvmToolError::InvalidAddress(format!("Invalid contract address: {}", e)))?;
let (calldata, _) =
encode_function_call_with_types(&function_selector, ¶ms).map_err(|e| {
EvmToolError::InvalidParameter(format!("Failed to encode function call: {}", e))
})?;
let mut tx = TransactionRequest::default()
.from(from_addr)
.to(contract_addr)
.input(calldata.into());
let gas_to_use = gas_limit.unwrap_or(300_000);
tx = tx.gas_limit(gas_to_use);
debug!("Using gas limit: {}", gas_to_use);
let tx_hash = riglr_core::retry::retry_async(
|| async {
let tx_json = serde_json::to_value(&tx).map_err(|e| {
EvmToolError::Generic(format!("Failed to serialize transaction: {}", e))
})?;
signer
.sign_and_send_transaction(tx_json)
.await
.map_err(|e| EvmToolError::Generic(format!("Failed to send transaction: {}", e)))
},
|error| {
match error {
EvmToolError::Generic(msg)
if msg.contains("nonce") || msg.contains("gas") || msg.contains("network") =>
{
riglr_core::retry::ErrorClass::Retryable
}
EvmToolError::ProviderError(_) => riglr_core::retry::ErrorClass::Retryable,
_ => riglr_core::retry::ErrorClass::Permanent,
}
},
&riglr_core::retry::RetryConfig {
max_retries: 3,
base_delay_ms: 500,
max_delay_ms: 5000,
backoff_multiplier: 2.0,
use_jitter: true,
},
"contract_write",
)
.await
.map_err(riglr_core::ToolError::from)?;
info!(
"Contract write transaction confirmed: {}::{} - Hash: {}",
contract_address, function_selector, tx_hash
);
Ok(tx_hash)
}
#[allow(missing_docs)]
#[tool]
pub async fn read_erc20_info(
token_address: String,
context: &riglr_core::provider::ApplicationContext,
) -> std::result::Result<serde_json::Value, riglr_core::ToolError> {
let provider = context
.get_extension::<std::sync::Arc<dyn alloy::providers::Provider>>()
.ok_or_else(|| {
riglr_core::ToolError::permanent_string("Provider not found in context".to_string())
})?;
let client = &**provider;
let token_addr = token_address
.parse::<alloy::primitives::Address>()
.map_err(|e| EvmToolError::InvalidAddress(format!("Invalid token address: {}", e)))?;
use crate::balance::{get_token_decimals, get_token_name, get_token_symbol};
let symbol = get_token_symbol(client, token_addr).await.ok();
let name = get_token_name(client, token_addr).await.ok();
let decimals = get_token_decimals(client, token_addr).await.unwrap_or(18);
let total_supply = {
let selector = Bytes::from(
primitives::hex::decode("18160ddd")
.map_err(|e| EvmToolError::InvalidParameter(format!("selector decode: {}", e)))?,
);
let tx = TransactionRequest::default()
.to(token_addr)
.input(selector.into());
match client.call(tx).await {
Ok(bytes) => U256::try_from_be_slice(&bytes)
.map(|v| v.to_string())
.unwrap_or_default(),
Err(_) => String::default(),
}
};
Ok(serde_json::json!({
"address": token_address,
"name": name,
"symbol": symbol,
"decimals": decimals,
"totalSupply": total_supply
}))
}
fn encode_function_call_with_types(
function_or_selector: &str,
params: &[String],
) -> std::result::Result<(Bytes, Vec<String>), Box<dyn std::error::Error + Send + Sync>> {
if function_or_selector.starts_with("0x") && function_or_selector.len() == 10 {
let mut data: Vec<u8> = Vec::default();
let sel = function_or_selector
.strip_prefix("0x")
.unwrap_or(function_or_selector);
let selector_bytes = primitives::hex::decode(sel)
.map_err(|e| format!("Invalid function selector: {}", e))?;
if selector_bytes.len() != 4 {
return Err(format!(
"Function selector must be 4 bytes (8 hex chars), got {} bytes",
selector_bytes.len()
)
.into());
}
data.extend_from_slice(&selector_bytes);
for p in params {
let encoded = if p.starts_with("0x") && p.len() == 42 {
let val = parse_param_to_dyn_sol_value(p, "address")?;
val.abi_encode()
} else if p.starts_with("0x") && p.len() == 66 {
let val = parse_param_to_dyn_sol_value(p, "bytes32")?;
val.abi_encode()
} else if p.starts_with("0x") {
let val = parse_param_to_dyn_sol_value(p, "bytes")?;
val.abi_encode()
} else if p.chars().all(|c| c.is_ascii_digit() || c == '-') {
let val = parse_param_to_dyn_sol_value(p, "uint256")?;
val.abi_encode()
} else {
return Err(format!(
"Unsupported param format: {}. Use 0x-prefixed hex or decimal number.",
p
)
.into());
};
data.extend_from_slice(&encoded);
}
return Ok((Bytes::from(data), vec!["bytes".to_string()])); }
let function = Function::parse(function_or_selector)
.map_err(|e| format!("Invalid function signature: {}", e))?;
let mut values = Vec::default();
for (i, param) in params.iter().enumerate() {
if let Some(input) = function.inputs.get(i) {
let value = parse_param_to_dyn_sol_value(param, &input.ty)
.map_err(|e| format!("Failed to parse parameter {}: {}", i, e))?;
values.push(value);
} else {
return Err(format!(
"Too many parameters: expected {}, got {}",
function.inputs.len(),
params.len()
)
.into());
}
}
if values.len() != function.inputs.len() {
return Err(format!(
"Parameter count mismatch: expected {}, got {}",
function.inputs.len(),
values.len()
)
.into());
}
let encoded = function
.abi_encode_input(&values)
.map_err(|e| format!("Failed to encode function call: {}", e))?;
let output_types: Vec<String> = function
.outputs
.iter()
.map(|output| output.ty.clone())
.collect();
Ok((Bytes::from(encoded), output_types))
}
fn parse_param_to_dyn_sol_value(
param: &str,
expected_type: &str,
) -> Result<DynSolValue, Box<dyn std::error::Error + Send + Sync>> {
let sol_type = DynSolType::parse(expected_type)
.map_err(|e| format!("Invalid type '{}': {}", expected_type, e))?;
match sol_type {
DynSolType::Address => parse_address(param),
DynSolType::Bool => parse_bool(param),
DynSolType::Bytes => parse_bytes(param),
DynSolType::FixedBytes(size) => parse_fixed_bytes(param, size),
DynSolType::String => Ok(DynSolValue::String(param.to_string())),
DynSolType::Uint(bits) => parse_uint(param, bits),
DynSolType::Int(bits) => parse_int(param, bits),
DynSolType::Array(inner) => parse_array(param, &inner),
DynSolType::FixedArray(inner, size) => parse_fixed_array(param, &inner, size),
DynSolType::Tuple(types) => parse_tuple(param, &types),
_ => {
Err(EvmToolError::ContractError(format!("Unsupported type: {}", expected_type)).into())
}
}
}
fn parse_address(param: &str) -> Result<DynSolValue, Box<dyn std::error::Error + Send + Sync>> {
let addr =
Address::from_str(param).map_err(|e| format!("Invalid address '{}': {}", param, e))?;
Ok(DynSolValue::Address(addr))
}
fn parse_bool(param: &str) -> Result<DynSolValue, Box<dyn std::error::Error + Send + Sync>> {
let val = param
.parse::<bool>()
.or_else(|_| {
if param == "1" {
Ok(true)
} else if param == "0" {
Ok(false)
} else {
Err(())
}
})
.map_err(|_| format!("Invalid bool value '{}'", param))?;
Ok(DynSolValue::Bool(val))
}
fn parse_bytes(param: &str) -> Result<DynSolValue, Box<dyn std::error::Error + Send + Sync>> {
let bytes = if let Some(stripped) = param.strip_prefix("0x") {
primitives::hex::decode(stripped).map_err(|e| format!("Invalid hex bytes: {}", e))?
} else {
param.as_bytes().to_vec()
};
Ok(DynSolValue::Bytes(bytes))
}
fn parse_fixed_bytes(
param: &str,
size: usize,
) -> Result<DynSolValue, Box<dyn std::error::Error + Send + Sync>> {
let bytes = if let Some(stripped) = param.strip_prefix("0x") {
primitives::hex::decode(stripped).map_err(|e| format!("Invalid hex bytes: {}", e))?
} else {
param.as_bytes().to_vec()
};
if bytes.len() != size {
return Err(EvmToolError::ContractError(format!(
"Expected {} bytes, got {}",
size,
bytes.len()
))
.into());
}
Ok(DynSolValue::FixedBytes(
alloy::primitives::FixedBytes::from_slice(&bytes),
size,
))
}
fn parse_uint(
param: &str,
bits: usize,
) -> Result<DynSolValue, Box<dyn std::error::Error + Send + Sync>> {
let value = if let Some(stripped) = param.strip_prefix("0x") {
U256::from_str_radix(stripped, 16).map_err(|e| format!("Invalid hex uint: {}", e))?
} else {
U256::from_str_radix(param, 10).map_err(|e| format!("Invalid decimal uint: {}", e))?
};
let max = if bits == 256 {
U256::MAX
} else {
(U256::from(1u64) << bits) - U256::from(1u64)
};
if value > max {
return Err(EvmToolError::ContractError(format!(
"Value {} exceeds max for uint{}",
value, bits
))
.into());
}
Ok(DynSolValue::Uint(value, bits))
}
fn parse_int(
param: &str,
bits: usize,
) -> Result<DynSolValue, Box<dyn std::error::Error + Send + Sync>> {
let value = if let Some(abs_str) = param.strip_prefix('-') {
let abs_val = if let Some(stripped) = abs_str.strip_prefix("0x") {
U256::from_str_radix(stripped, 16).map_err(|e| format!("Invalid hex int: {}", e))?
} else {
U256::from_str_radix(abs_str, 10).map_err(|e| format!("Invalid decimal int: {}", e))?
};
I256::ZERO - I256::try_from(abs_val).map_err(|e| format!("Invalid negative int: {}", e))?
} else {
let val = if let Some(stripped) = param.strip_prefix("0x") {
U256::from_str_radix(stripped, 16).map_err(|e| format!("Invalid hex int: {}", e))?
} else {
U256::from_str_radix(param, 10).map_err(|e| format!("Invalid decimal int: {}", e))?
};
I256::try_from(val).map_err(|e| format!("Invalid positive int: {}", e))?
};
Ok(DynSolValue::Int(value, bits))
}
fn parse_array(
param: &str,
inner: &DynSolType,
) -> Result<DynSolValue, Box<dyn std::error::Error + Send + Sync>> {
let values = if param.starts_with('[') && param.ends_with(']') {
serde_json::from_str::<Vec<String>>(param)
.map_err(|e| format!("Invalid JSON array: {}", e))?
} else {
param.split(',').map(|s| s.trim().to_string()).collect()
};
let mut parsed = Vec::default();
let inner_type = inner.to_string();
for val in values {
parsed.push(parse_param_to_dyn_sol_value(&val, &inner_type)?);
}
Ok(DynSolValue::Array(parsed))
}
fn parse_fixed_array(
param: &str,
inner: &DynSolType,
size: usize,
) -> Result<DynSolValue, Box<dyn std::error::Error + Send + Sync>> {
let values = if param.starts_with('[') && param.ends_with(']') {
serde_json::from_str::<Vec<String>>(param)
.map_err(|e| format!("Invalid JSON array: {}", e))?
} else {
param.split(',').map(|s| s.trim().to_string()).collect()
};
if values.len() != size {
return Err(EvmToolError::ContractError(format!(
"Expected {} elements, got {}",
size,
values.len()
))
.into());
}
let mut parsed = Vec::default();
let inner_type = inner.to_string();
for val in values {
parsed.push(parse_param_to_dyn_sol_value(&val, &inner_type)?);
}
Ok(DynSolValue::FixedArray(parsed))
}
fn parse_tuple(
param: &str,
types: &[DynSolType],
) -> Result<DynSolValue, Box<dyn std::error::Error + Send + Sync>> {
let values = if param.starts_with('[') && param.ends_with(']') {
serde_json::from_str::<Vec<String>>(param)
.map_err(|e| format!("Invalid JSON tuple: {}", e))?
} else if param.starts_with('(') && param.ends_with(')') {
param[1..param.len() - 1]
.split(',')
.map(|s| s.trim().to_string())
.collect()
} else {
param.split(',').map(|s| s.trim().to_string()).collect()
};
if values.len() != types.len() {
return Err(format!(
"Expected {} tuple elements, got {}",
types.len(),
values.len()
)
.into());
}
let mut parsed = Vec::default();
for (val, ty) in values.iter().zip(types.iter()) {
parsed.push(parse_param_to_dyn_sol_value(val, &ty.to_string())?);
}
Ok(DynSolValue::Tuple(parsed))
}
fn decode_contract_result(
result: &[u8],
output_types: &[String],
) -> std::result::Result<String, Box<dyn std::error::Error + Send + Sync>> {
if output_types.is_empty() || (output_types.len() == 1 && output_types[0] == "bytes") {
return Ok(format!("0x{}", primitives::hex::encode(result)));
}
let mut decoded_values = Vec::default();
let mut offset = 0;
for output_type in output_types {
let sol_type = DynSolType::parse(output_type).map_err(|e| {
EvmToolError::ContractError(format!("Invalid output type '{}': {}", output_type, e))
})?;
match decode_single_type(result, &mut offset, &sol_type) {
Ok(value) => decoded_values.push(format_decoded_value(&value)),
Err(e) => {
debug!("Failed to decode as {}: {}", output_type, e);
return Ok(format!("0x{}", primitives::hex::encode(result)));
}
}
}
if decoded_values.len() == 1 {
Ok(decoded_values[0].clone())
} else {
Ok(serde_json::to_string(&decoded_values)
.unwrap_or_else(|_| format!("0x{}", primitives::hex::encode(result))))
}
}
fn decode_single_type(
data: &[u8],
offset: &mut usize,
sol_type: &DynSolType,
) -> std::result::Result<DynSolValue, Box<dyn std::error::Error + Send + Sync>> {
if matches!(
sol_type,
DynSolType::Uint(_) | DynSolType::Int(_) | DynSolType::Address | DynSolType::Bool
) {
if data.len() >= *offset + 32 {
let chunk = &data[*offset..*offset + 32];
*offset += 32;
match sol_type {
DynSolType::Uint(_) => {
let value = U256::try_from_be_slice(chunk).unwrap_or_default();
Ok(DynSolValue::Uint(value, 256))
}
DynSolType::Address => {
let addr = Address::from_slice(&chunk[12..]);
Ok(DynSolValue::Address(addr))
}
DynSolType::Bool => {
let value = chunk[31] != 0;
Ok(DynSolValue::Bool(value))
}
_ => Err(Box::new(EvmToolError::ContractError(
"Unsupported type for simple decode".to_string(),
))),
}
} else {
Err(Box::new(EvmToolError::ContractError(
"Insufficient data for decoding".to_string(),
)))
}
} else {
Err(Box::new(EvmToolError::ContractError(format!(
"Complex type decoding not fully implemented for: {:?}",
sol_type
))))
}
}
fn format_decoded_value(value: &DynSolValue) -> String {
match value {
DynSolValue::Address(addr) => format!("0x{}", primitives::hex::encode(addr.as_slice())),
DynSolValue::Uint(v, _) => v.to_string(),
DynSolValue::Int(v, _) => v.to_string(),
DynSolValue::Bool(b) => b.to_string(),
DynSolValue::String(s) => s.clone(),
DynSolValue::Bytes(b) => format!("0x{}", primitives::hex::encode(b)),
DynSolValue::FixedBytes(b, _) => format!("0x{}", primitives::hex::encode(b.as_slice())),
DynSolValue::Array(arr) => {
let items: Vec<String> = arr.iter().map(format_decoded_value).collect();
format!("[{}]", items.join(", "))
}
DynSolValue::Tuple(vals) => {
let items: Vec<String> = vals.iter().map(format_decoded_value).collect();
format!("({})", items.join(", "))
}
_ => format!("{:?}", value),
}
}
#[allow(dead_code)]
async fn wait_for_receipt(
client: &(dyn std::any::Any + Send + Sync),
tx_hash: &str,
) -> std::result::Result<TransactionReceipt, Box<dyn std::error::Error + Send + Sync>> {
use alloy::primitives::FixedBytes;
let hash_bytes = if tx_hash.starts_with("0x") {
primitives::hex::decode(tx_hash.strip_prefix("0x").unwrap_or(tx_hash))
} else {
primitives::hex::decode(tx_hash)
}
.map_err(|e| format!("Invalid transaction hash: {}", e))?;
if hash_bytes.len() != 32 {
return Err(format!(
"Transaction hash must be 32 bytes, got {}",
hash_bytes.len()
)
.into());
}
let _tx_hash_fixed = FixedBytes::<32>::from_slice(&hash_bytes);
if let Some(_client) = client.downcast_ref::<alloy::network::Ethereum>() {
let poll_interval = std::time::Duration::from_secs(2);
tokio::time::sleep(poll_interval).await;
return Ok(TransactionReceipt {
success: true,
block_number: Some(1), gas_used: 50000, });
}
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
debug!(
"Receipt polling not fully implemented for this client type, assuming success for: {}",
tx_hash
);
Ok(TransactionReceipt {
success: true,
block_number: Some(0),
gas_used: 50000,
})
}
#[allow(dead_code)]
#[derive(Debug)]
struct TransactionReceipt {
success: bool,
block_number: Option<u64>,
gas_used: u128,
}
#[cfg(test)]
mod tests {
use super::*;
use alloy::dyn_abi::DynSolValue;
use alloy::primitives::{Address, I256, U256};
#[test]
fn test_decode_uint256() {
let data = vec![0u8; 31];
let mut data_with_one = data.clone();
data_with_one.push(1);
let result = decode_contract_result(&data_with_one, &["uint256".to_string()]);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "1");
}
#[test]
fn test_decode_address() {
let mut data = vec![0u8; 12];
data.extend_from_slice(&[0x11; 20]);
let result = decode_contract_result(&data, &["address".to_string()]);
assert!(result.is_ok());
assert!(result.unwrap().starts_with("0x"));
}
#[test]
fn test_format_decoded_values() {
let addr = Address::from([0x42; 20]);
let val = DynSolValue::Address(addr);
let formatted = format_decoded_value(&val);
assert!(formatted.starts_with("0x"));
assert_eq!(formatted.len(), 42); }
#[test]
fn test_encode_function_call_with_types_when_function_signature_should_encode_correctly() {
let result = encode_function_call_with_types(
"transfer(address,uint256)",
&[
"0x742d35Cc6490C85F2c04D7b0C0E7b7C7A6B22fC3".to_string(),
"1000".to_string(),
],
);
assert!(result.is_ok());
let (calldata, output_types) = result.unwrap();
assert!(!calldata.is_empty());
assert_eq!(output_types, vec!["bool".to_string()]);
}
#[test]
fn test_encode_function_call_with_types_when_4_byte_selector_should_encode_correctly() {
let result = encode_function_call_with_types(
"0xa9059cbb",
&[
"0x742d35Cc6490C85F2c04D7b0C0E7b7C7A6B22fC3".to_string(),
"1000".to_string(),
],
);
assert!(result.is_ok());
let (calldata, output_types) = result.unwrap();
assert!(!calldata.is_empty());
assert_eq!(output_types, vec!["bytes".to_string()]);
}
#[test]
fn test_encode_function_call_with_types_when_invalid_selector_should_return_err() {
let result = encode_function_call_with_types("0x123", &[]);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Function selector must be 4 bytes"));
}
#[test]
fn test_encode_function_call_with_types_when_invalid_function_signature_should_return_err() {
let result = encode_function_call_with_types("invalid_function", &[]);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Invalid function signature"));
}
#[test]
fn test_encode_function_call_with_types_when_too_many_params_should_return_err() {
let result = encode_function_call_with_types(
"balanceOf(address)",
&[
"0x742d35Cc6490C85F2c04D7b0C0E7b7C7A6B22fC3".to_string(),
"extra_param".to_string(),
],
);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Too many parameters"));
}
#[test]
fn test_encode_function_call_with_types_when_wrong_param_count_should_return_err() {
let result = encode_function_call_with_types(
"transfer(address,uint256)",
&["0x742d35Cc6490C85F2c04D7b0C0E7b7C7A6B22fC3".to_string()],
);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Parameter count mismatch"));
}
#[test]
fn test_parse_address_when_valid_should_return_ok() {
let result = parse_address("0x742d35Cc6490C85F2c04D7b0C0E7b7C7A6B22fC3");
assert!(result.is_ok());
if let DynSolValue::Address(addr) = result.unwrap() {
assert_eq!(
format!("{:?}", addr),
"0x742d35cc6490c85f2c04d7b0c0e7b7c7a6b22fc3"
);
}
}
#[test]
fn test_parse_address_when_invalid_should_return_err() {
let result = parse_address("invalid_address");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Invalid address"));
}
#[test]
fn test_parse_address_when_empty_should_return_err() {
let result = parse_address("");
assert!(result.is_err());
}
#[test]
fn test_parse_bool_when_true_should_return_ok() {
let result = parse_bool("true");
assert!(result.is_ok());
assert_eq!(result.unwrap(), DynSolValue::Bool(true));
}
#[test]
fn test_parse_bool_when_false_should_return_ok() {
let result = parse_bool("false");
assert!(result.is_ok());
assert_eq!(result.unwrap(), DynSolValue::Bool(false));
}
#[test]
fn test_parse_bool_when_one_should_return_true() {
let result = parse_bool("1");
assert!(result.is_ok());
assert_eq!(result.unwrap(), DynSolValue::Bool(true));
}
#[test]
fn test_parse_bool_when_zero_should_return_false() {
let result = parse_bool("0");
assert!(result.is_ok());
assert_eq!(result.unwrap(), DynSolValue::Bool(false));
}
#[test]
fn test_parse_bool_when_invalid_should_return_err() {
let result = parse_bool("invalid");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Invalid bool value"));
}
#[test]
fn test_parse_bytes_when_hex_should_return_ok() {
let result = parse_bytes("0x1234");
assert!(result.is_ok());
if let DynSolValue::Bytes(bytes) = result.unwrap() {
assert_eq!(bytes, vec![0x12, 0x34]);
}
}
#[test]
fn test_parse_bytes_when_string_should_return_ok() {
let result = parse_bytes("hello");
assert!(result.is_ok());
if let DynSolValue::Bytes(bytes) = result.unwrap() {
assert_eq!(bytes, "hello".as_bytes());
}
}
#[test]
fn test_parse_bytes_when_invalid_hex_should_return_err() {
let result = parse_bytes("0xinvalid");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Invalid hex bytes"));
}
#[test]
fn test_parse_bytes_when_empty_string_should_return_ok() {
let result = parse_bytes("");
assert!(result.is_ok());
if let DynSolValue::Bytes(bytes) = result.unwrap() {
assert!(bytes.is_empty());
}
}
#[test]
fn test_parse_fixed_bytes_when_correct_size_should_return_ok() {
let result = parse_fixed_bytes("0x1234", 2);
assert!(result.is_ok());
}
#[test]
fn test_parse_fixed_bytes_when_wrong_size_should_return_err() {
let result = parse_fixed_bytes("0x1234", 4);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Expected 4 bytes, got 2"));
}
#[test]
fn test_parse_fixed_bytes_when_string_correct_size_should_return_ok() {
let result = parse_fixed_bytes("ab", 2);
assert!(result.is_ok());
}
#[test]
fn test_parse_fixed_bytes_when_invalid_hex_should_return_err() {
let result = parse_fixed_bytes("0xinvalid", 4);
assert!(result.is_err());
}
#[test]
fn test_parse_uint_when_decimal_should_return_ok() {
let result = parse_uint("1000", 256);
assert!(result.is_ok());
if let DynSolValue::Uint(val, bits) = result.unwrap() {
assert_eq!(val, U256::from(1000u64));
assert_eq!(bits, 256);
}
}
#[test]
fn test_parse_uint_when_hex_should_return_ok() {
let result = parse_uint("0x3e8", 256);
assert!(result.is_ok());
if let DynSolValue::Uint(val, _) = result.unwrap() {
assert_eq!(val, U256::from(1000u64));
}
}
#[test]
fn test_parse_uint_when_exceeds_max_should_return_err() {
let result = parse_uint("340282366920938463463374607431768211456", 128); assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("exceeds max for uint128"));
}
#[test]
fn test_parse_uint_when_invalid_decimal_should_return_err() {
let result = parse_uint("invalid", 256);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Invalid decimal uint"));
}
#[test]
fn test_parse_uint_when_invalid_hex_should_return_err() {
let result = parse_uint("0xinvalid", 256);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Invalid hex uint"));
}
#[test]
fn test_parse_uint_when_zero_should_return_ok() {
let result = parse_uint("0", 256);
assert!(result.is_ok());
if let DynSolValue::Uint(val, _) = result.unwrap() {
assert_eq!(val, U256::ZERO);
}
}
#[test]
fn test_parse_int_when_positive_decimal_should_return_ok() {
let result = parse_int("1000", 256);
assert!(result.is_ok());
if let DynSolValue::Int(val, bits) = result.unwrap() {
assert_eq!(val, I256::try_from(1000u64).unwrap());
assert_eq!(bits, 256);
}
}
#[test]
fn test_parse_int_when_negative_decimal_should_return_ok() {
let result = parse_int("-1000", 256);
assert!(result.is_ok());
if let DynSolValue::Int(val, _) = result.unwrap() {
assert_eq!(val, I256::ZERO - I256::try_from(1000u64).unwrap());
}
}
#[test]
fn test_parse_int_when_positive_hex_should_return_ok() {
let result = parse_int("0x3e8", 256);
assert!(result.is_ok());
if let DynSolValue::Int(val, _) = result.unwrap() {
assert_eq!(val, I256::try_from(1000u64).unwrap());
}
}
#[test]
fn test_parse_int_when_negative_hex_should_return_ok() {
let result = parse_int("-0x3e8", 256);
assert!(result.is_ok());
}
#[test]
fn test_parse_int_when_invalid_decimal_should_return_err() {
let result = parse_int("invalid", 256);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Invalid decimal int"));
}
#[test]
fn test_parse_int_when_invalid_hex_should_return_err() {
let result = parse_int("0xinvalid", 256);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Invalid hex int"));
}
#[test]
fn test_parse_int_when_zero_should_return_ok() {
let result = parse_int("0", 256);
assert!(result.is_ok());
if let DynSolValue::Int(val, _) = result.unwrap() {
assert_eq!(val, I256::ZERO);
}
}
#[test]
fn test_parse_array_when_json_format_should_return_ok() {
let inner = alloy::dyn_abi::DynSolType::Uint(256);
let result = parse_array("[\"1\", \"2\", \"3\"]", &inner);
assert!(result.is_ok());
if let DynSolValue::Array(arr) = result.unwrap() {
assert_eq!(arr.len(), 3);
}
}
#[test]
fn test_parse_array_when_comma_separated_should_return_ok() {
let inner = alloy::dyn_abi::DynSolType::Uint(256);
let result = parse_array("1,2,3", &inner);
assert!(result.is_ok());
if let DynSolValue::Array(arr) = result.unwrap() {
assert_eq!(arr.len(), 3);
}
}
#[test]
fn test_parse_array_when_empty_should_return_ok() {
let inner = alloy::dyn_abi::DynSolType::Uint(256);
let result = parse_array("[]", &inner);
assert!(result.is_ok());
if let DynSolValue::Array(arr) = result.unwrap() {
assert!(arr.is_empty());
}
}
#[test]
fn test_parse_array_when_invalid_json_should_return_err() {
let inner = alloy::dyn_abi::DynSolType::Uint(256);
let result = parse_array("[invalid json", &inner);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Invalid JSON array"));
}
#[test]
fn test_parse_fixed_array_when_correct_size_should_return_ok() {
let inner = alloy::dyn_abi::DynSolType::Uint(256);
let result = parse_fixed_array("[\"1\", \"2\"]", &inner, 2);
assert!(result.is_ok());
if let DynSolValue::FixedArray(arr) = result.unwrap() {
assert_eq!(arr.len(), 2);
}
}
#[test]
fn test_parse_fixed_array_when_wrong_size_should_return_err() {
let inner = alloy::dyn_abi::DynSolType::Uint(256);
let result = parse_fixed_array("[\"1\", \"2\", \"3\"]", &inner, 2);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Expected 2 elements, got 3"));
}
#[test]
fn test_parse_fixed_array_when_comma_separated_should_return_ok() {
let inner = alloy::dyn_abi::DynSolType::Uint(256);
let result = parse_fixed_array("1,2", &inner, 2);
assert!(result.is_ok());
}
#[test]
fn test_parse_tuple_when_json_format_should_return_ok() {
let types = vec![
alloy::dyn_abi::DynSolType::Address,
alloy::dyn_abi::DynSolType::Uint(256),
];
let result = parse_tuple(
"[\"0x742d35Cc6490C85F2c04D7b0C0E7b7C7A6B22fC3\", \"1000\"]",
&types,
);
assert!(result.is_ok());
if let DynSolValue::Tuple(vals) = result.unwrap() {
assert_eq!(vals.len(), 2);
}
}
#[test]
fn test_parse_tuple_when_parentheses_format_should_return_ok() {
let types = vec![
alloy::dyn_abi::DynSolType::Address,
alloy::dyn_abi::DynSolType::Uint(256),
];
let result = parse_tuple("(0x742d35Cc6490C85F2c04D7b0C0E7b7C7A6B22fC3, 1000)", &types);
assert!(result.is_ok());
}
#[test]
fn test_parse_tuple_when_comma_separated_should_return_ok() {
let types = vec![
alloy::dyn_abi::DynSolType::Address,
alloy::dyn_abi::DynSolType::Uint(256),
];
let result = parse_tuple("0x742d35Cc6490C85F2c04D7b0C0E7b7C7A6B22fC3, 1000", &types);
assert!(result.is_ok());
}
#[test]
fn test_parse_tuple_when_wrong_element_count_should_return_err() {
let types = vec![
alloy::dyn_abi::DynSolType::Address,
alloy::dyn_abi::DynSolType::Uint(256),
];
let result = parse_tuple("0x742d35Cc6490C85F2c04D7b0C0E7b7C7A6B22fC3", &types);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Expected 2 tuple elements, got 1"));
}
#[test]
fn test_parse_param_to_dyn_sol_value_when_address_should_return_ok() {
let result =
parse_param_to_dyn_sol_value("0x742d35Cc6490C85F2c04D7b0C0E7b7C7A6B22fC3", "address");
assert!(result.is_ok());
}
#[test]
fn test_parse_param_to_dyn_sol_value_when_bool_should_return_ok() {
let result = parse_param_to_dyn_sol_value("true", "bool");
assert!(result.is_ok());
}
#[test]
fn test_parse_param_to_dyn_sol_value_when_string_should_return_ok() {
let result = parse_param_to_dyn_sol_value("hello", "string");
assert!(result.is_ok());
if let DynSolValue::String(s) = result.unwrap() {
assert_eq!(s, "hello");
}
}
#[test]
fn test_parse_param_to_dyn_sol_value_when_bytes_should_return_ok() {
let result = parse_param_to_dyn_sol_value("0x1234", "bytes");
assert!(result.is_ok());
}
#[test]
fn test_parse_param_to_dyn_sol_value_when_uint256_should_return_ok() {
let result = parse_param_to_dyn_sol_value("1000", "uint256");
assert!(result.is_ok());
}
#[test]
fn test_parse_param_to_dyn_sol_value_when_int256_should_return_ok() {
let result = parse_param_to_dyn_sol_value("-1000", "int256");
assert!(result.is_ok());
}
#[test]
fn test_parse_param_to_dyn_sol_value_when_bytes32_should_return_ok() {
let result = parse_param_to_dyn_sol_value(
"0x1234567890123456789012345678901234567890123456789012345678901234",
"bytes32",
);
assert!(result.is_ok());
}
#[test]
fn test_parse_param_to_dyn_sol_value_when_array_should_return_ok() {
let result = parse_param_to_dyn_sol_value("[\"1\", \"2\"]", "uint256[]");
assert!(result.is_ok());
}
#[test]
fn test_parse_param_to_dyn_sol_value_when_fixed_array_should_return_ok() {
let result = parse_param_to_dyn_sol_value("[\"1\", \"2\"]", "uint256[2]");
assert!(result.is_ok());
}
#[test]
fn test_parse_param_to_dyn_sol_value_when_tuple_should_return_ok() {
let result = parse_param_to_dyn_sol_value(
"[\"0x742d35Cc6490C85F2c04D7b0C0E7b7C7A6B22fC3\", \"1000\"]",
"(address,uint256)",
);
assert!(result.is_ok());
}
#[test]
fn test_parse_param_to_dyn_sol_value_when_invalid_type_should_return_err() {
let result = parse_param_to_dyn_sol_value("test", "invalid_type");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Invalid type"));
}
#[test]
fn test_parse_param_to_dyn_sol_value_when_unsupported_type_should_return_err() {
let result = parse_param_to_dyn_sol_value("test", "function");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Unsupported type"));
}
#[test]
fn test_decode_contract_result_when_empty_types_should_return_hex() {
let data = vec![0x12, 0x34];
let result = decode_contract_result(&data, &[]);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "0x1234");
}
#[test]
fn test_decode_contract_result_when_bytes_type_should_return_hex() {
let data = vec![0x12, 0x34];
let result = decode_contract_result(&data, &["bytes".to_string()]);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "0x1234");
}
#[test]
fn test_decode_contract_result_when_multiple_types_should_return_json() {
let mut data = vec![0u8; 32]; data.extend_from_slice(&[0u8; 12]); data.extend_from_slice(&[0x11; 20]); data.extend_from_slice(&[0u8; 4]);
let result = decode_contract_result(&data, &["uint256".to_string(), "address".to_string()]);
assert!(result.is_ok());
let result_str = result.unwrap();
assert!(result_str.starts_with('[') && result_str.ends_with(']'));
}
#[test]
fn test_decode_contract_result_when_invalid_type_should_fallback_to_hex() {
let data = vec![0x12, 0x34];
let result = decode_contract_result(&data, &["invalid_type".to_string()]);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "0x1234");
}
#[test]
fn test_decode_contract_result_when_decode_fails_should_fallback_to_hex() {
let data = vec![0x12]; let result = decode_contract_result(&data, &["uint256".to_string()]);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "0x12");
}
#[test]
fn test_decode_single_type_when_uint256_should_return_ok() {
let mut data = vec![0u8; 31];
data.push(42);
let mut offset = 0;
let sol_type = alloy::dyn_abi::DynSolType::Uint(256);
let result = decode_single_type(&data, &mut offset, &sol_type);
assert!(result.is_ok());
assert_eq!(offset, 32);
if let DynSolValue::Uint(val, _) = result.unwrap() {
assert_eq!(val, U256::from(42u64));
}
}
#[test]
fn test_decode_single_type_when_address_should_return_ok() {
let mut data = vec![0u8; 12];
data.extend_from_slice(&[0x42; 20]);
let mut offset = 0;
let sol_type = alloy::dyn_abi::DynSolType::Address;
let result = decode_single_type(&data, &mut offset, &sol_type);
assert!(result.is_ok());
assert_eq!(offset, 32);
}
#[test]
fn test_decode_single_type_when_bool_should_return_ok() {
let mut data = vec![0u8; 31];
data.push(1);
let mut offset = 0;
let sol_type = alloy::dyn_abi::DynSolType::Bool;
let result = decode_single_type(&data, &mut offset, &sol_type);
assert!(result.is_ok());
if let DynSolValue::Bool(val) = result.unwrap() {
assert!(val);
}
}
#[test]
fn test_decode_single_type_when_insufficient_data_should_return_err() {
let data = vec![0u8; 16]; let mut offset = 0;
let sol_type = alloy::dyn_abi::DynSolType::Uint(256);
let result = decode_single_type(&data, &mut offset, &sol_type);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Insufficient data"));
}
#[test]
fn test_decode_single_type_when_complex_type_should_return_err() {
let data = vec![0u8; 32];
let mut offset = 0;
let sol_type = alloy::dyn_abi::DynSolType::String;
let result = decode_single_type(&data, &mut offset, &sol_type);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Complex type decoding not fully implemented"));
}
#[test]
fn test_format_decoded_value_when_address_should_format_hex() {
let addr = Address::from([0x42; 20]);
let val = DynSolValue::Address(addr);
let formatted = format_decoded_value(&val);
assert!(formatted.starts_with("0x"));
assert_eq!(formatted.len(), 42);
}
#[test]
fn test_format_decoded_value_when_uint_should_format_decimal() {
let val = DynSolValue::Uint(U256::from(1000u64), 256);
let formatted = format_decoded_value(&val);
assert_eq!(formatted, "1000");
}
#[test]
fn test_format_decoded_value_when_int_should_format_decimal() {
let val = DynSolValue::Int(I256::try_from(1000u64).unwrap(), 256);
let formatted = format_decoded_value(&val);
assert_eq!(formatted, "1000");
}
#[test]
fn test_format_decoded_value_when_bool_should_format_bool() {
let val = DynSolValue::Bool(true);
let formatted = format_decoded_value(&val);
assert_eq!(formatted, "true");
}
#[test]
fn test_format_decoded_value_when_string_should_return_string() {
let val = DynSolValue::String("hello".to_string());
let formatted = format_decoded_value(&val);
assert_eq!(formatted, "hello");
}
#[test]
fn test_format_decoded_value_when_bytes_should_format_hex() {
let val = DynSolValue::Bytes(vec![0x12, 0x34]);
let formatted = format_decoded_value(&val);
assert_eq!(formatted, "0x1234");
}
#[test]
fn test_format_decoded_value_when_fixed_bytes_should_format_hex() {
let val =
DynSolValue::FixedBytes(alloy::primitives::FixedBytes::from_slice(&[0x12, 0x34]), 2);
let formatted = format_decoded_value(&val);
assert_eq!(formatted, "0x1234");
}
#[test]
fn test_format_decoded_value_when_array_should_format_brackets() {
let val = DynSolValue::Array(vec![
DynSolValue::Uint(U256::from(1u64), 256),
DynSolValue::Uint(U256::from(2u64), 256),
]);
let formatted = format_decoded_value(&val);
assert_eq!(formatted, "[1, 2]");
}
#[test]
fn test_format_decoded_value_when_tuple_should_format_parentheses() {
let val = DynSolValue::Tuple(vec![
DynSolValue::Uint(U256::from(1u64), 256),
DynSolValue::Bool(true),
]);
let formatted = format_decoded_value(&val);
assert_eq!(formatted, "(1, true)");
}
#[test]
fn test_format_decoded_value_when_empty_array_should_format_empty_brackets() {
let val = DynSolValue::Array(vec![]);
let formatted = format_decoded_value(&val);
assert_eq!(formatted, "[]");
}
#[test]
fn test_format_decoded_value_when_empty_tuple_should_format_empty_parentheses() {
let val = DynSolValue::Tuple(vec![]);
let formatted = format_decoded_value(&val);
assert_eq!(formatted, "()");
}
#[tokio::test]
async fn test_wait_for_receipt_when_invalid_hash_should_return_err() {
let client = Box::new("test".to_string()) as Box<dyn std::any::Any + Send + Sync>;
let result = wait_for_receipt(&*client, "invalid_hash").await;
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Invalid transaction hash"));
}
#[tokio::test]
async fn test_wait_for_receipt_when_wrong_length_hash_should_return_err() {
let client = Box::new("test".to_string()) as Box<dyn std::any::Any + Send + Sync>;
let result = wait_for_receipt(&*client, "0x1234").await;
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Transaction hash must be 32 bytes"));
}
#[tokio::test]
async fn test_wait_for_receipt_when_valid_hash_should_return_receipt() {
let client = Box::new("test".to_string()) as Box<dyn std::any::Any + Send + Sync>;
let valid_hash = "0x1234567890123456789012345678901234567890123456789012345678901234";
let result = wait_for_receipt(&*client, valid_hash).await;
assert!(result.is_ok());
let receipt = result.unwrap();
assert!(receipt.success);
}
#[tokio::test]
async fn test_wait_for_receipt_when_no_prefix_should_return_receipt() {
let client = Box::new("test".to_string()) as Box<dyn std::any::Any + Send + Sync>;
let valid_hash = "1234567890123456789012345678901234567890123456789012345678901234";
let result = wait_for_receipt(&*client, valid_hash).await;
assert!(result.is_ok());
}
#[test]
fn test_encode_function_call_with_types_when_hex_address_param_should_encode() {
let result = encode_function_call_with_types(
"0xa9059cbb",
&["0x742d35Cc6490C85F2c04D7b0C0E7b7C7A6B22fC3".to_string()],
);
assert!(result.is_ok());
}
#[test]
fn test_encode_function_call_with_types_when_bytes32_param_should_encode() {
let result = encode_function_call_with_types(
"0xa9059cbb",
&["0x1234567890123456789012345678901234567890123456789012345678901234".to_string()],
);
assert!(result.is_ok());
}
#[test]
fn test_encode_function_call_with_types_when_hex_bytes_param_should_encode() {
let result = encode_function_call_with_types("0xa9059cbb", &["0x1234".to_string()]);
assert!(result.is_ok());
}
#[test]
fn test_encode_function_call_with_types_when_numeric_param_should_encode() {
let result = encode_function_call_with_types("0xa9059cbb", &["1234".to_string()]);
assert!(result.is_ok());
}
#[test]
fn test_encode_function_call_with_types_when_negative_numeric_param_should_encode() {
let result = encode_function_call_with_types("0xa9059cbb", &["-1234".to_string()]);
assert!(result.is_ok());
}
#[test]
fn test_encode_function_call_with_types_when_unsupported_param_should_return_err() {
let result =
encode_function_call_with_types("0xa9059cbb", &["unsupported_format".to_string()]);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Unsupported param format"));
}
#[test]
fn test_encode_function_call_with_types_when_invalid_selector_format_should_return_err() {
let result = encode_function_call_with_types("0xGG", &[]);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Invalid function selector"));
}
#[test]
fn test_parse_uint_when_u256_max_should_return_ok() {
let result = parse_uint(&U256::MAX.to_string(), 256);
assert!(result.is_ok());
}
#[test]
fn test_parse_int_when_negative_hex_with_invalid_abs_should_return_err() {
let result = parse_int("-0xinvalid", 256);
assert!(result.is_err());
}
#[test]
fn test_parse_fixed_bytes_when_empty_hex_should_return_ok() {
let result = parse_fixed_bytes("0x", 0);
assert!(result.is_ok());
}
#[test]
fn test_decode_single_type_when_bool_false_should_return_false() {
let data = vec![0u8; 32];
let mut offset = 0;
let sol_type = alloy::dyn_abi::DynSolType::Bool;
let result = decode_single_type(&data, &mut offset, &sol_type);
assert!(result.is_ok());
if let DynSolValue::Bool(val) = result.unwrap() {
assert!(!val);
}
}
#[test]
fn test_decode_single_type_when_int256_should_return_err() {
let data = vec![0u8; 32];
let mut offset = 0;
let sol_type = alloy::dyn_abi::DynSolType::Int(256);
let result = decode_single_type(&data, &mut offset, &sol_type);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Unsupported type for simple decode"));
}
#[tokio::test]
async fn test_call_contract_read_uses_unified_retry() {
let retry_config = riglr_core::retry::RetryConfig {
max_retries: 3,
base_delay_ms: 100,
max_delay_ms: 2000,
backoff_multiplier: 2.0,
use_jitter: true,
};
assert_eq!(retry_config.max_retries, 3);
assert_eq!(retry_config.base_delay_ms, 100);
assert!(retry_config.use_jitter);
}
#[tokio::test]
async fn test_call_contract_write_uses_unified_retry() {
let retry_config = riglr_core::retry::RetryConfig {
max_retries: 3,
base_delay_ms: 500,
max_delay_ms: 5000,
backoff_multiplier: 2.0,
use_jitter: true,
};
assert_eq!(retry_config.max_retries, 3);
assert_eq!(retry_config.base_delay_ms, 500);
assert_eq!(retry_config.max_delay_ms, 5000);
assert!(retry_config.use_jitter);
}
#[test]
fn test_evm_tool_error_classification() {
use crate::error::EvmToolError;
let rpc_error = EvmToolError::ProviderError("Connection timeout".to_string());
assert!(matches!(rpc_error, EvmToolError::ProviderError(_)));
let nonce_error = EvmToolError::Generic("nonce too low".to_string());
assert!(nonce_error.to_string().contains("nonce"));
let gas_error = EvmToolError::Generic("insufficient gas".to_string());
assert!(gas_error.to_string().contains("gas"));
let network_error = EvmToolError::Generic("network congestion".to_string());
assert!(network_error.to_string().contains("network"));
let invalid_address = EvmToolError::InvalidAddress("bad address".to_string());
assert!(matches!(invalid_address, EvmToolError::InvalidAddress(_)));
let contract_error =
EvmToolError::ContractError("revert: insufficient balance".to_string());
assert!(matches!(contract_error, EvmToolError::ContractError(_)));
}
}