1use crate::{
8 CustomMetadata, CustomValueMetadata, ExtrinsicMetadata, Metadata, PalletMetadata,
9 RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata, ViewFunctionMetadata,
10};
11use alloc::vec::Vec;
12use hashbrown::HashMap;
13use scale_info::{Field, PortableRegistry, TypeDef, TypeDefVariant, Variant, form::PortableForm};
14
15pub(crate) const HASH_LEN: usize = 32;
17pub type Hash = [u8; HASH_LEN];
18
19#[repr(u8)]
22enum TypeBeingHashed {
23 Composite,
24 Variant,
25 Sequence,
26 Array,
27 Tuple,
28 Primitive,
29 Compact,
30 BitSequence,
31}
32
33fn hash(data: &[u8]) -> Hash {
35 sp_crypto_hashing::twox_256(data)
36}
37
38fn xor(a: Hash, b: Hash) -> Hash {
41 let mut out = [0u8; HASH_LEN];
42 for (idx, (a, b)) in a.into_iter().zip(b).enumerate() {
43 out[idx] = a ^ b;
44 }
45 out
46}
47
48macro_rules! count_idents {
51 () => { 0 };
52 ($n:ident $($rest:ident)*) => { 1 + count_idents!($($rest)*) }
53}
54macro_rules! concat_and_hash_n {
55 ($name:ident($($arg:ident)+)) => {
56 fn $name($($arg: &Hash),+) -> Hash {
57 let mut out = [0u8; HASH_LEN * count_idents!($($arg)+)];
58 let mut start = 0;
59 $(
60 out[start..start+HASH_LEN].copy_from_slice(&$arg[..]);
61 #[allow(unused_assignments)]
62 { start += HASH_LEN; }
63 )+
64 hash(&out)
65 }
66 }
67}
68concat_and_hash_n!(concat_and_hash2(a b));
69concat_and_hash_n!(concat_and_hash3(a b c));
70concat_and_hash_n!(concat_and_hash4(a b c d));
71concat_and_hash_n!(concat_and_hash5(a b c d e));
72concat_and_hash_n!(concat_and_hash6(a b c d e f));
73
74fn get_field_hash(
76 registry: &PortableRegistry,
77 field: &Field<PortableForm>,
78 cache: &mut HashMap<u32, CachedHash>,
79) -> Hash {
80 let field_name_bytes = match &field.name {
81 Some(name) => hash(name.as_bytes()),
82 None => [0u8; HASH_LEN],
83 };
84
85 concat_and_hash2(
86 &field_name_bytes,
87 &get_type_hash_recurse(registry, field.ty.id, cache),
88 )
89}
90
91fn get_variant_hash(
93 registry: &PortableRegistry,
94 var: &Variant<PortableForm>,
95 cache: &mut HashMap<u32, CachedHash>,
96) -> Hash {
97 let variant_name_bytes = hash(var.name.as_bytes());
98 let variant_field_bytes = var.fields.iter().fold([0u8; HASH_LEN], |bytes, field| {
99 xor(bytes, get_field_hash(registry, field, cache))
102 });
103
104 concat_and_hash2(&variant_name_bytes, &variant_field_bytes)
105}
106
107fn get_type_def_variant_hash(
108 registry: &PortableRegistry,
109 variant: &TypeDefVariant<PortableForm>,
110 only_these_variants: Option<&[&str]>,
111 cache: &mut HashMap<u32, CachedHash>,
112) -> Hash {
113 let variant_id_bytes = [TypeBeingHashed::Variant as u8; HASH_LEN];
114 let variant_field_bytes = variant.variants.iter().fold([0u8; HASH_LEN], |bytes, var| {
115 let should_hash = only_these_variants
118 .as_ref()
119 .map(|only_these_variants| only_these_variants.contains(&var.name.as_str()))
120 .unwrap_or(true);
121 if should_hash {
122 xor(bytes, get_variant_hash(registry, var, cache))
123 } else {
124 bytes
125 }
126 });
127 concat_and_hash2(&variant_id_bytes, &variant_field_bytes)
128}
129
130fn get_type_def_hash(
132 registry: &PortableRegistry,
133 ty_def: &TypeDef<PortableForm>,
134 cache: &mut HashMap<u32, CachedHash>,
135) -> Hash {
136 match ty_def {
137 TypeDef::Composite(composite) => {
138 let composite_id_bytes = [TypeBeingHashed::Composite as u8; HASH_LEN];
139 let composite_field_bytes =
140 composite
141 .fields
142 .iter()
143 .fold([0u8; HASH_LEN], |bytes, field| {
144 xor(bytes, get_field_hash(registry, field, cache))
147 });
148 concat_and_hash2(&composite_id_bytes, &composite_field_bytes)
149 }
150 TypeDef::Variant(variant) => get_type_def_variant_hash(registry, variant, None, cache),
151 TypeDef::Sequence(sequence) => concat_and_hash2(
152 &[TypeBeingHashed::Sequence as u8; HASH_LEN],
153 &get_type_hash_recurse(registry, sequence.type_param.id, cache),
154 ),
155 TypeDef::Array(array) => {
156 let array_id_bytes = {
158 let mut a = [0u8; HASH_LEN];
159 a[0] = TypeBeingHashed::Array as u8;
160 a[1..5].copy_from_slice(&array.len.to_be_bytes());
161 a
162 };
163 concat_and_hash2(
164 &array_id_bytes,
165 &get_type_hash_recurse(registry, array.type_param.id, cache),
166 )
167 }
168 TypeDef::Tuple(tuple) => {
169 let mut bytes = hash(&[TypeBeingHashed::Tuple as u8]);
170 for field in &tuple.fields {
171 bytes = concat_and_hash2(&bytes, &get_type_hash_recurse(registry, field.id, cache));
172 }
173 bytes
174 }
175 TypeDef::Primitive(primitive) => {
176 hash(&[TypeBeingHashed::Primitive as u8, primitive.clone() as u8])
178 }
179 TypeDef::Compact(compact) => concat_and_hash2(
180 &[TypeBeingHashed::Compact as u8; HASH_LEN],
181 &get_type_hash_recurse(registry, compact.type_param.id, cache),
182 ),
183 TypeDef::BitSequence(bitseq) => concat_and_hash3(
184 &[TypeBeingHashed::BitSequence as u8; HASH_LEN],
185 &get_type_hash_recurse(registry, bitseq.bit_order_type.id, cache),
186 &get_type_hash_recurse(registry, bitseq.bit_store_type.id, cache),
187 ),
188 }
189}
190
191#[derive(Clone, Debug)]
193pub enum CachedHash {
194 Recursive,
196 Hash(Hash),
198}
199
200impl CachedHash {
201 fn hash(&self) -> Hash {
202 match &self {
203 CachedHash::Hash(hash) => *hash,
204 CachedHash::Recursive => [123; HASH_LEN], }
206 }
207}
208
209pub fn get_type_hash(registry: &PortableRegistry, id: u32) -> Hash {
218 get_type_hash_recurse(registry, id, &mut HashMap::new())
219}
220
221fn get_type_hash_recurse(
223 registry: &PortableRegistry,
224 id: u32,
225 cache: &mut HashMap<u32, CachedHash>,
226) -> Hash {
227 if let Some(cached_hash) = cache.get(&id) {
241 return cached_hash.hash();
242 }
243 cache.insert(id, CachedHash::Recursive);
244 let ty = registry
245 .resolve(id)
246 .expect("Type ID provided by the metadata is registered; qed");
247 let type_hash = get_type_def_hash(registry, &ty.type_def, cache);
248 cache.insert(id, CachedHash::Hash(type_hash));
249 type_hash
250}
251
252fn get_extrinsic_hash(registry: &PortableRegistry, extrinsic: &ExtrinsicMetadata) -> Hash {
254 let address_hash = get_type_hash(registry, extrinsic.address_ty);
256 let signature_hash = get_type_hash(registry, extrinsic.signature_ty);
258
259 if extrinsic.supported_versions.len() > 32 {
262 panic!("The metadata validation logic does not support more than 32 extrinsic versions.");
263 }
264 let supported_extrinsic_versions = {
265 let mut a = [0u8; 32];
266 a[0..extrinsic.supported_versions.len()].copy_from_slice(&extrinsic.supported_versions);
267 a
268 };
269
270 let mut bytes = concat_and_hash3(
271 &address_hash,
272 &signature_hash,
273 &supported_extrinsic_versions,
274 );
275
276 for signed_extension in extrinsic.transaction_extensions.iter() {
277 bytes = concat_and_hash4(
278 &bytes,
279 &hash(signed_extension.identifier.as_bytes()),
280 &get_type_hash(registry, signed_extension.extra_ty),
281 &get_type_hash(registry, signed_extension.additional_ty),
282 )
283 }
284
285 bytes
286}
287
288fn get_storage_entry_hash(registry: &PortableRegistry, entry: &StorageEntryMetadata) -> Hash {
290 let mut bytes = concat_and_hash3(
291 &hash(entry.name.as_bytes()),
292 &get_type_hash(registry, entry.info.value_id),
293 &hash(entry.info.default_value.as_deref().unwrap_or_default()),
294 );
295
296 for key in &*entry.info.keys {
297 bytes = concat_and_hash3(
298 &bytes,
299 &[key.hasher as u8; HASH_LEN],
300 &get_type_hash(registry, key.key_id),
301 )
302 }
303
304 bytes
305}
306
307fn get_custom_metadata_hash(custom_metadata: &CustomMetadata) -> Hash {
308 custom_metadata
309 .iter()
310 .fold([0u8; HASH_LEN], |bytes, custom_value| {
311 xor(bytes, get_custom_value_hash(&custom_value))
312 })
313}
314
315pub fn get_custom_value_hash(custom_value: &CustomValueMetadata) -> Hash {
320 let name_hash = hash(custom_value.name.as_bytes());
321 if custom_value.types.resolve(custom_value.type_id()).is_none() {
322 hash(&name_hash)
323 } else {
324 concat_and_hash2(
325 &name_hash,
326 &get_type_hash(custom_value.types, custom_value.type_id()),
327 )
328 }
329}
330
331pub fn get_storage_hash(pallet: &PalletMetadata, entry_name: &str) -> Option<Hash> {
333 let storage = pallet.storage()?;
334 let entry = storage.entry_by_name(entry_name)?;
335 let hash = get_storage_entry_hash(pallet.types, entry);
336 Some(hash)
337}
338
339pub fn get_constant_hash(pallet: &PalletMetadata, constant_name: &str) -> Option<Hash> {
341 let constant = pallet.constant_by_name(constant_name)?;
342
343 let bytes = get_type_hash(pallet.types, constant.ty);
345 Some(bytes)
346}
347
348pub fn get_call_hash(pallet: &PalletMetadata, call_name: &str) -> Option<Hash> {
350 let call_variant = pallet.call_variant_by_name(call_name)?;
351
352 let hash = get_variant_hash(pallet.types, call_variant, &mut HashMap::new());
354 Some(hash)
355}
356
357pub fn get_runtime_api_hash(runtime_api: &RuntimeApiMethodMetadata) -> Hash {
359 let registry = runtime_api.types;
360
361 let mut bytes = concat_and_hash2(
366 &hash(runtime_api.trait_name.as_bytes()),
367 &hash(runtime_api.name().as_bytes()),
368 );
369
370 for input in runtime_api.inputs() {
371 bytes = concat_and_hash3(
372 &bytes,
373 &hash(input.name.as_bytes()),
374 &get_type_hash(registry, input.id),
375 );
376 }
377
378 bytes = concat_and_hash2(&bytes, &get_type_hash(registry, runtime_api.output_ty()));
379
380 bytes
381}
382
383pub fn get_runtime_apis_hash(trait_metadata: RuntimeApiMetadata) -> Hash {
385 trait_metadata
388 .methods()
389 .fold([0u8; HASH_LEN], |bytes, method_metadata| {
390 xor(bytes, get_runtime_api_hash(&method_metadata))
395 })
396}
397
398pub fn get_view_function_hash(view_function: &ViewFunctionMetadata) -> Hash {
400 let registry = view_function.types;
401
402 let mut bytes = *view_function.query_id();
404
405 for input in view_function.inputs() {
408 bytes = concat_and_hash3(
409 &bytes,
410 &hash(input.name.as_bytes()),
411 &get_type_hash(registry, input.id),
412 );
413 }
414
415 bytes = concat_and_hash2(&bytes, &get_type_hash(registry, view_function.output_ty()));
416
417 bytes
418}
419
420fn get_pallet_view_functions_hash(pallet_metadata: &PalletMetadata) -> Hash {
422 pallet_metadata
425 .view_functions()
426 .fold([0u8; HASH_LEN], |bytes, method_metadata| {
427 xor(bytes, get_view_function_hash(&method_metadata))
432 })
433}
434
435pub fn get_pallet_hash(pallet: PalletMetadata) -> Hash {
437 let registry = pallet.types;
438
439 let call_bytes = match pallet.call_ty_id() {
440 Some(calls) => get_type_hash(registry, calls),
441 None => [0u8; HASH_LEN],
442 };
443 let event_bytes = match pallet.event_ty_id() {
444 Some(event) => get_type_hash(registry, event),
445 None => [0u8; HASH_LEN],
446 };
447 let error_bytes = match pallet.error_ty_id() {
448 Some(error) => get_type_hash(registry, error),
449 None => [0u8; HASH_LEN],
450 };
451 let constant_bytes = pallet.constants().fold([0u8; HASH_LEN], |bytes, constant| {
452 let constant_hash = concat_and_hash2(
455 &hash(constant.name.as_bytes()),
456 &get_type_hash(registry, constant.ty()),
457 );
458 xor(bytes, constant_hash)
459 });
460 let storage_bytes = match pallet.storage() {
461 Some(storage) => {
462 let prefix_hash = hash(storage.prefix().as_bytes());
463 let entries_hash = storage
464 .entries()
465 .iter()
466 .fold([0u8; HASH_LEN], |bytes, entry| {
467 xor(bytes, get_storage_entry_hash(registry, entry))
470 });
471 concat_and_hash2(&prefix_hash, &entries_hash)
472 }
473 None => [0u8; HASH_LEN],
474 };
475 let view_functions_bytes = get_pallet_view_functions_hash(&pallet);
476
477 concat_and_hash6(
479 &call_bytes,
480 &event_bytes,
481 &error_bytes,
482 &constant_bytes,
483 &storage_bytes,
484 &view_functions_bytes,
485 )
486}
487
488pub struct MetadataHasher<'a> {
491 metadata: &'a Metadata,
492 specific_pallets: Option<Vec<&'a str>>,
493 specific_runtime_apis: Option<Vec<&'a str>>,
494 include_custom_values: bool,
495}
496
497impl<'a> MetadataHasher<'a> {
498 pub(crate) fn new(metadata: &'a Metadata) -> Self {
500 Self {
501 metadata,
502 specific_pallets: None,
503 specific_runtime_apis: None,
504 include_custom_values: true,
505 }
506 }
507
508 pub fn only_these_pallets<S: AsRef<str>>(&mut self, specific_pallets: &'a [S]) -> &mut Self {
510 self.specific_pallets = Some(specific_pallets.iter().map(|n| n.as_ref()).collect());
511 self
512 }
513
514 pub fn only_these_runtime_apis<S: AsRef<str>>(
516 &mut self,
517 specific_runtime_apis: &'a [S],
518 ) -> &mut Self {
519 self.specific_runtime_apis =
520 Some(specific_runtime_apis.iter().map(|n| n.as_ref()).collect());
521 self
522 }
523
524 pub fn ignore_custom_values(&mut self) -> &mut Self {
526 self.include_custom_values = false;
527 self
528 }
529
530 pub fn hash(&self) -> Hash {
532 let metadata = self.metadata;
533
534 let pallet_hash = metadata.pallets().fold([0u8; HASH_LEN], |bytes, pallet| {
535 let should_hash = self
537 .specific_pallets
538 .as_ref()
539 .map(|specific_pallets| specific_pallets.contains(&pallet.name()))
540 .unwrap_or(true);
541 if should_hash {
544 xor(bytes, get_pallet_hash(pallet))
545 } else {
546 bytes
547 }
548 });
549
550 let apis_hash = metadata
551 .runtime_api_traits()
552 .fold([0u8; HASH_LEN], |bytes, api| {
553 let should_hash = self
555 .specific_runtime_apis
556 .as_ref()
557 .map(|specific_runtime_apis| specific_runtime_apis.contains(&api.name()))
558 .unwrap_or(true);
559 if should_hash {
562 xor(bytes, get_runtime_apis_hash(api))
563 } else {
564 bytes
565 }
566 });
567
568 let outer_enums_hash = concat_and_hash3(
569 &get_type_hash(&metadata.types, metadata.outer_enums.call_enum_ty),
570 &get_type_hash(&metadata.types, metadata.outer_enums.event_enum_ty),
571 &get_type_hash(&metadata.types, metadata.outer_enums.error_enum_ty),
572 );
573
574 let extrinsic_hash = get_extrinsic_hash(&metadata.types, &metadata.extrinsic);
575
576 let custom_values_hash = if self.include_custom_values {
577 get_custom_metadata_hash(&metadata.custom())
578 } else {
579 Default::default()
580 };
581
582 concat_and_hash5(
583 &pallet_hash,
584 &apis_hash,
585 &outer_enums_hash,
586 &extrinsic_hash,
587 &custom_values_hash,
588 )
589 }
590}
591
592#[cfg(test)]
593mod tests {
594 use super::*;
595 use bitvec::{order::Lsb0, vec::BitVec};
596 use frame_metadata::v15;
597 use scale_info::{Registry, meta_type};
598
599 #[allow(dead_code)]
601 #[derive(scale_info::TypeInfo)]
602 struct A {
603 pub b: Box<B>,
604 }
605
606 #[allow(dead_code)]
607 #[derive(scale_info::TypeInfo)]
608 struct B {
609 pub a: Box<A>,
610 }
611
612 #[allow(dead_code)]
614 #[derive(scale_info::TypeInfo)]
615 struct AccountId32(Hash);
617
618 #[allow(dead_code)]
619 #[derive(scale_info::TypeInfo)]
620 enum DigestItem {
622 PreRuntime(
623 [::core::primitive::u8; 4usize],
625 ::std::vec::Vec<::core::primitive::u8>,
627 ),
628 Other(::std::vec::Vec<::core::primitive::u8>),
629 RuntimeEnvironmentUpdated(((i8, i16), (u32, u64))),
631 Index(#[codec(compact)] ::core::primitive::u8),
633 BitSeq(BitVec<u8, Lsb0>),
635 }
636
637 #[allow(dead_code)]
638 #[derive(scale_info::TypeInfo)]
639 struct MetadataTestType {
641 recursive: A,
642 composite: AccountId32,
643 type_def: DigestItem,
644 }
645
646 #[allow(dead_code)]
647 #[derive(scale_info::TypeInfo)]
648 enum Call {
650 #[codec(index = 0)]
651 FillBlock { ratio: AccountId32 },
652 #[codec(index = 1)]
653 Remark { remark: DigestItem },
654 }
655
656 fn build_default_extrinsic() -> v15::ExtrinsicMetadata {
657 v15::ExtrinsicMetadata {
658 version: 0,
659 signed_extensions: vec![],
660 address_ty: meta_type::<()>(),
661 call_ty: meta_type::<()>(),
662 signature_ty: meta_type::<()>(),
663 extra_ty: meta_type::<()>(),
664 }
665 }
666
667 fn default_pallet() -> v15::PalletMetadata {
668 v15::PalletMetadata {
669 name: "Test",
670 storage: None,
671 calls: None,
672 event: None,
673 constants: vec![],
674 error: None,
675 index: 0,
676 docs: vec![],
677 }
678 }
679
680 fn build_default_pallets() -> Vec<v15::PalletMetadata> {
681 vec![
682 v15::PalletMetadata {
683 name: "First",
684 calls: Some(v15::PalletCallMetadata {
685 ty: meta_type::<MetadataTestType>(),
686 }),
687 ..default_pallet()
688 },
689 v15::PalletMetadata {
690 name: "Second",
691 index: 1,
692 calls: Some(v15::PalletCallMetadata {
693 ty: meta_type::<(DigestItem, AccountId32, A)>(),
694 }),
695 ..default_pallet()
696 },
697 ]
698 }
699
700 fn pallets_to_metadata(pallets: Vec<v15::PalletMetadata>) -> Metadata {
701 v15::RuntimeMetadataV15::new(
702 pallets,
703 build_default_extrinsic(),
704 meta_type::<()>(),
705 vec![],
706 v15::OuterEnums {
707 call_enum_ty: meta_type::<()>(),
708 event_enum_ty: meta_type::<()>(),
709 error_enum_ty: meta_type::<()>(),
710 },
711 v15::CustomMetadata {
712 map: Default::default(),
713 },
714 )
715 .try_into()
716 .expect("can build valid metadata")
717 }
718
719 #[test]
720 fn different_pallet_index() {
721 let pallets = build_default_pallets();
722 let mut pallets_swap = pallets.clone();
723
724 let metadata = pallets_to_metadata(pallets);
725
726 pallets_swap.swap(0, 1);
728 pallets_swap[0].index = 0;
729 pallets_swap[1].index = 1;
730 let metadata_swap = pallets_to_metadata(pallets_swap);
731
732 let hash = MetadataHasher::new(&metadata).hash();
733 let hash_swap = MetadataHasher::new(&metadata_swap).hash();
734
735 assert_eq!(hash, hash_swap);
737 }
738
739 #[test]
740 fn recursive_type() {
741 let mut pallet = default_pallet();
742 pallet.calls = Some(v15::PalletCallMetadata {
743 ty: meta_type::<A>(),
744 });
745 let metadata = pallets_to_metadata(vec![pallet]);
746
747 MetadataHasher::new(&metadata).hash();
749 }
750
751 #[test]
752 fn recursive_types_different_order() {
758 let mut pallets = build_default_pallets();
759 pallets[0].calls = Some(v15::PalletCallMetadata {
760 ty: meta_type::<A>(),
761 });
762 pallets[1].calls = Some(v15::PalletCallMetadata {
763 ty: meta_type::<B>(),
764 });
765 pallets[1].index = 1;
766 let mut pallets_swap = pallets.clone();
767 let metadata = pallets_to_metadata(pallets);
768
769 pallets_swap.swap(0, 1);
770 pallets_swap[0].index = 0;
771 pallets_swap[1].index = 1;
772 let metadata_swap = pallets_to_metadata(pallets_swap);
773
774 let hash = MetadataHasher::new(&metadata).hash();
775 let hash_swap = MetadataHasher::new(&metadata_swap).hash();
776
777 assert_eq!(hash, hash_swap);
779 }
780
781 #[allow(dead_code)]
782 #[derive(scale_info::TypeInfo)]
783 struct Aba {
784 ab: (A, B),
785 other: A,
786 }
787
788 #[allow(dead_code)]
789 #[derive(scale_info::TypeInfo)]
790 struct Abb {
791 ab: (A, B),
792 other: B,
793 }
794
795 #[test]
796 fn do_not_reuse_visited_type_ids() {
798 let metadata_hash_with_type = |ty| {
799 let mut pallets = build_default_pallets();
800 pallets[0].calls = Some(v15::PalletCallMetadata { ty });
801 let metadata = pallets_to_metadata(pallets);
802 MetadataHasher::new(&metadata).hash()
803 };
804
805 let aba_hash = metadata_hash_with_type(meta_type::<Aba>());
806 let abb_hash = metadata_hash_with_type(meta_type::<Abb>());
807
808 assert_ne!(aba_hash, abb_hash);
809 }
810
811 #[test]
812 fn hash_cache_gets_filled_with_correct_hashes() {
813 let mut registry = Registry::new();
814 let a_type_id = registry.register_type(&meta_type::<A>()).id;
815 let b_type_id = registry.register_type(&meta_type::<B>()).id;
816 let registry: PortableRegistry = registry.into();
817
818 let mut cache = HashMap::new();
819
820 let a_hash = get_type_hash_recurse(®istry, a_type_id, &mut cache);
821 let a_hash2 = get_type_hash_recurse(®istry, a_type_id, &mut cache);
822 let b_hash = get_type_hash_recurse(®istry, b_type_id, &mut cache);
823
824 let CachedHash::Hash(a_cache_hash) = cache[&a_type_id] else {
825 panic!()
826 };
827 let CachedHash::Hash(b_cache_hash) = cache[&b_type_id] else {
828 panic!()
829 };
830
831 assert_eq!(a_hash, a_cache_hash);
832 assert_eq!(b_hash, b_cache_hash);
833
834 assert_eq!(a_hash, a_hash2);
835 assert_ne!(a_hash, b_hash);
836 }
837
838 #[test]
839 #[allow(clippy::redundant_clone)]
841 fn pallet_hash_correctness() {
842 let compare_pallets_hash = |lhs: &v15::PalletMetadata, rhs: &v15::PalletMetadata| {
843 let metadata = pallets_to_metadata(vec![lhs.clone()]);
844 let hash = MetadataHasher::new(&metadata).hash();
845
846 let metadata = pallets_to_metadata(vec![rhs.clone()]);
847 let new_hash = MetadataHasher::new(&metadata).hash();
848
849 assert_ne!(hash, new_hash);
850 };
851
852 let mut pallet = default_pallet();
854 let pallet_lhs = pallet.clone();
855 pallet.storage = Some(v15::PalletStorageMetadata {
856 prefix: "Storage",
857 entries: vec![v15::StorageEntryMetadata {
858 name: "BlockWeight",
859 modifier: v15::StorageEntryModifier::Default,
860 ty: v15::StorageEntryType::Plain(meta_type::<u8>()),
861 default: vec![],
862 docs: vec![],
863 }],
864 });
865 compare_pallets_hash(&pallet_lhs, &pallet);
866
867 let pallet_lhs = pallet.clone();
868 pallet.calls = Some(v15::PalletCallMetadata {
877 ty: meta_type::<Call>(),
878 });
879 compare_pallets_hash(&pallet_lhs, &pallet);
880
881 let pallet_lhs = pallet.clone();
882 pallet.event = Some(v15::PalletEventMetadata {
884 ty: meta_type::<Call>(),
885 });
886 compare_pallets_hash(&pallet_lhs, &pallet);
887
888 let pallet_lhs = pallet.clone();
889 pallet.constants = vec![v15::PalletConstantMetadata {
890 name: "BlockHashCount",
891 ty: meta_type::<u64>(),
892 value: vec![96u8, 0, 0, 0],
893 docs: vec![],
894 }];
895 compare_pallets_hash(&pallet_lhs, &pallet);
896
897 let pallet_lhs = pallet.clone();
898 pallet.error = Some(v15::PalletErrorMetadata {
899 ty: meta_type::<MetadataTestType>(),
900 });
901 compare_pallets_hash(&pallet_lhs, &pallet);
902 }
903
904 #[test]
905 fn metadata_per_pallet_hash_correctness() {
906 let pallets = build_default_pallets();
907
908 let metadata_one = pallets_to_metadata(vec![pallets[0].clone()]);
910 let metadata_both = pallets_to_metadata(pallets);
912
913 let hash = MetadataHasher::new(&metadata_one)
915 .only_these_pallets(&["First", "Second"])
916 .hash();
917 let hash_rhs = MetadataHasher::new(&metadata_one)
918 .only_these_pallets(&["First"])
919 .hash();
920 assert_eq!(hash, hash_rhs, "hashing should ignore non-existent pallets");
921
922 let hash_second = MetadataHasher::new(&metadata_both)
924 .only_these_pallets(&["First"])
925 .hash();
926 assert_eq!(
927 hash_second, hash,
928 "hashing one pallet should ignore the others"
929 );
930
931 let hash_second = MetadataHasher::new(&metadata_both)
933 .only_these_pallets(&["First", "Second"])
934 .hash();
935 assert_ne!(
936 hash_second, hash,
937 "hashing both pallets should produce a different result from hashing just one pallet"
938 );
939 }
940
941 #[test]
942 fn field_semantic_changes() {
943 let to_hash = |meta_ty| {
946 let pallet = v15::PalletMetadata {
947 calls: Some(v15::PalletCallMetadata { ty: meta_ty }),
948 ..default_pallet()
949 };
950 let metadata = pallets_to_metadata(vec![pallet]);
951 MetadataHasher::new(&metadata).hash()
952 };
953
954 #[allow(dead_code)]
955 #[derive(scale_info::TypeInfo)]
956 enum EnumA1 {
957 First { hi: u8, bye: String },
958 Second(u32),
959 Third,
960 }
961 #[allow(dead_code)]
962 #[derive(scale_info::TypeInfo)]
963 enum EnumA2 {
964 Second(u32),
965 Third,
966 First { bye: String, hi: u8 },
967 }
968
969 assert_eq!(
972 to_hash(meta_type::<EnumA1>()),
973 to_hash(meta_type::<EnumA2>())
974 );
975
976 #[allow(dead_code)]
977 #[derive(scale_info::TypeInfo)]
978 struct StructB1 {
979 hello: bool,
980 another: [u8; 32],
981 }
982 #[allow(dead_code)]
983 #[derive(scale_info::TypeInfo)]
984 struct StructB2 {
985 another: [u8; 32],
986 hello: bool,
987 }
988
989 assert_eq!(
992 to_hash(meta_type::<StructB1>()),
993 to_hash(meta_type::<StructB2>())
994 );
995
996 #[allow(dead_code)]
997 #[derive(scale_info::TypeInfo)]
998 enum EnumC1 {
999 First(u8),
1000 }
1001 #[allow(dead_code)]
1002 #[derive(scale_info::TypeInfo)]
1003 enum EnumC2 {
1004 Second(u8),
1005 }
1006
1007 assert_ne!(
1010 to_hash(meta_type::<EnumC1>()),
1011 to_hash(meta_type::<EnumC2>())
1012 );
1013
1014 #[allow(dead_code)]
1015 #[derive(scale_info::TypeInfo)]
1016 enum EnumD1 {
1017 First { a: u8 },
1018 }
1019 #[allow(dead_code)]
1020 #[derive(scale_info::TypeInfo)]
1021 enum EnumD2 {
1022 First { b: u8 },
1023 }
1024
1025 assert_ne!(
1028 to_hash(meta_type::<EnumD1>()),
1029 to_hash(meta_type::<EnumD2>())
1030 );
1031
1032 #[allow(dead_code)]
1033 #[derive(scale_info::TypeInfo)]
1034 struct StructE1 {
1035 a: u32,
1036 }
1037 #[allow(dead_code)]
1038 #[derive(scale_info::TypeInfo)]
1039 struct StructE2 {
1040 b: u32,
1041 }
1042
1043 assert_ne!(
1046 to_hash(meta_type::<StructE1>()),
1047 to_hash(meta_type::<StructE2>())
1048 );
1049 }
1050
1051 use frame_metadata::v15::{
1052 PalletEventMetadata, PalletStorageMetadata, StorageEntryMetadata, StorageEntryModifier,
1053 };
1054
1055 fn metadata_with_pallet_events() -> v15::RuntimeMetadataV15 {
1056 #[allow(dead_code)]
1057 #[derive(scale_info::TypeInfo)]
1058 struct FirstEvent {
1059 s: String,
1060 }
1061
1062 #[allow(dead_code)]
1063 #[derive(scale_info::TypeInfo)]
1064 struct SecondEvent {
1065 n: u8,
1066 }
1067
1068 #[allow(dead_code)]
1069 #[derive(scale_info::TypeInfo)]
1070 enum Events {
1071 First(FirstEvent),
1072 Second(SecondEvent),
1073 }
1074
1075 #[allow(dead_code)]
1076 #[derive(scale_info::TypeInfo)]
1077 enum Errors {
1078 First(DispatchError),
1079 Second(DispatchError),
1080 }
1081
1082 #[allow(dead_code)]
1083 #[derive(scale_info::TypeInfo)]
1084 enum Calls {
1085 First(u8),
1086 Second(u8),
1087 }
1088
1089 #[allow(dead_code)]
1090 enum DispatchError {
1091 A,
1092 B,
1093 C,
1094 }
1095
1096 impl scale_info::TypeInfo for DispatchError {
1097 type Identity = DispatchError;
1098
1099 fn type_info() -> scale_info::Type {
1100 scale_info::Type {
1101 path: scale_info::Path {
1102 segments: vec!["sp_runtime", "DispatchError"],
1103 },
1104 type_params: vec![],
1105 type_def: TypeDef::Variant(TypeDefVariant { variants: vec![] }),
1106 docs: vec![],
1107 }
1108 }
1109 }
1110
1111 let pallets = vec![
1112 v15::PalletMetadata {
1113 name: "First",
1114 index: 0,
1115 calls: Some(v15::PalletCallMetadata {
1116 ty: meta_type::<u8>(),
1117 }),
1118 storage: Some(PalletStorageMetadata {
1119 prefix: "___",
1120 entries: vec![StorageEntryMetadata {
1121 name: "Hello",
1122 modifier: StorageEntryModifier::Optional,
1123 ty: frame_metadata::v14::StorageEntryType::Plain(meta_type::<Vec<Events>>()),
1127 default: vec![],
1128 docs: vec![],
1129 }],
1130 }),
1131 event: Some(PalletEventMetadata {
1132 ty: meta_type::<FirstEvent>(),
1133 }),
1134 constants: vec![],
1135 error: None,
1136 docs: vec![],
1137 },
1138 v15::PalletMetadata {
1139 name: "Second",
1140 index: 1,
1141 calls: Some(v15::PalletCallMetadata {
1142 ty: meta_type::<u64>(),
1143 }),
1144 storage: None,
1145 event: Some(PalletEventMetadata {
1146 ty: meta_type::<SecondEvent>(),
1147 }),
1148 constants: vec![],
1149 error: None,
1150 docs: vec![],
1151 },
1152 ];
1153
1154 v15::RuntimeMetadataV15::new(
1155 pallets,
1156 build_default_extrinsic(),
1157 meta_type::<()>(),
1158 vec![],
1159 v15::OuterEnums {
1160 call_enum_ty: meta_type::<Calls>(),
1161 event_enum_ty: meta_type::<Events>(),
1162 error_enum_ty: meta_type::<Errors>(),
1163 },
1164 v15::CustomMetadata {
1165 map: Default::default(),
1166 },
1167 )
1168 }
1169
1170 #[test]
1171 fn hash_comparison_trimmed_metadata() {
1172 use subxt_utils_stripmetadata::StripMetadata;
1173
1174 let metadata = metadata_with_pallet_events();
1176 let trimmed_metadata = {
1177 let mut m = metadata.clone();
1178 m.strip_metadata(|e| e == "First", |_| true);
1179 m
1180 };
1181
1182 let metadata = Metadata::try_from(metadata).unwrap();
1184 let trimmed_metadata = Metadata::try_from(trimmed_metadata).unwrap();
1185
1186 let hash = MetadataHasher::new(&metadata)
1188 .only_these_pallets(&["First"])
1189 .hash();
1190 let hash_trimmed = MetadataHasher::new(&trimmed_metadata).hash();
1191
1192 assert_eq!(hash, hash_trimmed);
1193 }
1194}