diff --git a/server/src/http/dyn_contract.rs b/server/src/http/dyn_contract.rs index 3114982..0719af5 100644 --- a/server/src/http/dyn_contract.rs +++ b/server/src/http/dyn_contract.rs @@ -1,4 +1,5 @@ use alloy::hex; +use alloy::json_abi::Param; use alloy::{ dyn_abi::{DynSolType, DynSolValue, JsonAbiExt}, json_abi::{Function, JsonAbi}, @@ -194,7 +195,9 @@ impl ContractCall { Ok(abi) } - /// Extracts function name from method string + /// Extracts the function name from a method string, handling optional "function" prefix and parameter lists. + /// + /// Returns the function name as a string, or an error if the format is invalid. fn extract_function_name(&self, method: &str) -> Result { let trimmed = method.trim(); @@ -213,7 +216,84 @@ impl ContractCall { }) } - /// Encodes parameters using serde to directly deserialize into DynSolValue + /// Converts a slice of JSON values into dynamic Solidity values according to ABI parameter definitions. + /// + /// Validates parameter count, recursively parses complex types (such as tuples), and coerces each JSON value into a `DynSolValue` matching the expected Solidity type. Returns an error if parameter counts do not match, if a value cannot be coerced, or if a type mismatch is detected. + /// + /// # Arguments + /// + /// * `json_values` - The JSON values representing function arguments. + /// * `json_abi_params` - The ABI parameter definitions describing expected Solidity types. + /// + /// # Returns + /// + /// A vector of `DynSolValue` instances corresponding to the parsed and validated parameters, or an error message if parsing fails. + /// + /// # Examples + /// + /// ``` + /// use serde_json::json; + /// use alloy_sol_types::{DynSolValue, Param}; + /// + /// let json_values = vec![json!("0x1234..."), json!(42)]; + /// let abi_params = vec![Param { name: "to".into(), ty: "address".into(), components: vec![] }, Param { name: "amount".into(), ty: "uint256".into(), components: vec![] }]; + /// let result = ContractCall::json_to_sol(&json_values, &abi_params); + /// assert!(result.is_ok()); + /// ``` + fn json_to_sol( + json_values: &[JsonValue], + json_abi_params: &[Param], + ) -> Result, String> { + if json_values.len() != json_abi_params.len() { + return Err(format!( + "Parameter count mismatch: expected {}, got {}", + json_abi_params.len(), + json_values.len() + )); + } + + let mut parsed_params = Vec::new(); + + for (json_value, json_abi_param) in json_values.iter().zip(json_abi_params.iter()) { + if json_abi_param.is_complex_type() { + let json_value = json_value + .as_array() + .ok_or_else(|| "Expected array for complex type".to_string())?; + + let dyn_sol_value = Self::json_to_sol(json_value, &json_abi_param.components)?; + + parsed_params.push(DynSolValue::Tuple(dyn_sol_value)); + } else { + let sol_type: DynSolType = json_abi_param + .ty + .parse() + .map_err(|e| format!("Invalid Solidity type '{}': {}", json_abi_param.ty, e))?; + + let parsed_value: DynSolValue = sol_type + .coerce_json(json_value) + .map_err(|e| format!("Failed to parse parameter as DynSolValue: {}", e))?; + + if !parsed_value.matches(&sol_type) { + return Err(format!( + "Parameter type mismatch: expected {}, got {:?}", + json_abi_param.ty, + parsed_value.as_type() + )); + } + + parsed_params.push(parsed_value); + } + } + + Ok(parsed_params) + } + + /// Encodes the contract call parameters for a given function using the contract's JSON parameters and ABI. + /// + /// Validates parameter count, converts JSON values to Solidity types, and ABI-encodes the input data for transaction submission. + /// + /// # Returns + /// Encoded function call data as a byte vector on success, or an `EngineError` if parameter validation or encoding fails. pub fn encode_parameters( &self, function: &Function, @@ -231,39 +311,9 @@ impl ContractCall { )); } - let mut parsed_params = Vec::new(); - - for (param, input) in self.params.iter().zip(function.inputs.iter()) { - let sol_type: DynSolType = input.ty.parse().map_err(|e| { - EngineError::contract_preparation_error( - Some(self.contract_address), - chain_id, - format!("Invalid Solidity type '{}': {}", input.ty, e), - ) - })?; - - let parsed_value: DynSolValue = sol_type.coerce_json(param).map_err(|e| { - EngineError::contract_preparation_error( - Some(self.contract_address), - chain_id, - format!("Failed to parse parameter as DynSolValue: {}", e), - ) - })?; - - if !parsed_value.matches(&sol_type) { - return Err(EngineError::contract_preparation_error( - Some(self.contract_address), - chain_id, - format!( - "Parameter type mismatch: expected {}, got {:?}", - input.ty, - parsed_value.as_type() - ), - )); - } - - parsed_params.push(parsed_value); - } + let parsed_params = Self::json_to_sol(&self.params, &function.inputs).map_err(|e| { + EngineError::contract_preparation_error(Some(self.contract_address), chain_id, e) + })?; function.abi_encode_input(&parsed_params).map_err(|e| { EngineError::contract_preparation_error(