1use crate::{DefaultEnvironment, errors::Error};
6use contract_extrinsics::{ContractArtifacts, ContractStorageRpc, TrieId};
7use contract_transcode::{
8 ContractMessageTranscoder,
9 ink_metadata::{MessageParamSpec, layout::Layout},
10};
11use ink_env::call::utils::EncodeArgsWith;
12use pop_common::{
13 DefaultConfig, find_contract_artifact_path, format_type, manifest::from_path,
14 parse_h160_account,
15};
16use scale_info::{PortableRegistry, Type, form::PortableForm};
17use sp_core::blake2_128;
18use std::path::Path;
19use url::Url;
20
21const MAPPING_TYPE_PATH: &str = "ink_storage::lazy::mapping::Mapping";
22
23#[derive(Clone, Eq, PartialEq, Debug)]
25pub enum ContractCallable {
26 Function(ContractFunction),
28 Storage(ContractStorage),
30}
31
32impl ContractCallable {
33 pub fn name(&self) -> String {
41 match self {
42 ContractCallable::Function(f) => f.label.clone(),
43 ContractCallable::Storage(s) => s.name.clone(),
44 }
45 }
46
47 pub fn hint(&self) -> String {
49 match self {
50 ContractCallable::Function(f) => {
51 let prelude = if f.mutates { "📝 [MUTATES] " } else { "[READS] " };
52 format!("{}{}", prelude, f.label)
53 },
54 ContractCallable::Storage(s) => {
55 format!("[STORAGE] {}", &s.name)
56 },
57 }
58 }
59
60 pub fn docs(&self) -> String {
62 match self {
63 ContractCallable::Function(f) => f.docs.clone(),
64 ContractCallable::Storage(s) => s.type_name.clone(),
65 }
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct Param {
72 pub label: String,
74 pub type_name: String,
76}
77
78#[derive(Clone, PartialEq, Eq, Debug)]
80pub struct ContractFunction {
81 pub label: String,
83 pub payable: bool,
85 pub args: Vec<Param>,
87 pub docs: String,
89 pub default: bool,
91 pub mutates: bool,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
97pub struct ContractStorage {
98 pub name: String,
100 pub type_name: String,
102 pub storage_key: u32,
104 pub type_id: u32,
106 pub key_type_name: Option<String>,
108}
109
110#[derive(Clone, PartialEq, Eq)]
112pub enum FunctionType {
113 Constructor,
115 Message,
117}
118
119pub fn get_messages<P>(path: P) -> Result<Vec<ContractFunction>, Error>
124where
125 P: AsRef<Path>,
126{
127 get_contract_functions(path.as_ref(), FunctionType::Message)
128}
129
130pub fn get_constructors<P>(path: P) -> Result<Vec<ContractFunction>, Error>
135where
136 P: AsRef<Path>,
137{
138 get_contract_functions(path.as_ref(), FunctionType::Constructor)
139}
140
141fn collapse_docs(docs: &[String]) -> String {
142 docs.iter()
143 .map(|s| if s.is_empty() { " " } else { s })
144 .collect::<Vec<_>>()
145 .join("")
146 .trim()
147 .to_string()
148}
149
150fn get_contract_transcoder(path: &Path) -> anyhow::Result<ContractMessageTranscoder> {
151 let contract_artifacts = if path.is_dir() || path.ends_with("Cargo.toml") {
152 let cargo_toml_path =
153 if path.ends_with("Cargo.toml") { path.to_path_buf() } else { path.join("Cargo.toml") };
154 let artifact_from_manifest =
155 || ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None);
156
157 let artifact = from_path(&cargo_toml_path)
158 .ok()
159 .and_then(|manifest| manifest.package)
160 .and_then(|package| {
161 let project_root = cargo_toml_path.parent().unwrap_or(&cargo_toml_path);
162 find_contract_artifact_path(project_root, package.name())
163 });
164
165 if let Some(contract_path) = artifact {
166 ContractArtifacts::from_manifest_or_file(None, Some(&contract_path))?
167 } else {
168 artifact_from_manifest()?
169 }
170 } else {
171 ContractArtifacts::from_manifest_or_file(None, Some(&path.to_path_buf()))?
172 };
173 contract_artifacts.contract_transcoder()
174}
175
176async fn decode_mapping(
177 storage: &ContractStorage,
178 rpc: &ContractStorageRpc<DefaultConfig>,
179 trie_id: &TrieId,
180 ty: &Type<PortableForm>,
181 transcoder: &ContractMessageTranscoder,
182 key_filter: Option<&str>,
183) -> anyhow::Result<String> {
184 let mut all_keys = Vec::new();
187 let mut start_key: Option<Vec<u8>> = None;
188 const PAGE: u32 = 1000;
190 loop {
191 let page_keys = rpc
192 .fetch_storage_keys_paged(
193 trie_id,
194 None, PAGE,
196 start_key.as_deref(),
197 None,
198 )
199 .await?;
200 let count = page_keys.len();
201 if count == 0 {
202 break;
203 }
204 start_key = page_keys.last().map(|b| b.0.clone());
205 all_keys.extend(page_keys);
206 if (count as u32) < PAGE {
207 break;
208 }
209 }
210
211 let keys: Vec<_> = all_keys
221 .into_iter()
222 .filter(|k| {
223 if k.0.len() < 20 {
225 return false;
226 }
227 let mut rk = [0u8; 4];
229 rk.copy_from_slice(&k.0[16..20]);
230 let root = u32::from_le_bytes(rk);
231 root == storage.storage_key
232 })
233 .collect();
234
235 if keys.is_empty() {
236 return Ok("Mapping is empty".to_string());
237 }
238
239 let values = rpc.fetch_storage_entries(trie_id, &keys, None).await?;
241
242 let (key_type_id, value_type_id) = match (param_type_id(ty, "K"), param_type_id(ty, "V")) {
244 (Some(k), Some(v)) => (k, v),
245 _ => {
246 return Ok(format!("Mapping {{ {} entries }}", values.len()));
248 },
249 };
250
251 let pairs: Vec<(Vec<u8>, Option<Vec<u8>>)> = keys
253 .into_iter()
254 .zip(values.into_iter())
255 .map(|(k, v)| (k.0, v.map(|b| b.0)))
256 .collect();
257
258 decode_mapping_impl(pairs, key_type_id, value_type_id, transcoder, key_filter)
259}
260
261pub(crate) fn decode_mapping_impl(
264 pairs: Vec<(Vec<u8>, Option<Vec<u8>>)>,
265 key_type_id: u32,
266 value_type_id: u32,
267 transcoder: &ContractMessageTranscoder,
268 key_filter: Option<&str>,
269) -> anyhow::Result<String> {
270 let key_filter = key_filter.map(|s| s.trim()).filter(|s| !s.is_empty());
272
273 if pairs.is_empty() {
274 return Ok("Mapping is empty".to_string());
275 }
276
277 let mut rendered_pairs: Vec<String> = Vec::new();
278 for (key, val_opt) in pairs.into_iter() {
279 if let Some(val) = val_opt {
280 let key_bytes = if key.len() > 20 { &key[20..] } else { &[] };
282 let k_decoded = transcoder.decode(key_type_id, &mut &key_bytes[..])?;
283 let v_decoded = transcoder.decode(value_type_id, &mut &val[..])?;
284 let k_str = k_decoded.to_string();
285 if let Some(filter) = key_filter {
286 if k_str == filter {
287 return Ok(v_decoded.to_string());
289 }
290 } else {
291 rendered_pairs.push(format!("{{ {k_str} => {v_decoded} }}"));
292 }
293 }
294 }
295 if rendered_pairs.is_empty() {
296 if key_filter.is_some() {
297 Ok("No value found for the provided key".to_string())
298 } else {
299 Ok("Mapping is empty".to_string())
300 }
301 } else {
302 Ok(rendered_pairs.join("\n"))
303 }
304}
305
306pub async fn fetch_contract_storage(
318 storage: &ContractStorage,
319 account: &str,
320 rpc_url: &Url,
321 path: &Path,
322) -> anyhow::Result<String> {
323 fetch_contract_storage_with_param(storage, account, rpc_url, path, None).await
324}
325
326pub async fn fetch_contract_storage_with_param(
342 storage: &ContractStorage,
343 account: &str,
344 rpc_url: &Url,
345 path: &Path,
346 mapping_key: Option<&str>,
347) -> anyhow::Result<String> {
348 let transcoder = get_contract_transcoder(path)?;
350
351 let rpc = ContractStorageRpc::<DefaultConfig>::new(rpc_url).await?;
353
354 let account_id = parse_h160_account(account)?;
356
357 let contract_info = rpc.fetch_contract_info::<DefaultEnvironment>(&account_id).await?;
359 let trie_id = contract_info.trie_id();
360
361 let registry = transcoder.metadata().registry();
363 if let Some(ty) = registry.resolve(storage.type_id) {
364 let path = ty.path.to_string();
365 if path == MAPPING_TYPE_PATH {
366 return decode_mapping(storage, &rpc, trie_id, ty, &transcoder, mapping_key).await;
367 }
368 }
369
370 let root_key_bytes = storage.storage_key.encode();
373 let mut full_key = blake2_128(&root_key_bytes).to_vec();
374 full_key.extend_from_slice(&root_key_bytes);
375
376 let bytes = full_key.into();
378 let value = rpc.fetch_contract_storage(trie_id, &bytes, None).await?;
379
380 match value {
381 Some(data) => {
382 let decoded_value = transcoder.decode(storage.type_id, &mut &data.0[..])?;
384 Ok(decoded_value.to_string())
385 },
386 None => Ok("No value found".to_string()),
387 }
388}
389
390pub fn get_contract_storage_info(path: &Path) -> Result<Vec<ContractStorage>, Error> {
395 let transcoder = get_contract_transcoder(path)?;
396 let metadata = transcoder.metadata();
397 let layout = metadata.layout();
398 let registry = metadata.registry();
399
400 let mut storage_items = Vec::new();
401 extract_storage_fields(layout, registry, &mut storage_items);
402
403 Ok(storage_items)
404}
405
406fn extract_storage_fields(
408 layout: &Layout<PortableForm>,
409 registry: &PortableRegistry,
410 storage_items: &mut Vec<ContractStorage>,
411) {
412 match layout {
413 Layout::Root(root_layout) => {
414 let root_key = *root_layout.root_key().key();
416 extract_storage_fields_with_key(
417 root_layout.layout(),
418 registry,
419 storage_items,
420 root_key,
421 Some(root_layout.ty().id),
422 );
423 },
424 Layout::Struct(struct_layout) => {
425 for field in struct_layout.fields() {
428 extract_storage_fields(field.layout(), registry, storage_items);
429 }
430 },
431 Layout::Leaf(_) => {
432 },
435 Layout::Hash(_) | Layout::Array(_) | Layout::Enum(_) => {
436 },
439 }
440}
441
442fn extract_storage_fields_with_key(
444 layout: &Layout<PortableForm>,
445 registry: &PortableRegistry,
446 storage_items: &mut Vec<ContractStorage>,
447 root_key: u32,
448 root_type_id: Option<u32>,
449) {
450 match layout {
451 Layout::Root(root_layout) => {
452 let new_root_key = *root_layout.root_key().key();
454 extract_storage_fields_with_key(
455 root_layout.layout(),
456 registry,
457 storage_items,
458 new_root_key,
459 Some(root_layout.ty().id),
460 );
461 },
462 Layout::Struct(struct_layout) => {
463 for field in struct_layout.fields() {
465 extract_field(
466 field.name(),
467 field.layout(),
468 registry,
469 storage_items,
470 root_key,
471 root_type_id,
472 );
473 }
474 },
475 Layout::Leaf(_) => {
476 },
478 Layout::Hash(_) | Layout::Array(_) | Layout::Enum(_) => {
479 },
481 }
482}
483
484fn try_extract_mapping(
485 name: &str,
486 tid: u32,
487 root_key: u32,
488 registry: &PortableRegistry,
489 storage_items: &mut Vec<ContractStorage>,
490) -> bool {
491 if let Some(ty) = registry.resolve(tid) &&
492 ty.path.to_string() == MAPPING_TYPE_PATH
493 {
494 let type_name = format_type(ty, registry);
495 let key_type_name = param_type_id(ty, "K")
496 .and_then(|kid| registry.resolve(kid))
497 .map(|kty| format_type(kty, registry));
498 storage_items.push(ContractStorage {
499 name: name.to_string(),
500 type_name,
501 storage_key: root_key,
502 type_id: tid,
503 key_type_name,
504 });
505 return true;
506 }
507 false
508}
509
510fn extract_field(
512 name: &str,
513 layout: &Layout<PortableForm>,
514 registry: &PortableRegistry,
515 storage_items: &mut Vec<ContractStorage>,
516 root_key: u32,
517 root_type_id: Option<u32>,
518) {
519 match layout {
520 Layout::Leaf(leaf_layout) => {
521 let type_id = leaf_layout.ty();
523 if let Some(ty) = registry.resolve(type_id.id) {
524 let type_name = format_type(ty, registry);
525 storage_items.push(ContractStorage {
526 name: name.to_string(),
527 type_name,
528 storage_key: root_key,
529 type_id: type_id.id,
530 key_type_name: None,
531 });
532 }
533 },
534 Layout::Struct(struct_layout) => {
535 for field in struct_layout.fields() {
537 let qualified_name = format!("{}.{}", name, field.name());
538 extract_field(
539 &qualified_name,
540 field.layout(),
541 registry,
542 storage_items,
543 root_key,
544 root_type_id,
545 );
546 }
547 },
548 Layout::Array(array_layout) => {
549 let len = array_layout.len();
551 for i in 0..len {
552 let qualified_name = format!("{}[{}]", name, i);
553 extract_field(
554 &qualified_name,
555 array_layout.layout(),
556 registry,
557 storage_items,
558 root_key,
559 root_type_id,
560 );
561 }
562 },
563 Layout::Enum(enum_layout) => {
564 for variant_layout in enum_layout.variants().values() {
566 let variant_prefix = format!("{}::{}", name, variant_layout.name());
567 for field in variant_layout.fields() {
568 let qualified_name = format!("{}.{}", variant_prefix, field.name());
569 extract_field(
570 &qualified_name,
571 field.layout(),
572 registry,
573 storage_items,
574 root_key,
575 root_type_id,
576 );
577 }
578 }
579 },
580 Layout::Hash(hash_layout) => {
581 if let Some(tid) = root_type_id &&
585 try_extract_mapping(name, tid, root_key, registry, storage_items)
586 {
587 return;
588 }
589 extract_field(
591 name,
592 hash_layout.layout(),
593 registry,
594 storage_items,
595 root_key,
596 root_type_id,
597 );
598 },
599 Layout::Root(root_layout) => {
600 let new_root_key = *root_layout.root_key().key();
602 let tid = root_layout.ty().id;
603 if try_extract_mapping(name, tid, new_root_key, registry, storage_items) {
606 return;
607 }
608 extract_field(
609 name,
610 root_layout.layout(),
611 registry,
612 storage_items,
613 new_root_key,
614 Some(tid),
615 );
616 },
617 }
618}
619
620fn param_type_id(type_def: &Type<PortableForm>, param_name: &str) -> Option<u32> {
622 type_def
623 .type_params
624 .iter()
625 .find(|p| p.name == param_name)
626 .and_then(|p| p.ty.as_ref())
627 .map(|pt| pt.id)
628}
629
630fn get_contract_functions(
637 path: &Path,
638 function_type: FunctionType,
639) -> Result<Vec<ContractFunction>, Error> {
640 let transcoder = get_contract_transcoder(path)?;
641 let metadata = transcoder.metadata();
642
643 Ok(match function_type {
644 FunctionType::Message => metadata
645 .spec()
646 .messages()
647 .iter()
648 .map(|message| ContractFunction {
649 label: message.label().to_string(),
650 mutates: message.mutates(),
651 payable: message.payable(),
652 args: process_args(message.args(), metadata.registry()),
653 docs: collapse_docs(message.docs()),
654 default: message.default(),
655 })
656 .collect(),
657 FunctionType::Constructor => metadata
658 .spec()
659 .constructors()
660 .iter()
661 .map(|constructor| ContractFunction {
662 label: constructor.label().to_string(),
663 payable: constructor.payable(),
664 args: process_args(constructor.args(), metadata.registry()),
665 docs: collapse_docs(constructor.docs()),
666 default: constructor.default(),
667 mutates: true,
668 })
669 .collect(),
670 })
671}
672
673pub fn get_message<P>(path: P, message: &str) -> Result<ContractFunction, Error>
679where
680 P: AsRef<Path>,
681{
682 get_messages(path.as_ref())?
683 .into_iter()
684 .find(|msg| msg.label == message)
685 .ok_or_else(|| Error::InvalidMessageName(message.to_string()))
686}
687
688fn get_constructor<P>(path: P, constructor: &str) -> Result<ContractFunction, Error>
694where
695 P: AsRef<Path>,
696{
697 get_constructors(path.as_ref())?
698 .into_iter()
699 .find(|c| c.label == constructor)
700 .ok_or_else(|| Error::InvalidConstructorName(constructor.to_string()))
701}
702
703fn process_args(
705 params: &[MessageParamSpec<PortableForm>],
706 registry: &PortableRegistry,
707) -> Vec<Param> {
708 let mut args: Vec<Param> = Vec::new();
709 for arg in params {
710 let type_name =
712 format_type(registry.resolve(arg.ty().ty().id).expect("type not found"), registry);
713 args.push(Param { label: arg.label().to_string(), type_name });
714 }
715 args
716}
717
718pub fn extract_function<P>(
726 path: P,
727 label: &str,
728 function_type: FunctionType,
729) -> Result<ContractFunction, Error>
730where
731 P: AsRef<Path>,
732{
733 match function_type {
734 FunctionType::Message => get_message(path.as_ref(), label),
735 FunctionType::Constructor => get_constructor(path.as_ref(), label),
736 }
737}
738
739pub fn process_function_args(
746 function: &ContractFunction,
747 args: Vec<String>,
748) -> Result<Vec<String>, Error> {
749 if args.len() != function.args.len() {
750 return Err(Error::IncorrectArguments {
751 expected: function.args.len(),
752 provided: args.len(),
753 });
754 }
755 Ok(args
756 .into_iter()
757 .zip(&function.args)
758 .map(|(arg, param)| match (param.type_name.starts_with("Option<"), arg.is_empty()) {
759 (true, true) => "None".to_string(),
761 (true, false) => format!("Some({})", arg),
763 _ => arg,
765 })
766 .collect())
767}
768
769#[cfg(test)]
770mod tests {
771 use std::env;
772
773 use super::*;
774 use crate::{mock_build_process, new_environment};
775 use anyhow::Result;
776 use scale_info::{Registry, TypeDef, TypeDefPrimitive, TypeInfo};
777 use std::{
778 marker::PhantomData,
779 path::PathBuf,
780 sync::{LazyLock, Mutex},
781 };
782 use temp_env;
783 const CONTRACT_FILE: &str = "./tests/files/testing.contract";
787 static ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
788
789 fn shared_contract_dir() -> PathBuf {
793 let base = std::env::temp_dir().join("pop-test-contract-fixture");
794 let contract_dir = base.join("testing");
795 let ready_marker = base.join(".ready");
796 let lock_dir = base.join(".creating");
797
798 if ready_marker.exists() {
800 return contract_dir;
801 }
802
803 std::fs::create_dir_all(&base).ok();
804
805 match std::fs::create_dir(&lock_dir) {
807 Ok(_) => {
808 let temp_dir =
810 new_environment("testing").expect("Failed to create test environment");
811 let current_dir = env::current_dir().expect("Failed to get current directory");
812 mock_build_process(
813 temp_dir.path().join("testing"),
814 current_dir.join(CONTRACT_FILE),
815 current_dir.join("./tests/files/testing.json"),
816 )
817 .expect("Failed to mock build process");
818
819 if contract_dir.exists() {
821 std::fs::remove_dir_all(&contract_dir).ok();
822 }
823 copy_dir_recursive(temp_dir.path().join("testing"), &contract_dir);
824
825 std::fs::write(&ready_marker, "").unwrap();
827 std::fs::remove_dir(&lock_dir).ok();
828 },
829 Err(_) => {
830 while !ready_marker.exists() {
832 std::thread::sleep(std::time::Duration::from_millis(50));
833 }
834 },
835 }
836
837 contract_dir
838 }
839
840 fn copy_dir_recursive(src: impl AsRef<std::path::Path>, dst: &std::path::Path) {
841 std::fs::create_dir_all(dst).unwrap();
842 for entry in std::fs::read_dir(src).unwrap() {
843 let entry = entry.unwrap();
844 let ty = entry.file_type().unwrap();
845 let dest_path = dst.join(entry.file_name());
846 if ty.is_dir() {
847 copy_dir_recursive(entry.path(), &dest_path);
848 } else {
849 std::fs::copy(entry.path(), dest_path).unwrap();
850 }
851 }
852 }
853
854 #[test]
855 fn get_messages_work() -> Result<()> {
856 let contract_dir = shared_contract_dir();
857 let current_dir = env::current_dir().expect("Failed to get current directory");
858
859 fn assert_contract_metadata_parsed(message: Vec<ContractFunction>) -> Result<()> {
861 assert_eq!(message.len(), 3);
862 assert_eq!(message[0].label, "flip");
863 assert_eq!(
864 message[0].docs,
865 "A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa."
866 );
867 assert_eq!(message[1].label, "get");
868 assert_eq!(message[1].docs, "Simply returns the current value of our `bool`.");
869 assert_eq!(message[2].label, "specific_flip");
870 assert_eq!(
871 message[2].docs,
872 "A message for testing, flips the value of the stored `bool` with `new_value` and is payable"
873 );
874 assert_eq!(message[2].args.len(), 2);
876 assert_eq!(message[2].args[0].label, "new_value".to_string());
877 assert_eq!(message[2].args[0].type_name, "bool".to_string());
878 assert_eq!(message[2].args[1].label, "number".to_string());
879 assert_eq!(message[2].args[1].type_name, "Option<u32>: None, Some(u32)".to_string());
880 Ok(())
881 }
882
883 let message = get_messages(&contract_dir)?;
885 assert_contract_metadata_parsed(message)?;
886
887 let message = get_messages(current_dir.join(CONTRACT_FILE))?;
889 assert_contract_metadata_parsed(message)?;
890
891 Ok(())
892 }
893
894 #[test]
895 fn get_messages_uses_artifact_when_cargo_unavailable() -> Result<()> {
896 let temp_dir = new_environment("testing")?;
897 let current_dir = env::current_dir().expect("Failed to get current directory");
898 mock_build_process(
899 temp_dir.path().join("testing"),
900 current_dir.join(CONTRACT_FILE),
901 current_dir.join("./tests/files/testing.json"),
902 )?;
903
904 let _env_guard = ENV_LOCK.lock().expect("env lock poisoned");
906 let message = temp_env::with_var("CARGO", Some("/nonexistent/cargo"), || {
907 get_messages(temp_dir.path().join("testing"))
908 });
909
910 let message = message?;
911 assert_eq!(message.len(), 3);
912 assert_eq!(message[0].label, "flip");
913 Ok(())
914 }
915
916 #[test]
917 fn get_message_work() -> Result<()> {
918 let contract_dir = shared_contract_dir();
919 assert!(matches!(
920 get_message(&contract_dir, "wrong_flip"),
921 Err(Error::InvalidMessageName(name)) if name == *"wrong_flip"));
922 let message = get_message(&contract_dir, "specific_flip")?;
923 assert_eq!(message.label, "specific_flip");
924 assert_eq!(
925 message.docs,
926 "A message for testing, flips the value of the stored `bool` with `new_value` and is payable"
927 );
928 assert_eq!(message.args.len(), 2);
930 assert_eq!(message.args[0].label, "new_value".to_string());
931 assert_eq!(message.args[0].type_name, "bool".to_string());
932 assert_eq!(message.args[1].label, "number".to_string());
933 assert_eq!(message.args[1].type_name, "Option<u32>: None, Some(u32)".to_string());
934 Ok(())
935 }
936
937 #[test]
938 fn get_constructors_work() -> Result<()> {
939 let contract_dir = shared_contract_dir();
940 let constructor = get_constructors(&contract_dir)?;
941 assert_eq!(constructor.len(), 2);
942 assert_eq!(constructor[0].label, "new");
943 assert_eq!(
944 constructor[0].docs,
945 "Constructor that initializes the `bool` value to the given `init_value`."
946 );
947 assert_eq!(constructor[1].label, "default");
948 assert_eq!(
949 constructor[1].docs,
950 "Constructor that initializes the `bool` value to `false`. Constructors can delegate to other constructors."
951 );
952 assert_eq!(constructor[0].args.len(), 1);
954 assert_eq!(constructor[0].args[0].label, "init_value".to_string());
955 assert_eq!(constructor[0].args[0].type_name, "bool".to_string());
956 assert_eq!(constructor[1].args.len(), 2);
957 assert_eq!(constructor[1].args[0].label, "init_value".to_string());
958 assert_eq!(constructor[1].args[0].type_name, "bool".to_string());
959 assert_eq!(constructor[1].args[1].label, "number".to_string());
960 assert_eq!(constructor[1].args[1].type_name, "Option<u32>: None, Some(u32)".to_string());
961 Ok(())
962 }
963
964 #[test]
965 fn get_constructor_work() -> Result<()> {
966 let contract_dir = shared_contract_dir();
967 assert!(matches!(
968 get_constructor(&contract_dir, "wrong_constructor"),
969 Err(Error::InvalidConstructorName(name)) if name == *"wrong_constructor"));
970 let constructor = get_constructor(&contract_dir, "default")?;
971 assert_eq!(constructor.label, "default");
972 assert_eq!(
973 constructor.docs,
974 "Constructor that initializes the `bool` value to `false`. Constructors can delegate to other constructors."
975 );
976 assert_eq!(constructor.args.len(), 2);
978 assert_eq!(constructor.args[0].label, "init_value".to_string());
979 assert_eq!(constructor.args[0].type_name, "bool".to_string());
980 assert_eq!(constructor.args[1].label, "number".to_string());
981 assert_eq!(constructor.args[1].type_name, "Option<u32>: None, Some(u32)".to_string());
982 Ok(())
983 }
984
985 #[test]
986 fn process_function_args_work() -> Result<()> {
987 let contract_dir = shared_contract_dir();
988
989 assert!(matches!(
991 extract_function(&contract_dir, "wrong_flip", FunctionType::Message),
992 Err(Error::InvalidMessageName(error)) if error == *"wrong_flip"));
993
994 let specific_flip =
995 extract_function(&contract_dir, "specific_flip", FunctionType::Message)?;
996
997 assert!(matches!(
998 process_function_args(&specific_flip, Vec::new()),
999 Err(Error::IncorrectArguments {expected, provided }) if expected == 2 && provided == 0
1000 ));
1001
1002 assert_eq!(
1003 process_function_args(&specific_flip, ["true".to_string(), "2".to_string()].to_vec())?,
1004 ["true".to_string(), "Some(2)".to_string()]
1005 );
1006
1007 assert_eq!(
1008 process_function_args(&specific_flip, ["true".to_string(), "".to_string()].to_vec())?,
1009 ["true".to_string(), "None".to_string()]
1010 );
1011
1012 assert!(matches!(
1014 extract_function(&contract_dir, "wrong_constructor", FunctionType::Constructor),
1015 Err(Error::InvalidConstructorName(error)) if error == *"wrong_constructor"));
1016
1017 let default_constructor =
1018 extract_function(&contract_dir, "default", FunctionType::Constructor)?;
1019 assert!(matches!(
1020 process_function_args(&default_constructor, Vec::new()),
1021 Err(Error::IncorrectArguments {expected, provided }) if expected == 2 && provided == 0
1022 ));
1023
1024 assert_eq!(
1025 process_function_args(
1026 &default_constructor,
1027 ["true".to_string(), "2".to_string()].to_vec()
1028 )?,
1029 ["true".to_string(), "Some(2)".to_string()]
1030 );
1031
1032 assert_eq!(
1033 process_function_args(
1034 &default_constructor,
1035 ["true".to_string(), "".to_string()].to_vec()
1036 )?,
1037 ["true".to_string(), "None".to_string()]
1038 );
1039 Ok(())
1040 }
1041
1042 #[test]
1043 fn get_contract_storage_work() -> Result<()> {
1044 let contract_dir = shared_contract_dir();
1045 let current_dir = env::current_dir().expect("Failed to get current directory");
1046
1047 let storage = get_contract_storage_info(contract_dir.as_path())?;
1049 assert_eq!(storage.len(), 2);
1050 assert_eq!(storage[0].name, "value");
1051 assert_eq!(storage[0].type_name, "bool");
1052 assert_eq!(storage[1].name, "number");
1053 assert!(storage[1].type_name.contains("u32"));
1055
1056 let storage = get_contract_storage_info(
1058 current_dir.join("./tests/files/testing.contract").as_path(),
1059 )?;
1060 assert_eq!(storage.len(), 2);
1061 assert_eq!(storage[0].name, "value");
1062 assert_eq!(storage[0].type_name, "bool");
1063
1064 Ok(())
1065 }
1066
1067 #[derive(TypeInfo)]
1068 struct DummyKV<K, V>(PhantomData<(K, V)>);
1069
1070 #[test]
1071 fn param_type_id_resolves_generic_k_v() -> Result<()> {
1072 let mut reg = Registry::new();
1074 let _ = reg.register_type(&scale_info::meta_type::<DummyKV<u32, bool>>());
1075 let portable: PortableRegistry = reg.into();
1076 let type_id = portable
1078 .types
1079 .iter()
1080 .find(|t| t.ty.path.segments.last().map(|s| s == "DummyKV").unwrap_or(false))
1081 .map(|t| t.id)
1082 .expect("dummy type must exist");
1083 let ty = portable.resolve(type_id).unwrap();
1084 let k_id = param_type_id(ty, "K").expect("K param must exist");
1086 let v_id = param_type_id(ty, "V").expect("V param must exist");
1087 let k_ty = portable.resolve(k_id).unwrap();
1088 let v_ty = portable.resolve(v_id).unwrap();
1089 match &k_ty.type_def {
1090 TypeDef::Primitive(p) => assert_eq!(*p, TypeDefPrimitive::U32),
1091 other => panic!("Expected primitive u32 for K, got {:?}", other),
1092 }
1093 match &v_ty.type_def {
1094 TypeDef::Primitive(p) => assert_eq!(*p, TypeDefPrimitive::Bool),
1095 other => panic!("Expected primitive bool for V, got {:?}", other),
1096 }
1097 Ok(())
1098 }
1099
1100 fn test_transcoder() -> Result<ContractMessageTranscoder> {
1102 let current_dir = env::current_dir().expect("Failed to get current directory");
1103 get_contract_transcoder(current_dir.join("./tests/files/testing.contract").as_path())
1104 }
1105
1106 fn find_primitive(reg: &PortableRegistry, prim: TypeDefPrimitive) -> u32 {
1108 reg.types
1109 .iter()
1110 .find_map(|t| match &t.ty.type_def {
1111 TypeDef::Primitive(p) if *p == prim => Some(t.id),
1112 _ => None,
1113 })
1114 .expect("primitive type must exist in registry")
1115 }
1116
1117 #[test]
1118 fn decode_mapping_impl_empty_returns_message() -> Result<()> {
1119 let transcoder = test_transcoder()?;
1120 let reg = transcoder.metadata().registry();
1121 let u8_id = find_primitive(reg, TypeDefPrimitive::U8);
1122
1123 let out = decode_mapping_impl(Vec::new(), u8_id, u8_id, &transcoder, None)?;
1124 assert_eq!(out, "Mapping is empty");
1125 Ok(())
1126 }
1127
1128 #[test]
1129 fn decode_mapping_impl_renders_single_entry() -> Result<()> {
1130 let transcoder = test_transcoder()?;
1131 let reg = transcoder.metadata().registry();
1132 let u8_id = find_primitive(reg, TypeDefPrimitive::U8);
1133
1134 let mut full_key = vec![0u8; 16];
1136 full_key.extend_from_slice(&1u32.to_le_bytes());
1137 full_key.push(4u8); let value_bytes = vec![8u8];
1139
1140 let out = decode_mapping_impl(
1141 vec![(full_key, Some(value_bytes))],
1142 u8_id,
1143 u8_id,
1144 &transcoder,
1145 None,
1146 )?;
1147
1148 assert_eq!(out, "{ 4 => 8 }");
1149 Ok(())
1150 }
1151
1152 #[test]
1153 fn decode_mapping_impl_filter_match_returns_value_only() -> Result<()> {
1154 let transcoder = test_transcoder()?;
1155 let reg = transcoder.metadata().registry();
1156 let u8_id = find_primitive(reg, TypeDefPrimitive::U8);
1157
1158 let mut full_key = vec![0u8; 16];
1159 full_key.extend_from_slice(&1u32.to_le_bytes());
1160 full_key.push(4u8);
1161 let value_bytes = vec![8u8];
1162
1163 let out = decode_mapping_impl(
1164 vec![(full_key, Some(value_bytes))],
1165 u8_id,
1166 u8_id,
1167 &transcoder,
1168 Some("4"),
1169 )?;
1170 assert_eq!(out, "8");
1171 Ok(())
1172 }
1173
1174 #[test]
1175 fn decode_mapping_impl_filter_no_match() -> Result<()> {
1176 let transcoder = test_transcoder()?;
1177 let reg = transcoder.metadata().registry();
1178 let u8_id = find_primitive(reg, TypeDefPrimitive::U8);
1179
1180 let mut full_key = vec![0u8; 16];
1181 full_key.extend_from_slice(&1u32.to_le_bytes());
1182 full_key.push(4u8);
1183 let value_bytes = vec![8u8];
1184
1185 let out = decode_mapping_impl(
1186 vec![(full_key, Some(value_bytes))],
1187 u8_id,
1188 u8_id,
1189 &transcoder,
1190 Some("5"),
1191 )?;
1192 assert_eq!(out, "No value found for the provided key");
1193 Ok(())
1194 }
1195}