/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2026 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
use std::str::FromStr;
use tinyjson::JsonValue;
use darkfi::{
rpc::jsonrpc::{parse_json_array_string, validate_empty_params},
Result,
};
use darkfi_sdk::crypto::ContractId;
use crate::{error::ExplorerdError, Explorerd};
impl Explorerd {
// RPCAPI:
// Retrieves the native contracts deployed in the DarkFi network.
// Returns a JSON array containing Contract IDs along with their associated metadata upon success.
//
// **Params:**
// * `None`
//
// **Returns:**
// * Array of `ContractRecord`s encoded into a JSON.
//
// **Example API Usage:**
// --> {"jsonrpc": "2.0", "method": "contracts.get_native_contracts", "params": ["5cc...2f9"], "id": 1}
// <-- {"jsonrpc": "2.0", "result": ["BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o", "Money Contract", "The money contract..."], "id": 1}
pub async fn contracts_get_native_contracts(&self, params: &JsonValue) -> Result {
// Validate that no parameters are provided
validate_empty_params(params)?;
// Retrieve native contracts
let contract_records = self.service.get_native_contracts()?;
// Transform contract records into a JSON array and return result
if contract_records.is_empty() {
Ok(JsonValue::Array(vec![]))
} else {
let json_blocks: Vec = contract_records
.into_iter()
.map(|contract_record| contract_record.to_json_array())
.collect();
Ok(JsonValue::Array(json_blocks))
}
}
// RPCAPI:
// Retrieves the source code paths for the contract associated with the specified Contract ID.
// Returns a JSON array containing the source code paths upon success.
//
// **Params:**
// * `array[0]`: `String` Contract ID
//
// **Returns:**
// * `JsonArray` containing source code paths for the specified Contract ID.
//
// **Example API Usage:**
// --> {"jsonrpc": "2.0", "method": "contracts.get_contract_source_code_paths", "params": ["BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o"], "id": 1}
// <-- {"jsonrpc": "2.0", "result": ["path/to/source1.rs", "path/to/source2.rs"], "id": 1}
pub async fn contracts_get_contract_source_code_paths(
&self,
params: &JsonValue,
) -> Result {
// Extract contract ID
let contact_id_str = parse_json_array_string("contract_id", 0, params)?;
// Convert the contract string to a `ContractId` instance
let contract_id = ContractId::from_str(&contact_id_str)
.map_err(|_| ExplorerdError::InvalidContractId(contact_id_str))?;
// Retrieve source code paths for the contract
let paths = self.service.get_contract_source_paths(&contract_id)?;
// Tranform found paths into `JsonValues`
let json_value_paths = paths.iter().map(|path| JsonValue::String(path.clone())).collect();
Ok(JsonValue::Array(json_value_paths))
}
// RPCAPI:
// Retrieves contract source code content using the provided Contract ID and source path.
// Returns the source code content as a JSON string upon success.
//
// **Params:**
// * `array[0]`: `String` Contract ID
// * `array[1]`: `String` Source path
//
// **Returns:**
// * `String` containing the content of the contract source file.
//
// **Example API Usage:**
// --> {"jsonrpc": "2.0", "method": "contracts.get_contract_source", "params": ["BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o", "client/lib.rs"], "id": 1}
// <-- {"jsonrpc": "2.0", "result": "/* This file is ...", "id": 1}
pub async fn contracts_get_contract_source(&self, params: &JsonValue) -> Result {
// Extract the contract ID
let contact_id_str = parse_json_array_string("contract_id", 0, params)?;
// Convert the contract string to a `ContractId` instance
let contract_id = ContractId::from_str(&contact_id_str)
.map_err(|_| ExplorerdError::InvalidContractId(contact_id_str))?;
// Extract the source path
let source_path = parse_json_array_string("source_path", 1, params)?;
// Retrieve the contract source code, transform it into a `JsonValue`, and return the result
match self.service.get_contract_source_content(&contract_id, &source_path)? {
Some(source_file) => Ok(JsonValue::String(source_file)),
None => Ok(JsonValue::from(std::collections::HashMap::::new())),
}
}
}
#[cfg(test)]
/// Test module for validating the functionality of RPC methods related to explorer contracts.
/// Focuses on ensuring proper error handling for invalid parameters across several use cases,
/// including cases with missing values, unsupported types, and unparsable inputs.
mod tests {
use tinyjson::JsonValue;
use darkfi::rpc::jsonrpc::ErrorCode;
use crate::test_utils::{
setup, validate_empty_rpc_parameters, validate_invalid_rpc_contract_id,
validate_invalid_rpc_parameter,
};
#[test]
/// Tests the `contracts.get_native_contracts` method to ensure it correctly handles cases where
/// empty parameters are supplied, returning an expected result or error response.
fn test_contracts_get_native_contracts_empty_params() {
smol::block_on(async {
validate_empty_rpc_parameters(&setup(), "contracts.get_native_contracts").await;
});
}
#[test]
/// Tests the `contracts.get_contract_source_code_paths` method to ensure it correctly handles cases
/// with invalid or missing `contract_id` parameters, returning appropriate error responses.
fn test_contracts_get_contract_source_code_paths_invalid_params() {
validate_invalid_rpc_contract_id(&setup(), "contracts.get_contract_source_code_paths");
}
#[test]
/// Tests the `contracts.get_contract_source` method to ensure it correctly handles cases
/// with invalid or missing parameters, returning appropriate error responses.
fn test_contracts_get_contract_source_invalid_params() {
let test_method = "contracts.get_contract_source";
let parameter_name = "source_path";
smol::block_on(async {
// Set up the explorerd instance
let explorerd = setup();
validate_invalid_rpc_contract_id(&explorerd, test_method);
// Test for missing `source_path` parameter
validate_invalid_rpc_parameter(
&explorerd,
test_method,
&[JsonValue::String("BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o".to_string())],
ErrorCode::InvalidParams.code(),
&format!("Parameter '{parameter_name}' at index 1 is missing"),
)
.await;
// Test for invalid `source_path` parameter
validate_invalid_rpc_parameter(
&explorerd,
test_method,
&[
JsonValue::String("BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o".to_string()),
JsonValue::Number(123.0), // Invalid `source_path` type
],
ErrorCode::InvalidParams.code(),
&format!("Parameter '{parameter_name}' is not a valid string"),
)
.await;
});
}
}