Skip to content

📝 Add docstrings to pb/tuple-abi-encoding #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 85 additions & 35 deletions server/src/http/dyn_contract.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use alloy::hex;
use alloy::json_abi::Param;
use alloy::{
dyn_abi::{DynSolType, DynSolValue, JsonAbiExt},
json_abi::{Function, JsonAbi},
Expand Down Expand Up @@ -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<String, EngineError> {
let trimmed = method.trim();

Expand All @@ -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<Vec<DynSolValue>, 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,
Expand All @@ -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(
Expand Down