1#[cfg(target_arch = "wasm32")]
2use crate::types::identifier::block_identifier::BlockIdentifier;
3#[cfg(target_arch = "wasm32")]
4use crate::{rpcs::query_global_state::QueryGlobalStateResult, types::path::Path};
5use crate::{
6 rpcs::query_global_state::{KeyIdentifierInput, PathIdentifierInput, QueryGlobalStateParams},
7 types::{
8 identifier::{block_identifier::BlockIdentifierInput, entity_identifier::EntityIdentifier},
9 sdk_error::SdkError,
10 verbosity::Verbosity,
11 },
12 SDK,
13};
14use casper_client::{
15 rpcs::results::QueryGlobalStateResult as _QueryGlobalStateResult, SuccessResponse,
16};
17#[cfg(target_arch = "wasm32")]
18use gloo_utils::format::JsValueSerdeExt;
19#[cfg(target_arch = "wasm32")]
20use serde::{Deserialize, Serialize};
21#[cfg(target_arch = "wasm32")]
22use wasm_bindgen::prelude::*;
23
24#[derive(Deserialize, Default, Serialize)]
25#[cfg(target_arch = "wasm32")]
26#[wasm_bindgen(js_name = "queryContractKeyOptions", getter_with_clone)]
27pub struct QueryContractKeyOptions {
28 pub entity_identifier: Option<EntityIdentifier>,
29 pub entity_identifier_as_string: Option<String>,
30 pub maybe_block_identifier: Option<BlockIdentifier>,
31 pub maybe_block_id_as_string: Option<String>,
32 pub path_as_string: Option<String>,
33 pub path: Option<Path>,
34 pub rpc_address: Option<String>,
35 pub verbosity: Option<Verbosity>,
36}
37
38#[cfg(target_arch = "wasm32")]
39#[wasm_bindgen]
40impl SDK {
41 #[wasm_bindgen(js_name = "query_contract_key_options")]
43 pub fn query_contract_key_state_options(
44 &self,
45 options: JsValue,
46 ) -> Result<QueryContractKeyOptions, JsError> {
47 options
48 .into_serde::<QueryContractKeyOptions>()
49 .map_err(|err| JsError::new(&format!("Error deserializing options: {:?}", err)))
50 }
51
52 #[wasm_bindgen(js_name = "query_contract_key")]
54 pub async fn query_contract_key_js_alias(
55 &self,
56 options: Option<QueryContractKeyOptions>,
57 ) -> Result<QueryGlobalStateResult, JsError> {
58 let options = options.unwrap_or_default();
59
60 let path_input = match (options.path, options.path_as_string) {
62 (Some(path), None) => PathIdentifierInput::Path(path),
63 (None, Some(path_string)) => PathIdentifierInput::String(path_string),
64 (Some(_), Some(_)) => {
65 let err = "Only one of `path` or `path_as_string` can be provided".to_string();
66 return Err(JsError::new(&err));
67 }
68 (None, None) => {
69 let err = "Either `path` or `path_as_string` must be provided".to_string();
70 return Err(JsError::new(&err));
71 }
72 };
73
74 let maybe_block_identifier =
75 if let Some(maybe_block_identifier) = options.maybe_block_identifier {
76 Some(BlockIdentifierInput::BlockIdentifier(
77 maybe_block_identifier,
78 ))
79 } else {
80 options
81 .maybe_block_id_as_string
82 .map(BlockIdentifierInput::String)
83 };
84
85 let result = self
86 .query_contract_key(
87 options.entity_identifier,
88 options.entity_identifier_as_string,
89 path_input,
90 maybe_block_identifier,
91 options.verbosity,
92 options.rpc_address,
93 )
94 .await;
95 match result {
96 Ok(data) => Ok(data.result.into()),
97 Err(err) => {
98 let err = &format!("Error occurred with {:?}", err);
99 Err(JsError::new(err))
100 }
101 }
102 }
103}
104
105impl SDK {
107 pub async fn query_contract_key(
117 &self,
118 entity_identifier: Option<EntityIdentifier>,
119 entity_identifier_as_string: Option<String>,
120 path: PathIdentifierInput,
121 maybe_block_identifier: Option<BlockIdentifierInput>,
122 verbosity: Option<Verbosity>,
123 rpc_address: Option<String>,
124 ) -> Result<SuccessResponse<_QueryGlobalStateResult>, SdkError> {
125 match path {
126 PathIdentifierInput::Path(ref path_struct) => {
127 if path_struct.is_empty() {
128 return Err(SdkError::InvalidArgument {
129 context: "Path",
130 error: "Path is empty".to_string(),
131 });
132 }
133 path_struct.to_string()
134 }
135 PathIdentifierInput::String(ref path_string) => {
136 if path_string.is_empty() {
137 return Err(SdkError::InvalidArgument {
138 context: "Path string",
139 error: "Path string is empty".to_string(),
140 });
141 }
142 path_string.clone()
143 }
144 };
145
146 let entity = self
148 .get_entity(
149 entity_identifier.clone(),
150 entity_identifier_as_string.clone(),
151 maybe_block_identifier.clone(),
152 verbosity,
153 rpc_address.clone(),
154 )
155 .await;
156
157 let key_as_string = entity_identifier_as_string
158 .or_else(|| entity_identifier.as_ref().map(ToString::to_string))
159 .unwrap_or_default();
160
161 let maybe_block_id: Option<String> = match maybe_block_identifier.clone() {
162 Some(block_identifier) => match block_identifier {
163 BlockIdentifierInput::BlockIdentifier(_) => {
164 maybe_block_identifier.map(|b| b.to_string())
165 }
166 BlockIdentifierInput::String(block_identifier_as_string) => {
167 Some(block_identifier_as_string.clone())
168 }
169 },
170 None => None,
171 };
172
173 match entity {
174 Ok(_) => {
176 let key = KeyIdentifierInput::String(key_as_string);
177 self.query_global_state(QueryGlobalStateParams {
178 key,
179 path: Some(path),
180 maybe_global_state_identifier: None,
181 state_root_hash: None,
182 maybe_block_id,
183 verbosity,
184 rpc_address,
185 })
186 .await
187 }
188 Err(_) => {
189 let key_as_string = key_as_string.replace("entity-contract", "hash");
191 let key = KeyIdentifierInput::String(key_as_string);
192 self.query_global_state(QueryGlobalStateParams {
193 key,
194 path: Some(path),
195 maybe_global_state_identifier: None,
196 state_root_hash: None,
197 maybe_block_id,
198 verbosity,
199 rpc_address,
200 })
201 .await
202 }
203 }
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use crate::{
211 install_cep78,
212 types::{
213 identifier::{block_identifier::BlockIdentifier, entity_identifier::EntityIdentifier},
214 verbosity::Verbosity,
215 },
216 };
217 use sdk_tests::tests::helpers::{
218 get_block, get_enable_addressable_entity, get_network_constants,
219 };
220 use tokio;
221
222 async fn get_entity_input() -> EntityIdentifier {
223 EntityIdentifier::from_formatted_str(&install_cep78().await).unwrap()
224 }
225
226 #[tokio::test]
227 async fn test_query_contract_key_with_none_values() {
228 let sdk = SDK::new(None, None, None);
230 let error_message = "Invalid argument 'Path string': Path string is empty";
231
232 let path = PathIdentifierInput::String("".to_string());
233
234 let result = sdk
236 .query_contract_key(Some(get_entity_input().await), None, path, None, None, None)
237 .await;
238
239 assert!(result.is_err());
241 let err_string = result.err().unwrap().to_string();
242 assert!(err_string.contains(error_message));
243 }
244
245 #[tokio::test]
246 async fn test_query_contract_key_with_missing_key() {
247 let sdk = SDK::new(None, None, None);
249 let error_message = if get_enable_addressable_entity() {
250 "Invalid argument 'get_entity': Error: Missing entity identifier"
251 } else {
252 "Invalid argument 'query_global_state': Error: Missing key from formatted string"
253 };
254
255 let path = PathIdentifierInput::String("installer".to_string());
256
257 let result = sdk
259 .query_contract_key(None, None, path, None, None, None)
260 .await;
261
262 assert!(result.is_err());
264 let err_string = result.err().unwrap().to_string();
265 assert!(err_string.contains(error_message));
266 }
267
268 #[tokio::test]
269 async fn test_query_contract_key_with_entity_identifier_as_string() {
270 let sdk = SDK::new(None, None, None);
272 let verbosity = Some(Verbosity::High);
273 let (rpc_address, _, _, _, _) = get_network_constants();
274
275 let entity = get_entity_input().await;
276
277 let path = PathIdentifierInput::String("installer".to_string());
278
279 let (_, block_height) = get_block(&rpc_address.clone()).await;
280 let block_identifier =
281 BlockIdentifierInput::BlockIdentifier(BlockIdentifier::from_height(block_height));
282
283 let result = sdk
285 .query_contract_key(
286 Some(entity),
287 None,
288 path,
289 Some(block_identifier),
290 verbosity,
291 Some(rpc_address),
292 )
293 .await;
294
295 assert!(result.is_ok());
297 }
298
299 #[tokio::test]
300 async fn test_query_contract_key_with_global_state_identifier() {
301 let sdk = SDK::new(None, None, None);
303 let verbosity = Some(Verbosity::High);
304 let (rpc_address, _, _, _, _) = get_network_constants();
305
306 let entity = get_entity_input().await;
307
308 let path = PathIdentifierInput::String("installer".to_string());
309
310 let (_, block_height) = get_block(&rpc_address.clone()).await;
311 let block_identifier =
312 BlockIdentifierInput::BlockIdentifier(BlockIdentifier::from_height(block_height));
313 let result = sdk
315 .query_contract_key(
316 None,
317 Some(entity.to_string()),
318 path,
319 Some(block_identifier),
320 verbosity,
321 Some(rpc_address),
322 )
323 .await;
324
325 assert!(result.is_ok());
327 }
328
329 #[tokio::test]
330 async fn test_query_contract_key_with_missing_path() {
331 let sdk = SDK::new(None, None, None);
333 let verbosity = Some(Verbosity::High);
334 let (rpc_address, _, _, _, _) = get_network_constants();
335
336 let entity = get_entity_input().await;
337 let error_message = "Invalid argument 'Path': Path is empty";
338
339 let vec_of_strings = vec!["".to_string()]; let path = PathIdentifierInput::Path(vec_of_strings.into());
341
342 let result = sdk
344 .query_contract_key(Some(entity), None, path, None, verbosity, Some(rpc_address))
345 .await;
346
347 assert!(result.is_err());
349 let err_string = result.err().unwrap().to_string();
350 assert!(err_string.contains(error_message));
351 }
352
353 #[tokio::test]
354 async fn test_query_contract_key_with_missing_path_as_string() {
355 let sdk = SDK::new(None, None, None);
357 let verbosity = Some(Verbosity::High);
358 let (rpc_address, _, _, _, _) = get_network_constants();
359
360 let entity = get_entity_input().await;
361 let error_message = "Invalid argument 'Path string': Path string is empty";
362
363 let path = PathIdentifierInput::String("".to_string()); let result = sdk
367 .query_contract_key(Some(entity), None, path, None, verbosity, Some(rpc_address))
368 .await;
369
370 assert!(result.is_err());
372 let err_string = result.err().unwrap().to_string();
373 assert!(err_string.contains(error_message));
374 }
375
376 #[tokio::test]
377 async fn test_query_contract_key_with_path_as_string() {
378 let sdk = SDK::new(None, None, None);
380 let verbosity = Some(Verbosity::High);
381 let (rpc_address, _, _, _, _) = get_network_constants();
382
383 let entity = get_entity_input().await;
384
385 let vec_of_strings = vec!["installer".to_string()];
386 let path = PathIdentifierInput::Path(vec_of_strings.into());
387
388 let result = sdk
390 .query_contract_key(Some(entity), None, path, None, verbosity, Some(rpc_address))
391 .await;
392
393 assert!(result.is_ok());
395 }
396
397 #[tokio::test]
398 async fn test_query_contract_key_with_error() {
399 let sdk = SDK::new(Some("http://localhost".to_string()), None, None);
400 let block_identifier =
401 BlockIdentifierInput::BlockIdentifier(BlockIdentifier::from_height(1));
402 let error_message = "error sending request for url (http://localhost/rpc)";
403 let entity = get_entity_input().await;
404
405 let path = PathIdentifierInput::String("installer".to_string());
406
407 let result = sdk
409 .query_contract_key(Some(entity), None, path, Some(block_identifier), None, None)
410 .await;
411 assert!(result.is_err());
413 let err_string = result.err().unwrap().to_string();
414 assert!(err_string.contains(error_message));
415 }
416}