1use crate::types::hash::account_hash::AccountHash;
2use crate::types::transaction_params::transaction_str_params::{DEFAULT_GAS_PRICE, DEFAULT_TTL};
3use crate::types::{key::Key, public_key::PublicKey, sdk_error::SdkError, verbosity::Verbosity};
4use base64::engine::general_purpose;
5use base64::Engine;
6use bigdecimal::BigDecimal;
7use blake2::{
8 digest::{Update, VariableOutput},
9 Blake2bVar,
10};
11use casper_client::cli::JsonArg;
12use casper_types::{
13 bytesrepr::ToBytes, cl_value_to_json as cl_value_to_json_from_casper_types, CLValue,
14 Key as CasperTypesKey, NamedArg, PublicKey as CasperTypesPublicKey, RuntimeArgs, SecretKey,
15 TimeDiff, Timestamp,
16};
17use chrono::{DateTime, SecondsFormat, Utc};
18#[cfg(all(feature = "js", target_arch = "wasm32"))]
19use gloo_utils::format::JsValueSerdeExt;
20use serde::Serialize;
21use serde_json::Value;
22use std::str::FromStr;
23#[cfg(all(feature = "js", target_arch = "wasm32"))]
24use wasm_bindgen::{JsCast, JsValue};
25
26pub const BLAKE2B_DIGEST_LENGTH: usize = 32;
27
28pub fn cl_value_to_json(cl_value: &CLValue) -> Option<Value> {
38 cl_value_to_json_from_casper_types(cl_value)
39}
40
41pub fn get_current_timestamp(timestamp: Option<String>) -> String {
51 let parsed_timestamp = timestamp.as_ref().and_then(|ts| ts.parse::<i64>().ok());
52 let current_timestamp = parsed_timestamp
53 .map(|parsed_time| DateTime::from_timestamp(parsed_time / 1000, 0).unwrap_or_else(Utc::now))
54 .unwrap_or_else(Utc::now);
55 current_timestamp.to_rfc3339_opts(SecondsFormat::Secs, true)
56}
57
58pub fn get_blake2b_hash(meta_data: &str) -> String {
68 let mut result = [0; BLAKE2B_DIGEST_LENGTH];
69 let mut hasher = Blake2bVar::new(BLAKE2B_DIGEST_LENGTH).expect("should create hasher");
70
71 hasher.update(meta_data.as_bytes());
72 hasher
73 .finalize_variable(&mut result)
74 .expect("finalize failed");
75 hex::encode(result).to_lowercase()
76}
77
78pub fn make_dictionary_item_key<V: ToBytes>(key: &Key, value: &V) -> String {
113 let key: CasperTypesKey = CasperTypesKey::from(key.clone());
114 let mut bytes_a = key.to_bytes().unwrap_or_default();
115 let mut bytes_b = value.to_bytes().unwrap_or_default();
116
117 bytes_a.append(&mut bytes_b);
118
119 let mut result = [0; BLAKE2B_DIGEST_LENGTH];
120 let mut hasher = Blake2bVar::new(BLAKE2B_DIGEST_LENGTH).expect("should create hasher");
121
122 hasher.update(&bytes_a);
123 hasher
124 .finalize_variable(&mut result)
125 .expect("finalize failed");
126 hex::encode(result)
127}
128
129pub fn get_base64_key_from_account_hash(account_hash: &str) -> Result<String, Box<SdkError>> {
142 let account_hash = AccountHash::from_formatted_str(account_hash)?;
143 let key = Key::from_account(account_hash).to_bytes().unwrap();
144 Ok(general_purpose::STANDARD.encode(key)) }
146
147pub fn get_base64_key_from_key_hash(formatted_hash: &str) -> Result<String, Box<SdkError>> {
165 let key = Key::from_formatted_str(formatted_hash)?;
166 let key = key.to_bytes().unwrap();
167 Ok(general_purpose::STANDARD.encode(key)) }
169
170pub const ENTITY_CONTRACT_PREFIX: &str = "entity-contract-";
172pub const HASH_PREFIX: &str = "hash-";
174
175pub fn contract_hash_key_for_global_state(formatted: &str) -> String {
187 if let Some(hex) = formatted.strip_prefix(ENTITY_CONTRACT_PREFIX) {
188 format!("{HASH_PREFIX}{hex}")
189 } else if formatted.starts_with(HASH_PREFIX) {
190 formatted.to_string()
191 } else if formatted.contains('-') {
192 formatted.to_string()
194 } else {
195 format!("{HASH_PREFIX}{formatted}")
196 }
197}
198
199pub fn strip_contract_key_prefix(formatted: &str) -> &str {
201 formatted
202 .strip_prefix(HASH_PREFIX)
203 .or_else(|| formatted.strip_prefix(ENTITY_CONTRACT_PREFIX))
204 .unwrap_or(formatted)
205}
206
207pub fn get_ttl_or_default(ttl: Option<&str>) -> String {
217 if let Some(ttl) = ttl {
218 ttl.to_string()
219 } else {
220 DEFAULT_TTL.to_string()
221 }
222}
223
224pub fn parse_timestamp(value: &str) -> Result<Timestamp, Box<SdkError>> {
234 Timestamp::from_str(value).map_err(|error| {
235 Box::new(SdkError::FailedToParseTimestamp {
236 context: "timestamp",
237 error,
238 })
239 })
240}
241
242pub fn parse_ttl(value: &str) -> Result<TimeDiff, Box<SdkError>> {
252 TimeDiff::from_str(value).map_err(|error| {
253 Box::new(SdkError::FailedToParseTimeDiff {
254 context: "ttl",
255 error,
256 })
257 })
258}
259
260pub fn get_gas_price_or_default(gas_price: Option<u64>) -> u64 {
270 gas_price.unwrap_or(DEFAULT_GAS_PRICE)
271}
272
273pub(crate) fn get_str_or_default(opt_str: Option<&String>) -> &str {
283 opt_str.map(String::as_str).unwrap_or_default()
284}
285
286pub fn secret_key_generate() -> Result<SecretKey, Box<SdkError>> {
296 SecretKey::generate_ed25519().map_err(|err| {
297 Box::new(SdkError::FailedToGenerateSecretKey {
298 context: "secret_key_from_pem".to_string(),
299 error: err,
300 })
301 })
302}
303
304pub fn secret_key_secp256k1_generate() -> Result<SecretKey, Box<SdkError>> {
314 SecretKey::generate_secp256k1().map_err(|err| {
315 Box::new(SdkError::FailedToGenerateSecretKey {
316 context: "secret_key_from_pem".to_string(),
317 error: err,
318 })
319 })
320}
321
322pub fn secret_key_from_pem(secret_key: &str) -> Result<SecretKey, Box<SdkError>> {
332 SecretKey::from_pem(secret_key).map_err(|err| {
333 Box::new(SdkError::FailedToParseSecretKey {
334 context: "secret_key_from_pem".to_string(),
335 error: err,
336 })
337 })
338}
339
340pub fn public_key_from_secret_key(secret_key: &str) -> Result<String, Box<SdkError>> {
350 let secret_key_from_pem = secret_key_from_pem(secret_key)?;
352
353 let public_key = CasperTypesPublicKey::from(&secret_key_from_pem);
355
356 let public_key_test: PublicKey = public_key.into();
358
359 Ok(public_key_test.to_string())
361}
362
363pub fn hex_to_uint8_vec(hex_string: &str) -> Vec<u8> {
373 let mut bytes = Vec::with_capacity(hex_string.len() / 2);
374 let mut hex_chars = hex_string.chars();
375 while let (Some(a), Some(b)) = (hex_chars.next(), hex_chars.next()) {
376 if let Ok(byte) = u8::from_str_radix(&format!("{a}{b}"), 16) {
377 bytes.push(byte);
378 } else {
379 return Vec::new();
381 }
382 }
383 bytes
384}
385
386pub fn hex_to_string(hex_string: &str) -> String {
396 match hex::decode(hex_string) {
397 Ok(bytes) => String::from_utf8_lossy(&bytes).to_string(),
398 Err(_) => hex_string.to_string(),
399 }
400}
401
402pub fn motes_to_cspr(motes: &str) -> Result<String, Box<SdkError>> {
412 match BigDecimal::from_str(motes) {
413 Ok(motes_decimal) => {
414 let divisor = BigDecimal::from(1_000_000_000);
415 let cspr_decimal = &motes_decimal / divisor;
416 let formatted_cspr = format!("{cspr_decimal:.2}");
417
418 if formatted_cspr.ends_with(".00") {
419 Ok(formatted_cspr.replace(".00", ""))
420 } else {
421 Ok(formatted_cspr)
422 }
423 }
424 Err(err) => Err(Box::new(SdkError::CustomError {
425 context: "Failed to parse input as BigDecimal",
426 error: format!("{err:?}"),
427 })),
428 }
429}
430
431pub fn json_pretty_print<T>(value: T, verbosity: Option<Verbosity>) -> Result<String, Box<SdkError>>
442where
443 T: Serialize,
444{
445 let deserialized = serde_json::to_value(&value).map_err(|e| Box::new(SdkError::from(e)))?;
446
447 match verbosity {
448 Some(Verbosity::Low) | None => Ok(deserialized.to_string()),
449 Some(Verbosity::Medium) => casper_types::json_pretty_print(&deserialized).map_err(|err| {
450 Box::new(SdkError::CustomError {
451 context: "Error in json_pretty_print",
452 error: format!("{err}"),
453 })
454 }),
455 Some(Verbosity::High) => {
456 serde_json::to_string_pretty(&deserialized).map_err(|e| Box::new(SdkError::from(e)))
457 }
458 }
459}
460
461#[cfg(all(feature = "js", target_arch = "wasm32"))]
472pub fn insert_js_value_arg(
473 args: &mut RuntimeArgs,
474 js_value_arg: JsValue,
475) -> Result<&RuntimeArgs, SdkError> {
476 if js_value_arg.is_instance_of::<js_sys::Object>() {
477 let json_arg: JsonArg = js_value_arg
478 .into_serde()
479 .map_err(|err| SdkError::CustomError {
480 context: "Error converting to JsonArg",
481 error: format!("{err:?}"),
482 })?;
483
484 let named_arg = NamedArg::try_from(json_arg).map_err(|err| SdkError::CustomError {
485 context: "Error converting to NamedArg",
486 error: format!("{err:?}"),
487 })?;
488
489 args.insert_cl_value(named_arg.name(), named_arg.cl_value().clone());
490 } else if let Some(string_arg) = js_value_arg.as_string() {
491 let simple_arg = string_arg;
492 casper_client::cli::insert_arg(&simple_arg, args).map_err(|err| SdkError::CustomError {
493 context: "Error inserting simple arg",
494 error: format!("{err:?}"),
495 })?;
496 } else {
497 return Err(SdkError::CustomError {
498 context: "Error converting to JsonArg or Simple Arg",
499 error: String::from("Conversion failed"),
500 });
501 }
502
503 Ok(args)
504}
505
506pub(crate) fn insert_arg(args: &mut RuntimeArgs, new_arg: String) -> &RuntimeArgs {
517 match serde_json::from_str::<JsonArg>(&new_arg) {
518 Ok(json_arg) => {
519 if let Ok(named_arg) = NamedArg::try_from(json_arg.clone()) {
520 args.insert_cl_value(named_arg.name(), named_arg.cl_value().clone());
522 }
523 }
524 Err(_) => {
525 let _ = casper_client::cli::insert_arg(&new_arg, args);
527 }
528 }
529 args
530}
531
532#[cfg(test)]
533mod tests {
534 use super::*;
535 use casper_types::U256;
536
537 #[test]
538 fn test_contract_hash_key_for_global_state() {
539 let hex = "5be5b0ef09a7016e11292848d77f539e55791cb07a7012fbc336b1f92a4fe743";
540 assert_eq!(
541 contract_hash_key_for_global_state(&format!("entity-contract-{hex}")),
542 format!("hash-{hex}")
543 );
544 assert_eq!(
545 contract_hash_key_for_global_state(&format!("hash-{hex}")),
546 format!("hash-{hex}")
547 );
548 assert_eq!(
549 contract_hash_key_for_global_state(hex),
550 format!("hash-{hex}")
551 );
552 assert_eq!(
553 contract_hash_key_for_global_state(&format!("uref-{hex}-007")),
554 format!("uref-{hex}-007")
555 );
556 assert_eq!(
557 strip_contract_key_prefix(&format!("entity-contract-{hex}")),
558 hex
559 );
560 assert_eq!(strip_contract_key_prefix(&format!("hash-{hex}")), hex);
561 }
562
563 #[test]
564 fn test_cl_value_to_json() {
565 let cl_value = CLValue::from_t((1, 2, 3)).unwrap();
566 let json_value = cl_value_to_json(&cl_value).unwrap();
567 assert_eq!(
568 json_value,
569 Value::Array(vec![
570 Value::Number(1.into()),
571 Value::Number(2.into()),
572 Value::Number(3.into())
573 ])
574 );
575 }
576
577 #[test]
578 fn test_get_current_timestamp() {
579 let timestamp = Some("1234567890".to_string());
580 let current_timestamp = get_current_timestamp(timestamp);
581 assert_eq!(¤t_timestamp, "1970-01-15T06:56:07Z");
582 }
583
584 #[test]
585 fn test_get_blake2b_hash() {
586 let metadata = "some metadata";
587 let hash = get_blake2b_hash(metadata);
588 assert_eq!(
589 &hash,
590 "767de9efccc76bc0eef85ea81fcaa56dc7047e660c74b3dc39f84ab8c4931c0d"
591 );
592 }
593
594 #[test]
595 fn test_get_ttl_or_default() {
596 let ttl = Some("1h".to_string());
597 let ttl_value = get_ttl_or_default(ttl.as_deref());
598 assert_eq!(ttl_value, "1h".to_string());
599
600 let default_ttl = get_ttl_or_default(None);
601 assert_eq!(default_ttl, DEFAULT_TTL.to_string());
602 }
603
604 #[test]
605 fn test_parse_timestamp() {
606 let valid_timestamp = "2023-11-06T12:00:00Z";
607 let parsed_timestamp = parse_timestamp(valid_timestamp);
608 assert!(parsed_timestamp.is_ok());
609
610 let invalid_timestamp = "invalid_timestamp";
611 let parsed_timestamp = parse_timestamp(invalid_timestamp);
612 assert!(parsed_timestamp.is_err());
613 }
614
615 #[test]
616 fn test_parse_ttl() {
617 let valid_ttl = "1h";
618 let parsed_ttl = parse_ttl(valid_ttl);
619 assert!(parsed_ttl.is_ok());
620
621 let invalid_ttl = "invalid_ttl";
622 let parsed_ttl = parse_ttl(invalid_ttl);
623 assert!(parsed_ttl.is_err());
624 }
625
626 #[test]
627 fn test_get_gas_price_or_default() {
628 let gas_price = Some(100);
629 let price = get_gas_price_or_default(gas_price);
630 assert_eq!(price, 100);
631
632 let default_price = get_gas_price_or_default(None);
633 assert_eq!(default_price, DEFAULT_GAS_PRICE);
634 }
635
636 #[test]
637 fn test_get_str_or_default() {
638 let input_str = Some("test_string".to_string());
639 let result = get_str_or_default(input_str.as_ref());
640 assert_eq!(result, "test_string");
641
642 let default_str: Option<String> = None;
643 let result = get_str_or_default(default_str.as_ref());
644 assert_eq!(result, "");
645 }
646
647 #[test]
648 fn test_secret_key_generate() {
649 let result = secret_key_generate();
651
652 assert!(result.is_ok());
654 let secret_key = result.unwrap();
655 assert_eq!(&secret_key.to_string(), "SecretKey::Ed25519");
656 }
657
658 #[test]
659 fn test_secret_key_secp256k1_generate() {
660 let result = secret_key_secp256k1_generate();
662
663 assert!(result.is_ok());
665 let secret_key = result.unwrap();
666 assert_eq!(&secret_key.to_string(), "SecretKey::Secp256k1");
667 }
668
669 #[test]
670 fn test_secret_key_from_pem() {
671 let pem_key = "-----BEGIN PRIVATE KEY-----\nTEST\n-----END PRIVATE KEY-----";
672 let result = secret_key_from_pem(pem_key);
673 assert!(result.is_err());
674 let pem_key =
675 "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEII8ULlk1CJ12ZQ+bScjBt/IxMAZNggClWqK56D1/7CbI\n-----END PRIVATE KEY-----";
676 let result = secret_key_from_pem(pem_key);
677 assert!(result.is_ok());
678 assert_eq!(&result.unwrap().to_string(), "SecretKey::Ed25519");
679 }
680
681 #[test]
682 fn test_public_key_from_secret_key() {
683 let pem_key = "-----BEGIN PRIVATE KEY-----\nTEST\n-----END PRIVATE KEY-----";
684 let result = public_key_from_secret_key(pem_key);
685 assert!(result.is_err());
686 let pem_key =
687 "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEII8ULlk1CJ12ZQ+bScjBt/IxMAZNggClWqK56D1/7CbI-----END PRIVATE KEY-----";
688 let result = public_key_from_secret_key(pem_key);
689 assert!(result.is_ok());
690 assert_eq!(
691 result.unwrap(),
692 "01aff5c18a954604dd27d139d8e0cfc533ac3d53784d76c7a7ac5ff4039510fdf6"
693 );
694 }
695
696 #[test]
697 fn test_hex_to_uint8_vec() {
698 let hex_string = "0161e40005434ba3cd9a791a2827f5fa3ee514d1475fe72b2823cbaac9c3c71483";
699 let result = hex_to_uint8_vec(hex_string);
700 assert_eq!(
701 result,
702 vec![
703 1, 97, 228, 0, 5, 67, 75, 163, 205, 154, 121, 26, 40, 39, 245, 250, 62, 229, 20,
704 209, 71, 95, 231, 43, 40, 35, 203, 170, 201, 195, 199, 20, 131
705 ]
706 );
707 }
708
709 #[test]
710 fn test_hex_to_string() {
711 let hex_string = "48656c6c6f20436173706572";
712 let result = hex_to_string(hex_string);
713 assert_eq!(result, "Hello Casper");
714 }
715
716 #[test]
717 fn test_motes_to_cspr() {
718 let motes = "1000000000";
719 let result = motes_to_cspr(motes).unwrap();
720 assert_eq!(result, "1");
721 }
722
723 #[test]
724 fn test_json_pretty_print() {
725 #[derive(Serialize, Clone)]
726 struct TestData {
727 age: i32,
728 name: String,
729 }
730
731 let data = TestData {
732 age: 42,
733 name: "Joe".to_string(),
734 };
735
736 let result = json_pretty_print(data.clone(), None).unwrap();
737 assert_eq!(result, "{\"age\":42,\"name\":\"Joe\"}");
738
739 let result = json_pretty_print(data.clone(), Some(Verbosity::Low)).unwrap();
740 assert_eq!(result, "{\"age\":42,\"name\":\"Joe\"}");
741
742 let result = json_pretty_print(data.clone(), Some(Verbosity::Medium)).unwrap();
743 assert_eq!(result, "{\n \"age\": 42,\n \"name\": \"Joe\"\n}");
744
745 let result = json_pretty_print(data, Some(Verbosity::High)).unwrap();
746 assert_eq!(result, "{\n \"age\": 42,\n \"name\": \"Joe\"\n}");
747 }
748
749 #[test]
750 fn test_insert_arg_simple() {
751 let mut args = RuntimeArgs::new();
752 let new_arg = "message:String='Hello Casper";
753 let result_args = insert_arg(&mut args, new_arg.to_string());
754 assert_eq!(result_args.len(), 1);
755 let cl_value = result_args.get("message").unwrap();
756 let json = cl_value_to_json(cl_value).unwrap();
757 let expexted_json = Value::String("Hello Casper".to_string());
758 assert_eq!(json, expexted_json);
759 }
760
761 #[test]
762 fn test_insert_arg_json() {
763 let mut args = RuntimeArgs::new();
764 let arg_json = r#"{"name": "bar", "type": "U256", "value": 1}"#; let result_args = insert_arg(&mut args, arg_json.to_string());
766 assert_eq!(result_args.len(), 1);
767 let cl_value = result_args.get("bar").unwrap();
768 let json = cl_value_to_json(cl_value).unwrap();
769 let expexted_json = Value::String("1".to_string());
770 assert_eq!(json, expexted_json);
771 }
772
773 #[test]
774 pub fn test_make_dictionary_item_key() {
775 let key = Key::from_formatted_str(
776 "account-hash-e11bfffe63bf899ea07117af8a2bb43ef0078c0e38ebee6b6cb0b0e39c233538",
777 )
778 .unwrap();
779 let value = U256::from(1);
780 let dictionary_item_key = make_dictionary_item_key(&key, &value);
781 assert_eq!(
782 dictionary_item_key,
783 "145f6211a24c0a8af16b47e7aa58431ea25172eb402903b3c25ac92b9784c7a9".to_string()
784 );
785 let key = Key::from_formatted_str(
786 "account-hash-813428ce1a9805f1087db07e6017c6c4f5af0ee78a05591bb6577763e89b4f1f",
787 )
788 .unwrap();
789 let value = Key::from_formatted_str(
790 "account-hash-e11bfffe63bf899ea07117af8a2bb43ef0078c0e38ebee6b6cb0b0e39c233538",
791 )
792 .unwrap();
793 let dictionary_item_key = make_dictionary_item_key(&key, &value);
794 assert_eq!(
795 dictionary_item_key,
796 "1e26dc82db208943c3785c0e11b9d78b9c408fee748c78dda5a5d016840dedca".to_string()
797 );
798 }
799
800 #[test]
801 fn test_get_base64_key_from_account_hash() {
802 let input_hash =
804 "account-hash-b485c074cef7ccaccd0302949d2043ab7133abdb14cfa87e8392945c0bd80a5f";
805 let expected_output = "ALSFwHTO98yszQMClJ0gQ6txM6vbFM+ofoOSlFwL2Apf";
806
807 let result = get_base64_key_from_account_hash(input_hash).unwrap();
809
810 assert_eq!(result, expected_output.to_string());
812 }
813
814 #[test]
815 fn test_get_base64_key_from_key_hash() {
816 let input_hash = "hash-b485c074cef7ccaccd0302949d2043ab7133abdb14cfa87e8392945c0bd80a5f";
818 let expected_output = "AbSFwHTO98yszQMClJ0gQ6txM6vbFM+ofoOSlFwL2Apf";
819
820 let result = get_base64_key_from_key_hash(input_hash).unwrap();
822
823 assert_eq!(result, expected_output.to_string());
825 }
826}