explorerd/rpc/contracts.rs
1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2025 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use std::str::FromStr;
20
21use tinyjson::JsonValue;
22
23use darkfi::{
24 rpc::jsonrpc::{parse_json_array_string, validate_empty_params},
25 Result,
26};
27use darkfi_sdk::crypto::ContractId;
28
29use crate::{error::ExplorerdError, Explorerd};
30
31impl Explorerd {
32 // RPCAPI:
33 // Retrieves the native contracts deployed in the DarkFi network.
34 // Returns a JSON array containing Contract IDs along with their associated metadata upon success.
35 //
36 // **Params:**
37 // * `None`
38 //
39 // **Returns:**
40 // * Array of `ContractRecord`s encoded into a JSON.
41 //
42 // **Example API Usage:**
43 // --> {"jsonrpc": "2.0", "method": "contracts.get_native_contracts", "params": ["5cc...2f9"], "id": 1}
44 // <-- {"jsonrpc": "2.0", "result": ["BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o", "Money Contract", "The money contract..."], "id": 1}
45 pub async fn contracts_get_native_contracts(&self, params: &JsonValue) -> Result<JsonValue> {
46 // Validate that no parameters are provided
47 validate_empty_params(params)?;
48
49 // Retrieve native contracts
50 let contract_records = self.service.get_native_contracts()?;
51
52 // Transform contract records into a JSON array and return result
53 if contract_records.is_empty() {
54 Ok(JsonValue::Array(vec![]))
55 } else {
56 let json_blocks: Vec<JsonValue> = contract_records
57 .into_iter()
58 .map(|contract_record| contract_record.to_json_array())
59 .collect();
60 Ok(JsonValue::Array(json_blocks))
61 }
62 }
63
64 // RPCAPI:
65 // Retrieves the source code paths for the contract associated with the specified Contract ID.
66 // Returns a JSON array containing the source code paths upon success.
67 //
68 // **Params:**
69 // * `array[0]`: `String` Contract ID
70 //
71 // **Returns:**
72 // * `JsonArray` containing source code paths for the specified Contract ID.
73 //
74 // **Example API Usage:**
75 // --> {"jsonrpc": "2.0", "method": "contracts.get_contract_source_code_paths", "params": ["BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o"], "id": 1}
76 // <-- {"jsonrpc": "2.0", "result": ["path/to/source1.rs", "path/to/source2.rs"], "id": 1}
77 pub async fn contracts_get_contract_source_code_paths(
78 &self,
79 params: &JsonValue,
80 ) -> Result<JsonValue> {
81 // Extract contract ID
82 let contact_id_str = parse_json_array_string("contract_id", 0, params)?;
83
84 // Convert the contract string to a `ContractId` instance
85 let contract_id = ContractId::from_str(&contact_id_str)
86 .map_err(|_| ExplorerdError::InvalidContractId(contact_id_str))?;
87
88 // Retrieve source code paths for the contract
89 let paths = self.service.get_contract_source_paths(&contract_id)?;
90
91 // Tranform found paths into `JsonValues`
92 let json_value_paths = paths.iter().map(|path| JsonValue::String(path.clone())).collect();
93
94 Ok(JsonValue::Array(json_value_paths))
95 }
96
97 // RPCAPI:
98 // Retrieves contract source code content using the provided Contract ID and source path.
99 // Returns the source code content as a JSON string upon success.
100 //
101 // **Params:**
102 // * `array[0]`: `String` Contract ID
103 // * `array[1]`: `String` Source path
104 //
105 // **Returns:**
106 // * `String` containing the content of the contract source file.
107 //
108 // **Example API Usage:**
109 // --> {"jsonrpc": "2.0", "method": "contracts.get_contract_source", "params": ["BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o", "client/lib.rs"], "id": 1}
110 // <-- {"jsonrpc": "2.0", "result": "/* This file is ...", "id": 1}
111 pub async fn contracts_get_contract_source(&self, params: &JsonValue) -> Result<JsonValue> {
112 // Extract the contract ID
113 let contact_id_str = parse_json_array_string("contract_id", 0, params)?;
114
115 // Convert the contract string to a `ContractId` instance
116 let contract_id = ContractId::from_str(&contact_id_str)
117 .map_err(|_| ExplorerdError::InvalidContractId(contact_id_str))?;
118
119 // Extract the source path
120 let source_path = parse_json_array_string("source_path", 1, params)?;
121
122 // Retrieve the contract source code, transform it into a `JsonValue`, and return the result
123 match self.service.get_contract_source_content(&contract_id, &source_path)? {
124 Some(source_file) => Ok(JsonValue::String(source_file)),
125 None => Ok(JsonValue::from(std::collections::HashMap::<String, JsonValue>::new())),
126 }
127 }
128}
129
130#[cfg(test)]
131/// Test module for validating the functionality of RPC methods related to explorer contracts.
132/// Focuses on ensuring proper error handling for invalid parameters across several use cases,
133/// including cases with missing values, unsupported types, and unparsable inputs.
134mod tests {
135
136 use tinyjson::JsonValue;
137
138 use darkfi::rpc::jsonrpc::ErrorCode;
139
140 use crate::test_utils::{
141 setup, validate_empty_rpc_parameters, validate_invalid_rpc_contract_id,
142 validate_invalid_rpc_parameter,
143 };
144
145 #[test]
146 /// Tests the `contracts.get_native_contracts` method to ensure it correctly handles cases where
147 /// empty parameters are supplied, returning an expected result or error response.
148 fn test_contracts_get_native_contracts_empty_params() {
149 smol::block_on(async {
150 validate_empty_rpc_parameters(&setup(), "contracts.get_native_contracts").await;
151 });
152 }
153
154 #[test]
155 /// Tests the `contracts.get_contract_source_code_paths` method to ensure it correctly handles cases
156 /// with invalid or missing `contract_id` parameters, returning appropriate error responses.
157 fn test_contracts_get_contract_source_code_paths_invalid_params() {
158 validate_invalid_rpc_contract_id(&setup(), "contracts.get_contract_source_code_paths");
159 }
160
161 #[test]
162 /// Tests the `contracts.get_contract_source` method to ensure it correctly handles cases
163 /// with invalid or missing parameters, returning appropriate error responses.
164 fn test_contracts_get_contract_source_invalid_params() {
165 let test_method = "contracts.get_contract_source";
166 let parameter_name = "source_path";
167
168 smol::block_on(async {
169 // Set up the explorerd instance
170 let explorerd = setup();
171
172 validate_invalid_rpc_contract_id(&explorerd, test_method);
173
174 // Test for missing `source_path` parameter
175 validate_invalid_rpc_parameter(
176 &explorerd,
177 test_method,
178 &[JsonValue::String("BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o".to_string())],
179 ErrorCode::InvalidParams.code(),
180 &format!("Parameter '{parameter_name}' at index 1 is missing"),
181 )
182 .await;
183
184 // Test for invalid `source_path` parameter
185 validate_invalid_rpc_parameter(
186 &explorerd,
187 test_method,
188 &[
189 JsonValue::String("BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o".to_string()),
190 JsonValue::Number(123.0), // Invalid `source_path` type
191 ],
192 ErrorCode::InvalidParams.code(),
193 &format!("Parameter '{parameter_name}' is not a valid string"),
194 )
195 .await;
196 });
197 }
198}