1use crate::{
8 CustomMetadata, CustomValueMetadata, ExtrinsicMetadata, Metadata, PalletMetadata,
9 RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata, StorageEntryType,
10};
11use alloc::vec::Vec;
12use hashbrown::HashMap;
13use outer_enum_hashes::OuterEnumHashes;
14use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, TypeDefVariant, Variant};
15
16pub mod outer_enum_hashes;
17
18pub(crate) const HASH_LEN: usize = 32;
20pub type Hash = [u8; HASH_LEN];
21
22#[repr(u8)]
25enum TypeBeingHashed {
26 Composite,
27 Variant,
28 Sequence,
29 Array,
30 Tuple,
31 Primitive,
32 Compact,
33 BitSequence,
34}
35
36fn hash(data: &[u8]) -> Hash {
38 sp_crypto_hashing::twox_256(data)
39}
40
41fn xor(a: Hash, b: Hash) -> Hash {
44 let mut out = [0u8; HASH_LEN];
45 for (idx, (a, b)) in a.into_iter().zip(b).enumerate() {
46 out[idx] = a ^ b;
47 }
48 out
49}
50
51macro_rules! count_idents {
54 () => { 0 };
55 ($n:ident $($rest:ident)*) => { 1 + count_idents!($($rest)*) }
56}
57macro_rules! concat_and_hash_n {
58 ($name:ident($($arg:ident)+)) => {
59 fn $name($($arg: &Hash),+) -> Hash {
60 let mut out = [0u8; HASH_LEN * count_idents!($($arg)+)];
61 let mut start = 0;
62 $(
63 out[start..start+HASH_LEN].copy_from_slice(&$arg[..]);
64 #[allow(unused_assignments)]
65 { start += HASH_LEN; }
66 )+
67 hash(&out)
68 }
69 }
70}
71concat_and_hash_n!(concat_and_hash2(a b));
72concat_and_hash_n!(concat_and_hash3(a b c));
73concat_and_hash_n!(concat_and_hash4(a b c d));
74concat_and_hash_n!(concat_and_hash5(a b c d e));
75concat_and_hash_n!(concat_and_hash6(a b c d e f));
76
77fn get_field_hash(
79 registry: &PortableRegistry,
80 field: &Field<PortableForm>,
81 cache: &mut HashMap<u32, CachedHash>,
82 outer_enum_hashes: &OuterEnumHashes,
83) -> Hash {
84 let field_name_bytes = match &field.name {
85 Some(name) => hash(name.as_bytes()),
86 None => [0u8; HASH_LEN],
87 };
88
89 concat_and_hash2(
90 &field_name_bytes,
91 &get_type_hash_recurse(registry, field.ty.id, cache, outer_enum_hashes),
92 )
93}
94
95fn get_variant_hash(
97 registry: &PortableRegistry,
98 var: &Variant<PortableForm>,
99 cache: &mut HashMap<u32, CachedHash>,
100 outer_enum_hashes: &OuterEnumHashes,
101) -> Hash {
102 let variant_name_bytes = hash(var.name.as_bytes());
103 let variant_field_bytes = var.fields.iter().fold([0u8; HASH_LEN], |bytes, field| {
104 xor(
107 bytes,
108 get_field_hash(registry, field, cache, outer_enum_hashes),
109 )
110 });
111
112 concat_and_hash2(&variant_name_bytes, &variant_field_bytes)
113}
114
115fn get_type_def_variant_hash(
116 registry: &PortableRegistry,
117 variant: &TypeDefVariant<PortableForm>,
118 only_these_variants: Option<&[&str]>,
119 cache: &mut HashMap<u32, CachedHash>,
120 outer_enum_hashes: &OuterEnumHashes,
121) -> Hash {
122 let variant_id_bytes = [TypeBeingHashed::Variant as u8; HASH_LEN];
123 let variant_field_bytes = variant.variants.iter().fold([0u8; HASH_LEN], |bytes, var| {
124 let should_hash = only_these_variants
127 .as_ref()
128 .map(|only_these_variants| only_these_variants.contains(&var.name.as_str()))
129 .unwrap_or(true);
130 if should_hash {
131 xor(
132 bytes,
133 get_variant_hash(registry, var, cache, outer_enum_hashes),
134 )
135 } else {
136 bytes
137 }
138 });
139 concat_and_hash2(&variant_id_bytes, &variant_field_bytes)
140}
141
142fn get_type_def_hash(
144 registry: &PortableRegistry,
145 ty_def: &TypeDef<PortableForm>,
146 cache: &mut HashMap<u32, CachedHash>,
147 outer_enum_hashes: &OuterEnumHashes,
148) -> Hash {
149 match ty_def {
150 TypeDef::Composite(composite) => {
151 let composite_id_bytes = [TypeBeingHashed::Composite as u8; HASH_LEN];
152 let composite_field_bytes =
153 composite
154 .fields
155 .iter()
156 .fold([0u8; HASH_LEN], |bytes, field| {
157 xor(
160 bytes,
161 get_field_hash(registry, field, cache, outer_enum_hashes),
162 )
163 });
164 concat_and_hash2(&composite_id_bytes, &composite_field_bytes)
165 }
166 TypeDef::Variant(variant) => {
167 get_type_def_variant_hash(registry, variant, None, cache, outer_enum_hashes)
168 }
169 TypeDef::Sequence(sequence) => concat_and_hash2(
170 &[TypeBeingHashed::Sequence as u8; HASH_LEN],
171 &get_type_hash_recurse(registry, sequence.type_param.id, cache, outer_enum_hashes),
172 ),
173 TypeDef::Array(array) => {
174 let array_id_bytes = {
176 let mut a = [0u8; HASH_LEN];
177 a[0] = TypeBeingHashed::Array as u8;
178 a[1..5].copy_from_slice(&array.len.to_be_bytes());
179 a
180 };
181 concat_and_hash2(
182 &array_id_bytes,
183 &get_type_hash_recurse(registry, array.type_param.id, cache, outer_enum_hashes),
184 )
185 }
186 TypeDef::Tuple(tuple) => {
187 let mut bytes = hash(&[TypeBeingHashed::Tuple as u8]);
188 for field in &tuple.fields {
189 bytes = concat_and_hash2(
190 &bytes,
191 &get_type_hash_recurse(registry, field.id, cache, outer_enum_hashes),
192 );
193 }
194 bytes
195 }
196 TypeDef::Primitive(primitive) => {
197 hash(&[TypeBeingHashed::Primitive as u8, primitive.clone() as u8])
199 }
200 TypeDef::Compact(compact) => concat_and_hash2(
201 &[TypeBeingHashed::Compact as u8; HASH_LEN],
202 &get_type_hash_recurse(registry, compact.type_param.id, cache, outer_enum_hashes),
203 ),
204 TypeDef::BitSequence(bitseq) => concat_and_hash3(
205 &[TypeBeingHashed::BitSequence as u8; HASH_LEN],
206 &get_type_hash_recurse(registry, bitseq.bit_order_type.id, cache, outer_enum_hashes),
207 &get_type_hash_recurse(registry, bitseq.bit_store_type.id, cache, outer_enum_hashes),
208 ),
209 }
210}
211
212#[derive(Clone, Debug)]
214pub enum CachedHash {
215 Recursive,
217 Hash(Hash),
219}
220
221impl CachedHash {
222 fn hash(&self) -> Hash {
223 match &self {
224 CachedHash::Hash(hash) => *hash,
225 CachedHash::Recursive => [123; HASH_LEN], }
227 }
228}
229
230pub fn get_type_hash(
239 registry: &PortableRegistry,
240 id: u32,
241 outer_enum_hashes: &OuterEnumHashes,
242) -> Hash {
243 get_type_hash_recurse(registry, id, &mut HashMap::new(), outer_enum_hashes)
244}
245
246fn get_type_hash_recurse(
248 registry: &PortableRegistry,
249 id: u32,
250 cache: &mut HashMap<u32, CachedHash>,
251 outer_enum_hashes: &OuterEnumHashes,
252) -> Hash {
253 if let Some(hash) = outer_enum_hashes.resolve(id) {
255 return hash;
256 }
257
258 if let Some(cached_hash) = cache.get(&id) {
272 return cached_hash.hash();
273 }
274 cache.insert(id, CachedHash::Recursive);
275 let ty = registry
276 .resolve(id)
277 .expect("Type ID provided by the metadata is registered; qed");
278 let type_hash = get_type_def_hash(registry, &ty.type_def, cache, outer_enum_hashes);
279 cache.insert(id, CachedHash::Hash(type_hash));
280 type_hash
281}
282
283fn get_extrinsic_hash(
285 registry: &PortableRegistry,
286 extrinsic: &ExtrinsicMetadata,
287 outer_enum_hashes: &OuterEnumHashes,
288) -> Hash {
289 let address_hash = get_type_hash(registry, extrinsic.address_ty, outer_enum_hashes);
291 let signature_hash = get_type_hash(registry, extrinsic.signature_ty, outer_enum_hashes);
293 let extra_hash = get_type_hash(registry, extrinsic.extra_ty, outer_enum_hashes);
294
295 if extrinsic.supported_versions.len() > 32 {
298 panic!("The metadata validation logic does not support more than 32 extrinsic versions.");
299 }
300 let supported_extrinsic_versions = {
301 let mut a = [0u8; 32];
302 a[0..extrinsic.supported_versions.len()].copy_from_slice(&extrinsic.supported_versions);
303 a
304 };
305
306 let mut bytes = concat_and_hash4(
307 &address_hash,
308 &signature_hash,
309 &extra_hash,
310 &supported_extrinsic_versions,
311 );
312
313 for signed_extension in extrinsic.transaction_extensions.iter() {
314 bytes = concat_and_hash4(
315 &bytes,
316 &hash(signed_extension.identifier.as_bytes()),
317 &get_type_hash(registry, signed_extension.extra_ty, outer_enum_hashes),
318 &get_type_hash(registry, signed_extension.additional_ty, outer_enum_hashes),
319 )
320 }
321
322 bytes
323}
324
325fn get_storage_entry_hash(
327 registry: &PortableRegistry,
328 entry: &StorageEntryMetadata,
329 outer_enum_hashes: &OuterEnumHashes,
330) -> Hash {
331 let mut bytes = concat_and_hash3(
332 &hash(entry.name.as_bytes()),
333 &[entry.modifier as u8; HASH_LEN],
335 &hash(&entry.default),
336 );
337
338 match &entry.entry_type {
339 StorageEntryType::Plain(ty) => {
340 concat_and_hash2(&bytes, &get_type_hash(registry, *ty, outer_enum_hashes))
341 }
342 StorageEntryType::Map {
343 hashers,
344 key_ty,
345 value_ty,
346 } => {
347 for hasher in hashers {
348 bytes = concat_and_hash2(&bytes, &[*hasher as u8; HASH_LEN]);
350 }
351 concat_and_hash3(
352 &bytes,
353 &get_type_hash(registry, *key_ty, outer_enum_hashes),
354 &get_type_hash(registry, *value_ty, outer_enum_hashes),
355 )
356 }
357 }
358}
359
360fn get_runtime_method_hash(
362 registry: &PortableRegistry,
363 trait_name: &str,
364 method_metadata: &RuntimeApiMethodMetadata,
365 outer_enum_hashes: &OuterEnumHashes,
366) -> Hash {
367 let mut bytes = concat_and_hash2(
372 &hash(trait_name.as_bytes()),
373 &hash(method_metadata.name.as_bytes()),
374 );
375
376 for input in &method_metadata.inputs {
377 bytes = concat_and_hash3(
378 &bytes,
379 &hash(input.name.as_bytes()),
380 &get_type_hash(registry, input.ty, outer_enum_hashes),
381 );
382 }
383
384 bytes = concat_and_hash2(
385 &bytes,
386 &get_type_hash(registry, method_metadata.output_ty, outer_enum_hashes),
387 );
388
389 bytes
390}
391
392pub fn get_runtime_trait_hash(
394 trait_metadata: RuntimeApiMetadata,
395 outer_enum_hashes: &OuterEnumHashes,
396) -> Hash {
397 let trait_name = &*trait_metadata.inner.name;
398 let method_bytes = trait_metadata
399 .methods()
400 .fold([0u8; HASH_LEN], |bytes, method_metadata| {
401 xor(
406 bytes,
407 get_runtime_method_hash(
408 trait_metadata.types,
409 trait_name,
410 method_metadata,
411 outer_enum_hashes,
412 ),
413 )
414 });
415
416 concat_and_hash2(&hash(trait_name.as_bytes()), &method_bytes)
417}
418
419fn get_custom_metadata_hash(
420 custom_metadata: &CustomMetadata,
421 outer_enum_hashes: &OuterEnumHashes,
422) -> Hash {
423 custom_metadata
424 .iter()
425 .fold([0u8; HASH_LEN], |bytes, custom_value| {
426 xor(
427 bytes,
428 get_custom_value_hash(&custom_value, outer_enum_hashes),
429 )
430 })
431}
432
433pub fn get_custom_value_hash(
438 custom_value: &CustomValueMetadata,
439 outer_enum_hashes: &OuterEnumHashes,
440) -> Hash {
441 let name_hash = hash(custom_value.name.as_bytes());
442 if custom_value.types.resolve(custom_value.type_id()).is_none() {
443 hash(&name_hash)
444 } else {
445 concat_and_hash2(
446 &name_hash,
447 &get_type_hash(
448 custom_value.types,
449 custom_value.type_id(),
450 outer_enum_hashes,
451 ),
452 )
453 }
454}
455
456pub fn get_storage_hash(pallet: &PalletMetadata, entry_name: &str) -> Option<Hash> {
458 let storage = pallet.storage()?;
459 let entry = storage.entry_by_name(entry_name)?;
460 let hash = get_storage_entry_hash(pallet.types, entry, &OuterEnumHashes::empty());
461 Some(hash)
462}
463
464pub fn get_constant_hash(pallet: &PalletMetadata, constant_name: &str) -> Option<Hash> {
466 let constant = pallet.constant_by_name(constant_name)?;
467
468 let bytes = get_type_hash(pallet.types, constant.ty, &OuterEnumHashes::empty());
470 Some(bytes)
471}
472
473pub fn get_call_hash(pallet: &PalletMetadata, call_name: &str) -> Option<Hash> {
475 let call_variant = pallet.call_variant_by_name(call_name)?;
476
477 let hash = get_variant_hash(
479 pallet.types,
480 call_variant,
481 &mut HashMap::new(),
482 &OuterEnumHashes::empty(),
483 );
484 Some(hash)
485}
486
487pub fn get_runtime_api_hash(runtime_apis: &RuntimeApiMetadata, method_name: &str) -> Option<Hash> {
489 let trait_name = &*runtime_apis.inner.name;
490 let method_metadata = runtime_apis.method_by_name(method_name)?;
491
492 Some(get_runtime_method_hash(
493 runtime_apis.types,
494 trait_name,
495 method_metadata,
496 &OuterEnumHashes::empty(),
497 ))
498}
499
500pub fn get_pallet_hash(pallet: PalletMetadata, outer_enum_hashes: &OuterEnumHashes) -> Hash {
502 let registry = pallet.types;
503
504 let call_bytes = match pallet.call_ty_id() {
505 Some(calls) => get_type_hash(registry, calls, outer_enum_hashes),
506 None => [0u8; HASH_LEN],
507 };
508 let event_bytes = match pallet.event_ty_id() {
509 Some(event) => get_type_hash(registry, event, outer_enum_hashes),
510 None => [0u8; HASH_LEN],
511 };
512 let error_bytes = match pallet.error_ty_id() {
513 Some(error) => get_type_hash(registry, error, outer_enum_hashes),
514 None => [0u8; HASH_LEN],
515 };
516 let constant_bytes = pallet.constants().fold([0u8; HASH_LEN], |bytes, constant| {
517 let constant_hash = concat_and_hash2(
520 &hash(constant.name.as_bytes()),
521 &get_type_hash(registry, constant.ty(), outer_enum_hashes),
522 );
523 xor(bytes, constant_hash)
524 });
525 let storage_bytes = match pallet.storage() {
526 Some(storage) => {
527 let prefix_hash = hash(storage.prefix().as_bytes());
528 let entries_hash = storage
529 .entries()
530 .iter()
531 .fold([0u8; HASH_LEN], |bytes, entry| {
532 xor(
535 bytes,
536 get_storage_entry_hash(registry, entry, outer_enum_hashes),
537 )
538 });
539 concat_and_hash2(&prefix_hash, &entries_hash)
540 }
541 None => [0u8; HASH_LEN],
542 };
543
544 concat_and_hash5(
546 &call_bytes,
547 &event_bytes,
548 &error_bytes,
549 &constant_bytes,
550 &storage_bytes,
551 )
552}
553
554pub struct MetadataHasher<'a> {
557 metadata: &'a Metadata,
558 specific_pallets: Option<Vec<&'a str>>,
559 specific_runtime_apis: Option<Vec<&'a str>>,
560 include_custom_values: bool,
561}
562
563impl<'a> MetadataHasher<'a> {
564 pub(crate) fn new(metadata: &'a Metadata) -> Self {
566 Self {
567 metadata,
568 specific_pallets: None,
569 specific_runtime_apis: None,
570 include_custom_values: true,
571 }
572 }
573
574 pub fn only_these_pallets<S: AsRef<str>>(&mut self, specific_pallets: &'a [S]) -> &mut Self {
576 self.specific_pallets = Some(specific_pallets.iter().map(|n| n.as_ref()).collect());
577 self
578 }
579
580 pub fn only_these_runtime_apis<S: AsRef<str>>(
582 &mut self,
583 specific_runtime_apis: &'a [S],
584 ) -> &mut Self {
585 self.specific_runtime_apis =
586 Some(specific_runtime_apis.iter().map(|n| n.as_ref()).collect());
587 self
588 }
589
590 pub fn ignore_custom_values(&mut self) -> &mut Self {
592 self.include_custom_values = false;
593 self
594 }
595
596 pub fn hash(&self) -> Hash {
598 let metadata = self.metadata;
599
600 let outer_enum_hashes = OuterEnumHashes::new(
603 metadata,
604 self.specific_pallets.as_deref(),
605 self.specific_runtime_apis.as_deref(),
606 );
607
608 let pallet_hash = metadata.pallets().fold([0u8; HASH_LEN], |bytes, pallet| {
609 let should_hash = self
611 .specific_pallets
612 .as_ref()
613 .map(|specific_pallets| specific_pallets.contains(&pallet.name()))
614 .unwrap_or(true);
615 if should_hash {
618 xor(bytes, get_pallet_hash(pallet, &outer_enum_hashes))
619 } else {
620 bytes
621 }
622 });
623
624 let apis_hash = metadata
625 .runtime_api_traits()
626 .fold([0u8; HASH_LEN], |bytes, api| {
627 let should_hash = self
629 .specific_runtime_apis
630 .as_ref()
631 .map(|specific_runtime_apis| specific_runtime_apis.contains(&api.name()))
632 .unwrap_or(true);
633 if should_hash {
636 xor(bytes, get_runtime_trait_hash(api, &outer_enum_hashes))
637 } else {
638 bytes
639 }
640 });
641
642 let extrinsic_hash =
643 get_extrinsic_hash(&metadata.types, &metadata.extrinsic, &outer_enum_hashes);
644 let runtime_hash =
645 get_type_hash(&metadata.types, metadata.runtime_ty(), &outer_enum_hashes);
646 let custom_values_hash = self
647 .include_custom_values
648 .then(|| get_custom_metadata_hash(&metadata.custom(), &outer_enum_hashes))
649 .unwrap_or_default();
650
651 concat_and_hash6(
652 &pallet_hash,
653 &apis_hash,
654 &extrinsic_hash,
655 &runtime_hash,
656 &outer_enum_hashes.combined_hash(),
657 &custom_values_hash,
658 )
659 }
660}
661
662#[cfg(test)]
663mod tests {
664 use super::*;
665 use bitvec::{order::Lsb0, vec::BitVec};
666 use frame_metadata::v15;
667 use scale_info::{meta_type, Registry};
668
669 #[allow(dead_code)]
671 #[derive(scale_info::TypeInfo)]
672 struct A {
673 pub b: Box<B>,
674 }
675
676 #[allow(dead_code)]
677 #[derive(scale_info::TypeInfo)]
678 struct B {
679 pub a: Box<A>,
680 }
681
682 #[allow(dead_code)]
684 #[derive(scale_info::TypeInfo)]
685 struct AccountId32(Hash);
687
688 #[allow(dead_code)]
689 #[derive(scale_info::TypeInfo)]
690 enum DigestItem {
692 PreRuntime(
693 [::core::primitive::u8; 4usize],
695 ::std::vec::Vec<::core::primitive::u8>,
697 ),
698 Other(::std::vec::Vec<::core::primitive::u8>),
699 RuntimeEnvironmentUpdated(((i8, i16), (u32, u64))),
701 Index(#[codec(compact)] ::core::primitive::u8),
703 BitSeq(BitVec<u8, Lsb0>),
705 }
706
707 #[allow(dead_code)]
708 #[derive(scale_info::TypeInfo)]
709 struct MetadataTestType {
711 recursive: A,
712 composite: AccountId32,
713 type_def: DigestItem,
714 }
715
716 #[allow(dead_code)]
717 #[derive(scale_info::TypeInfo)]
718 enum Call {
720 #[codec(index = 0)]
721 FillBlock { ratio: AccountId32 },
722 #[codec(index = 1)]
723 Remark { remark: DigestItem },
724 }
725
726 fn build_default_extrinsic() -> v15::ExtrinsicMetadata {
727 v15::ExtrinsicMetadata {
728 version: 0,
729 signed_extensions: vec![],
730 address_ty: meta_type::<()>(),
731 call_ty: meta_type::<()>(),
732 signature_ty: meta_type::<()>(),
733 extra_ty: meta_type::<()>(),
734 }
735 }
736
737 fn default_pallet() -> v15::PalletMetadata {
738 v15::PalletMetadata {
739 name: "Test",
740 storage: None,
741 calls: None,
742 event: None,
743 constants: vec![],
744 error: None,
745 index: 0,
746 docs: vec![],
747 }
748 }
749
750 fn build_default_pallets() -> Vec<v15::PalletMetadata> {
751 vec![
752 v15::PalletMetadata {
753 name: "First",
754 calls: Some(v15::PalletCallMetadata {
755 ty: meta_type::<MetadataTestType>(),
756 }),
757 ..default_pallet()
758 },
759 v15::PalletMetadata {
760 name: "Second",
761 index: 1,
762 calls: Some(v15::PalletCallMetadata {
763 ty: meta_type::<(DigestItem, AccountId32, A)>(),
764 }),
765 ..default_pallet()
766 },
767 ]
768 }
769
770 fn pallets_to_metadata(pallets: Vec<v15::PalletMetadata>) -> Metadata {
771 v15::RuntimeMetadataV15::new(
772 pallets,
773 build_default_extrinsic(),
774 meta_type::<()>(),
775 vec![],
776 v15::OuterEnums {
777 call_enum_ty: meta_type::<()>(),
778 event_enum_ty: meta_type::<()>(),
779 error_enum_ty: meta_type::<()>(),
780 },
781 v15::CustomMetadata {
782 map: Default::default(),
783 },
784 )
785 .try_into()
786 .expect("can build valid metadata")
787 }
788
789 #[test]
790 fn different_pallet_index() {
791 let pallets = build_default_pallets();
792 let mut pallets_swap = pallets.clone();
793
794 let metadata = pallets_to_metadata(pallets);
795
796 pallets_swap.swap(0, 1);
798 pallets_swap[0].index = 0;
799 pallets_swap[1].index = 1;
800 let metadata_swap = pallets_to_metadata(pallets_swap);
801
802 let hash = MetadataHasher::new(&metadata).hash();
803 let hash_swap = MetadataHasher::new(&metadata_swap).hash();
804
805 assert_eq!(hash, hash_swap);
807 }
808
809 #[test]
810 fn recursive_type() {
811 let mut pallet = default_pallet();
812 pallet.calls = Some(v15::PalletCallMetadata {
813 ty: meta_type::<A>(),
814 });
815 let metadata = pallets_to_metadata(vec![pallet]);
816
817 MetadataHasher::new(&metadata).hash();
819 }
820
821 #[test]
822 fn recursive_types_different_order() {
828 let mut pallets = build_default_pallets();
829 pallets[0].calls = Some(v15::PalletCallMetadata {
830 ty: meta_type::<A>(),
831 });
832 pallets[1].calls = Some(v15::PalletCallMetadata {
833 ty: meta_type::<B>(),
834 });
835 pallets[1].index = 1;
836 let mut pallets_swap = pallets.clone();
837 let metadata = pallets_to_metadata(pallets);
838
839 pallets_swap.swap(0, 1);
840 pallets_swap[0].index = 0;
841 pallets_swap[1].index = 1;
842 let metadata_swap = pallets_to_metadata(pallets_swap);
843
844 let hash = MetadataHasher::new(&metadata).hash();
845 let hash_swap = MetadataHasher::new(&metadata_swap).hash();
846
847 assert_eq!(hash, hash_swap);
849 }
850
851 #[allow(dead_code)]
852 #[derive(scale_info::TypeInfo)]
853 struct Aba {
854 ab: (A, B),
855 other: A,
856 }
857
858 #[allow(dead_code)]
859 #[derive(scale_info::TypeInfo)]
860 struct Abb {
861 ab: (A, B),
862 other: B,
863 }
864
865 #[test]
866 fn do_not_reuse_visited_type_ids() {
868 let metadata_hash_with_type = |ty| {
869 let mut pallets = build_default_pallets();
870 pallets[0].calls = Some(v15::PalletCallMetadata { ty });
871 let metadata = pallets_to_metadata(pallets);
872 MetadataHasher::new(&metadata).hash()
873 };
874
875 let aba_hash = metadata_hash_with_type(meta_type::<Aba>());
876 let abb_hash = metadata_hash_with_type(meta_type::<Abb>());
877
878 assert_ne!(aba_hash, abb_hash);
879 }
880
881 #[test]
882 fn hash_cache_gets_filled_with_correct_hashes() {
883 let mut registry = Registry::new();
884 let a_type_id = registry.register_type(&meta_type::<A>()).id;
885 let b_type_id = registry.register_type(&meta_type::<B>()).id;
886 let registry: PortableRegistry = registry.into();
887
888 let mut cache = HashMap::new();
889 let ignored_enums = &OuterEnumHashes::empty();
890
891 let a_hash = get_type_hash_recurse(®istry, a_type_id, &mut cache, ignored_enums);
892 let a_hash2 = get_type_hash_recurse(®istry, a_type_id, &mut cache, ignored_enums);
893 let b_hash = get_type_hash_recurse(®istry, b_type_id, &mut cache, ignored_enums);
894
895 let CachedHash::Hash(a_cache_hash) = cache[&a_type_id] else {
896 panic!()
897 };
898 let CachedHash::Hash(b_cache_hash) = cache[&b_type_id] else {
899 panic!()
900 };
901
902 assert_eq!(a_hash, a_cache_hash);
903 assert_eq!(b_hash, b_cache_hash);
904
905 assert_eq!(a_hash, a_hash2);
906 assert_ne!(a_hash, b_hash);
907 }
908
909 #[test]
910 #[allow(clippy::redundant_clone)]
912 fn pallet_hash_correctness() {
913 let compare_pallets_hash = |lhs: &v15::PalletMetadata, rhs: &v15::PalletMetadata| {
914 let metadata = pallets_to_metadata(vec![lhs.clone()]);
915 let hash = MetadataHasher::new(&metadata).hash();
916
917 let metadata = pallets_to_metadata(vec![rhs.clone()]);
918 let new_hash = MetadataHasher::new(&metadata).hash();
919
920 assert_ne!(hash, new_hash);
921 };
922
923 let mut pallet = default_pallet();
925 let pallet_lhs = pallet.clone();
926 pallet.storage = Some(v15::PalletStorageMetadata {
927 prefix: "Storage",
928 entries: vec![v15::StorageEntryMetadata {
929 name: "BlockWeight",
930 modifier: v15::StorageEntryModifier::Default,
931 ty: v15::StorageEntryType::Plain(meta_type::<u8>()),
932 default: vec![],
933 docs: vec![],
934 }],
935 });
936 compare_pallets_hash(&pallet_lhs, &pallet);
937
938 let pallet_lhs = pallet.clone();
939 pallet.calls = Some(v15::PalletCallMetadata {
948 ty: meta_type::<Call>(),
949 });
950 compare_pallets_hash(&pallet_lhs, &pallet);
951
952 let pallet_lhs = pallet.clone();
953 pallet.event = Some(v15::PalletEventMetadata {
955 ty: meta_type::<Call>(),
956 });
957 compare_pallets_hash(&pallet_lhs, &pallet);
958
959 let pallet_lhs = pallet.clone();
960 pallet.constants = vec![v15::PalletConstantMetadata {
961 name: "BlockHashCount",
962 ty: meta_type::<u64>(),
963 value: vec![96u8, 0, 0, 0],
964 docs: vec![],
965 }];
966 compare_pallets_hash(&pallet_lhs, &pallet);
967
968 let pallet_lhs = pallet.clone();
969 pallet.error = Some(v15::PalletErrorMetadata {
970 ty: meta_type::<MetadataTestType>(),
971 });
972 compare_pallets_hash(&pallet_lhs, &pallet);
973 }
974
975 #[test]
976 fn metadata_per_pallet_hash_correctness() {
977 let pallets = build_default_pallets();
978
979 let metadata_one = pallets_to_metadata(vec![pallets[0].clone()]);
981 let metadata_both = pallets_to_metadata(pallets);
983
984 let hash = MetadataHasher::new(&metadata_one)
986 .only_these_pallets(&["First", "Second"])
987 .hash();
988 let hash_rhs = MetadataHasher::new(&metadata_one)
989 .only_these_pallets(&["First"])
990 .hash();
991 assert_eq!(hash, hash_rhs, "hashing should ignore non-existant pallets");
992
993 let hash_second = MetadataHasher::new(&metadata_both)
995 .only_these_pallets(&["First"])
996 .hash();
997 assert_eq!(
998 hash_second, hash,
999 "hashing one pallet should ignore the others"
1000 );
1001
1002 let hash_second = MetadataHasher::new(&metadata_both)
1004 .only_these_pallets(&["First", "Second"])
1005 .hash();
1006 assert_ne!(
1007 hash_second, hash,
1008 "hashing both pallets should produce a different result from hashing just one pallet"
1009 );
1010 }
1011
1012 #[test]
1013 fn field_semantic_changes() {
1014 let to_hash = |meta_ty| {
1017 let pallet = v15::PalletMetadata {
1018 calls: Some(v15::PalletCallMetadata { ty: meta_ty }),
1019 ..default_pallet()
1020 };
1021 let metadata = pallets_to_metadata(vec![pallet]);
1022 MetadataHasher::new(&metadata).hash()
1023 };
1024
1025 #[allow(dead_code)]
1026 #[derive(scale_info::TypeInfo)]
1027 enum EnumA1 {
1028 First { hi: u8, bye: String },
1029 Second(u32),
1030 Third,
1031 }
1032 #[allow(dead_code)]
1033 #[derive(scale_info::TypeInfo)]
1034 enum EnumA2 {
1035 Second(u32),
1036 Third,
1037 First { bye: String, hi: u8 },
1038 }
1039
1040 assert_eq!(
1043 to_hash(meta_type::<EnumA1>()),
1044 to_hash(meta_type::<EnumA2>())
1045 );
1046
1047 #[allow(dead_code)]
1048 #[derive(scale_info::TypeInfo)]
1049 struct StructB1 {
1050 hello: bool,
1051 another: [u8; 32],
1052 }
1053 #[allow(dead_code)]
1054 #[derive(scale_info::TypeInfo)]
1055 struct StructB2 {
1056 another: [u8; 32],
1057 hello: bool,
1058 }
1059
1060 assert_eq!(
1063 to_hash(meta_type::<StructB1>()),
1064 to_hash(meta_type::<StructB2>())
1065 );
1066
1067 #[allow(dead_code)]
1068 #[derive(scale_info::TypeInfo)]
1069 enum EnumC1 {
1070 First(u8),
1071 }
1072 #[allow(dead_code)]
1073 #[derive(scale_info::TypeInfo)]
1074 enum EnumC2 {
1075 Second(u8),
1076 }
1077
1078 assert_ne!(
1081 to_hash(meta_type::<EnumC1>()),
1082 to_hash(meta_type::<EnumC2>())
1083 );
1084
1085 #[allow(dead_code)]
1086 #[derive(scale_info::TypeInfo)]
1087 enum EnumD1 {
1088 First { a: u8 },
1089 }
1090 #[allow(dead_code)]
1091 #[derive(scale_info::TypeInfo)]
1092 enum EnumD2 {
1093 First { b: u8 },
1094 }
1095
1096 assert_ne!(
1099 to_hash(meta_type::<EnumD1>()),
1100 to_hash(meta_type::<EnumD2>())
1101 );
1102
1103 #[allow(dead_code)]
1104 #[derive(scale_info::TypeInfo)]
1105 struct StructE1 {
1106 a: u32,
1107 }
1108 #[allow(dead_code)]
1109 #[derive(scale_info::TypeInfo)]
1110 struct StructE2 {
1111 b: u32,
1112 }
1113
1114 assert_ne!(
1117 to_hash(meta_type::<StructE1>()),
1118 to_hash(meta_type::<StructE2>())
1119 );
1120 }
1121
1122 use frame_metadata::v15::{
1123 PalletEventMetadata, PalletStorageMetadata, StorageEntryMetadata, StorageEntryModifier,
1124 };
1125
1126 fn metadata_with_pallet_events() -> Metadata {
1127 #[allow(dead_code)]
1128 #[derive(scale_info::TypeInfo)]
1129 struct FirstEvent {
1130 s: String,
1131 }
1132
1133 #[allow(dead_code)]
1134 #[derive(scale_info::TypeInfo)]
1135 struct SecondEvent {
1136 n: u8,
1137 }
1138
1139 #[allow(dead_code)]
1140 #[derive(scale_info::TypeInfo)]
1141 enum Events {
1142 First(FirstEvent),
1143 Second(SecondEvent),
1144 }
1145
1146 #[allow(dead_code)]
1147 #[derive(scale_info::TypeInfo)]
1148 enum Errors {
1149 First(DispatchError),
1150 Second(DispatchError),
1151 }
1152
1153 #[allow(dead_code)]
1154 #[derive(scale_info::TypeInfo)]
1155 enum Calls {
1156 First(u8),
1157 Second(u8),
1158 }
1159
1160 #[allow(dead_code)]
1161 enum DispatchError {
1162 A,
1163 B,
1164 C,
1165 }
1166
1167 impl scale_info::TypeInfo for DispatchError {
1168 type Identity = DispatchError;
1169
1170 fn type_info() -> scale_info::Type {
1171 scale_info::Type {
1172 path: scale_info::Path {
1173 segments: vec!["sp_runtime", "DispatchError"],
1174 },
1175 type_params: vec![],
1176 type_def: TypeDef::Variant(TypeDefVariant { variants: vec![] }),
1177 docs: vec![],
1178 }
1179 }
1180 }
1181
1182 let pallets = vec![
1183 v15::PalletMetadata {
1184 name: "First",
1185 index: 0,
1186 calls: Some(v15::PalletCallMetadata {
1187 ty: meta_type::<u8>(),
1188 }),
1189 storage: Some(PalletStorageMetadata {
1190 prefix: "___",
1191 entries: vec![StorageEntryMetadata {
1192 name: "Hello",
1193 modifier: StorageEntryModifier::Optional,
1194 ty: frame_metadata::v14::StorageEntryType::Plain(meta_type::<Vec<Events>>()),
1198 default: vec![],
1199 docs: vec![],
1200 }],
1201 }),
1202 event: Some(PalletEventMetadata {
1203 ty: meta_type::<FirstEvent>(),
1204 }),
1205 constants: vec![],
1206 error: None,
1207 docs: vec![],
1208 },
1209 v15::PalletMetadata {
1210 name: "Second",
1211 index: 1,
1212 calls: Some(v15::PalletCallMetadata {
1213 ty: meta_type::<u64>(),
1214 }),
1215 storage: None,
1216 event: Some(PalletEventMetadata {
1217 ty: meta_type::<SecondEvent>(),
1218 }),
1219 constants: vec![],
1220 error: None,
1221 docs: vec![],
1222 },
1223 ];
1224
1225 v15::RuntimeMetadataV15::new(
1226 pallets,
1227 build_default_extrinsic(),
1228 meta_type::<()>(),
1229 vec![],
1230 v15::OuterEnums {
1231 call_enum_ty: meta_type::<Calls>(),
1232 event_enum_ty: meta_type::<Events>(),
1233 error_enum_ty: meta_type::<Errors>(),
1234 },
1235 v15::CustomMetadata {
1236 map: Default::default(),
1237 },
1238 )
1239 .try_into()
1240 .expect("can build valid metadata")
1241 }
1242
1243 #[test]
1244 fn hash_comparison_trimmed_metadata() {
1245 let metadata = metadata_with_pallet_events();
1247 let trimmed_metadata = {
1248 let mut m = metadata.clone();
1249 m.retain(|e| e == "First", |_| true);
1250 m
1251 };
1252
1253 let hash = MetadataHasher::new(&metadata)
1255 .only_these_pallets(&["First"])
1256 .hash();
1257 let hash_trimmed = MetadataHasher::new(&trimmed_metadata).hash();
1258
1259 assert_eq!(hash, hash_trimmed);
1260 }
1261}