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 let mut bytes = concat_and_hash4(
296 &address_hash,
297 &signature_hash,
298 &extra_hash,
299 &[extrinsic.version; 32],
300 );
301
302 for signed_extension in extrinsic.signed_extensions.iter() {
303 bytes = concat_and_hash4(
304 &bytes,
305 &hash(signed_extension.identifier.as_bytes()),
306 &get_type_hash(registry, signed_extension.extra_ty, outer_enum_hashes),
307 &get_type_hash(registry, signed_extension.additional_ty, outer_enum_hashes),
308 )
309 }
310
311 bytes
312}
313
314fn get_storage_entry_hash(
316 registry: &PortableRegistry,
317 entry: &StorageEntryMetadata,
318 outer_enum_hashes: &OuterEnumHashes,
319) -> Hash {
320 let mut bytes = concat_and_hash3(
321 &hash(entry.name.as_bytes()),
322 &[entry.modifier as u8; HASH_LEN],
324 &hash(&entry.default),
325 );
326
327 match &entry.entry_type {
328 StorageEntryType::Plain(ty) => {
329 concat_and_hash2(&bytes, &get_type_hash(registry, *ty, outer_enum_hashes))
330 }
331 StorageEntryType::Map {
332 hashers,
333 key_ty,
334 value_ty,
335 } => {
336 for hasher in hashers {
337 bytes = concat_and_hash2(&bytes, &[*hasher as u8; HASH_LEN]);
339 }
340 concat_and_hash3(
341 &bytes,
342 &get_type_hash(registry, *key_ty, outer_enum_hashes),
343 &get_type_hash(registry, *value_ty, outer_enum_hashes),
344 )
345 }
346 }
347}
348
349fn get_runtime_method_hash(
351 registry: &PortableRegistry,
352 trait_name: &str,
353 method_metadata: &RuntimeApiMethodMetadata,
354 outer_enum_hashes: &OuterEnumHashes,
355) -> Hash {
356 let mut bytes = concat_and_hash2(
361 &hash(trait_name.as_bytes()),
362 &hash(method_metadata.name.as_bytes()),
363 );
364
365 for input in &method_metadata.inputs {
366 bytes = concat_and_hash3(
367 &bytes,
368 &hash(input.name.as_bytes()),
369 &get_type_hash(registry, input.ty, outer_enum_hashes),
370 );
371 }
372
373 bytes = concat_and_hash2(
374 &bytes,
375 &get_type_hash(registry, method_metadata.output_ty, outer_enum_hashes),
376 );
377
378 bytes
379}
380
381pub fn get_runtime_trait_hash(
383 trait_metadata: RuntimeApiMetadata,
384 outer_enum_hashes: &OuterEnumHashes,
385) -> Hash {
386 let trait_name = &*trait_metadata.inner.name;
387 let method_bytes = trait_metadata
388 .methods()
389 .fold([0u8; HASH_LEN], |bytes, method_metadata| {
390 xor(
395 bytes,
396 get_runtime_method_hash(
397 trait_metadata.types,
398 trait_name,
399 method_metadata,
400 outer_enum_hashes,
401 ),
402 )
403 });
404
405 concat_and_hash2(&hash(trait_name.as_bytes()), &method_bytes)
406}
407
408fn get_custom_metadata_hash(
409 custom_metadata: &CustomMetadata,
410 outer_enum_hashes: &OuterEnumHashes,
411) -> Hash {
412 custom_metadata
413 .iter()
414 .fold([0u8; HASH_LEN], |bytes, custom_value| {
415 xor(
416 bytes,
417 get_custom_value_hash(&custom_value, outer_enum_hashes),
418 )
419 })
420}
421
422pub fn get_custom_value_hash(
427 custom_value: &CustomValueMetadata,
428 outer_enum_hashes: &OuterEnumHashes,
429) -> Hash {
430 let name_hash = hash(custom_value.name.as_bytes());
431 if custom_value.types.resolve(custom_value.type_id()).is_none() {
432 hash(&name_hash)
433 } else {
434 concat_and_hash2(
435 &name_hash,
436 &get_type_hash(
437 custom_value.types,
438 custom_value.type_id(),
439 outer_enum_hashes,
440 ),
441 )
442 }
443}
444
445pub fn get_storage_hash(pallet: &PalletMetadata, entry_name: &str) -> Option<Hash> {
447 let storage = pallet.storage()?;
448 let entry = storage.entry_by_name(entry_name)?;
449 let hash = get_storage_entry_hash(pallet.types, entry, &OuterEnumHashes::empty());
450 Some(hash)
451}
452
453pub fn get_constant_hash(pallet: &PalletMetadata, constant_name: &str) -> Option<Hash> {
455 let constant = pallet.constant_by_name(constant_name)?;
456
457 let bytes = get_type_hash(pallet.types, constant.ty, &OuterEnumHashes::empty());
459 Some(bytes)
460}
461
462pub fn get_call_hash(pallet: &PalletMetadata, call_name: &str) -> Option<Hash> {
464 let call_variant = pallet.call_variant_by_name(call_name)?;
465
466 let hash = get_variant_hash(
468 pallet.types,
469 call_variant,
470 &mut HashMap::new(),
471 &OuterEnumHashes::empty(),
472 );
473 Some(hash)
474}
475
476pub fn get_runtime_api_hash(runtime_apis: &RuntimeApiMetadata, method_name: &str) -> Option<Hash> {
478 let trait_name = &*runtime_apis.inner.name;
479 let method_metadata = runtime_apis.method_by_name(method_name)?;
480
481 Some(get_runtime_method_hash(
482 runtime_apis.types,
483 trait_name,
484 method_metadata,
485 &OuterEnumHashes::empty(),
486 ))
487}
488
489pub fn get_pallet_hash(pallet: PalletMetadata, outer_enum_hashes: &OuterEnumHashes) -> Hash {
491 let registry = pallet.types;
492
493 let call_bytes = match pallet.call_ty_id() {
494 Some(calls) => get_type_hash(registry, calls, outer_enum_hashes),
495 None => [0u8; HASH_LEN],
496 };
497 let event_bytes = match pallet.event_ty_id() {
498 Some(event) => get_type_hash(registry, event, outer_enum_hashes),
499 None => [0u8; HASH_LEN],
500 };
501 let error_bytes = match pallet.error_ty_id() {
502 Some(error) => get_type_hash(registry, error, outer_enum_hashes),
503 None => [0u8; HASH_LEN],
504 };
505 let constant_bytes = pallet.constants().fold([0u8; HASH_LEN], |bytes, constant| {
506 let constant_hash = concat_and_hash2(
509 &hash(constant.name.as_bytes()),
510 &get_type_hash(registry, constant.ty(), outer_enum_hashes),
511 );
512 xor(bytes, constant_hash)
513 });
514 let storage_bytes = match pallet.storage() {
515 Some(storage) => {
516 let prefix_hash = hash(storage.prefix().as_bytes());
517 let entries_hash = storage
518 .entries()
519 .iter()
520 .fold([0u8; HASH_LEN], |bytes, entry| {
521 xor(
524 bytes,
525 get_storage_entry_hash(registry, entry, outer_enum_hashes),
526 )
527 });
528 concat_and_hash2(&prefix_hash, &entries_hash)
529 }
530 None => [0u8; HASH_LEN],
531 };
532
533 concat_and_hash5(
535 &call_bytes,
536 &event_bytes,
537 &error_bytes,
538 &constant_bytes,
539 &storage_bytes,
540 )
541}
542
543pub struct MetadataHasher<'a> {
546 metadata: &'a Metadata,
547 specific_pallets: Option<Vec<&'a str>>,
548 specific_runtime_apis: Option<Vec<&'a str>>,
549 include_custom_values: bool,
550}
551
552impl<'a> MetadataHasher<'a> {
553 pub(crate) fn new(metadata: &'a Metadata) -> Self {
555 Self {
556 metadata,
557 specific_pallets: None,
558 specific_runtime_apis: None,
559 include_custom_values: true,
560 }
561 }
562
563 pub fn only_these_pallets<S: AsRef<str>>(&mut self, specific_pallets: &'a [S]) -> &mut Self {
565 self.specific_pallets = Some(specific_pallets.iter().map(|n| n.as_ref()).collect());
566 self
567 }
568
569 pub fn only_these_runtime_apis<S: AsRef<str>>(
571 &mut self,
572 specific_runtime_apis: &'a [S],
573 ) -> &mut Self {
574 self.specific_runtime_apis =
575 Some(specific_runtime_apis.iter().map(|n| n.as_ref()).collect());
576 self
577 }
578
579 pub fn ignore_custom_values(&mut self) -> &mut Self {
581 self.include_custom_values = false;
582 self
583 }
584
585 pub fn hash(&self) -> Hash {
587 let metadata = self.metadata;
588
589 let outer_enum_hashes = OuterEnumHashes::new(
592 metadata,
593 self.specific_pallets.as_deref(),
594 self.specific_runtime_apis.as_deref(),
595 );
596
597 let pallet_hash = metadata.pallets().fold([0u8; HASH_LEN], |bytes, pallet| {
598 let should_hash = self
600 .specific_pallets
601 .as_ref()
602 .map(|specific_pallets| specific_pallets.contains(&pallet.name()))
603 .unwrap_or(true);
604 if should_hash {
607 xor(bytes, get_pallet_hash(pallet, &outer_enum_hashes))
608 } else {
609 bytes
610 }
611 });
612
613 let apis_hash = metadata
614 .runtime_api_traits()
615 .fold([0u8; HASH_LEN], |bytes, api| {
616 let should_hash = self
618 .specific_runtime_apis
619 .as_ref()
620 .map(|specific_runtime_apis| specific_runtime_apis.contains(&api.name()))
621 .unwrap_or(true);
622 if should_hash {
625 xor(bytes, get_runtime_trait_hash(api, &outer_enum_hashes))
626 } else {
627 bytes
628 }
629 });
630
631 let extrinsic_hash =
632 get_extrinsic_hash(&metadata.types, &metadata.extrinsic, &outer_enum_hashes);
633 let runtime_hash =
634 get_type_hash(&metadata.types, metadata.runtime_ty(), &outer_enum_hashes);
635 let custom_values_hash = self
636 .include_custom_values
637 .then(|| get_custom_metadata_hash(&metadata.custom(), &outer_enum_hashes))
638 .unwrap_or_default();
639
640 concat_and_hash6(
641 &pallet_hash,
642 &apis_hash,
643 &extrinsic_hash,
644 &runtime_hash,
645 &outer_enum_hashes.combined_hash(),
646 &custom_values_hash,
647 )
648 }
649}
650
651#[cfg(test)]
652mod tests {
653 use super::*;
654 use bitvec::{order::Lsb0, vec::BitVec};
655 use frame_metadata::v15;
656 use scale_info::{meta_type, Registry};
657
658 #[allow(dead_code)]
660 #[derive(scale_info::TypeInfo)]
661 struct A {
662 pub b: Box<B>,
663 }
664
665 #[allow(dead_code)]
666 #[derive(scale_info::TypeInfo)]
667 struct B {
668 pub a: Box<A>,
669 }
670
671 #[allow(dead_code)]
673 #[derive(scale_info::TypeInfo)]
674 struct AccountId32(Hash);
676
677 #[allow(dead_code)]
678 #[derive(scale_info::TypeInfo)]
679 enum DigestItem {
681 PreRuntime(
682 [::core::primitive::u8; 4usize],
684 ::std::vec::Vec<::core::primitive::u8>,
686 ),
687 Other(::std::vec::Vec<::core::primitive::u8>),
688 RuntimeEnvironmentUpdated(((i8, i16), (u32, u64))),
690 Index(#[codec(compact)] ::core::primitive::u8),
692 BitSeq(BitVec<u8, Lsb0>),
694 }
695
696 #[allow(dead_code)]
697 #[derive(scale_info::TypeInfo)]
698 struct MetadataTestType {
700 recursive: A,
701 composite: AccountId32,
702 type_def: DigestItem,
703 }
704
705 #[allow(dead_code)]
706 #[derive(scale_info::TypeInfo)]
707 enum Call {
709 #[codec(index = 0)]
710 FillBlock { ratio: AccountId32 },
711 #[codec(index = 1)]
712 Remark { remark: DigestItem },
713 }
714
715 fn build_default_extrinsic() -> v15::ExtrinsicMetadata {
716 v15::ExtrinsicMetadata {
717 version: 0,
718 signed_extensions: vec![],
719 address_ty: meta_type::<()>(),
720 call_ty: meta_type::<()>(),
721 signature_ty: meta_type::<()>(),
722 extra_ty: meta_type::<()>(),
723 }
724 }
725
726 fn default_pallet() -> v15::PalletMetadata {
727 v15::PalletMetadata {
728 name: "Test",
729 storage: None,
730 calls: None,
731 event: None,
732 constants: vec![],
733 error: None,
734 index: 0,
735 docs: vec![],
736 }
737 }
738
739 fn build_default_pallets() -> Vec<v15::PalletMetadata> {
740 vec![
741 v15::PalletMetadata {
742 name: "First",
743 calls: Some(v15::PalletCallMetadata {
744 ty: meta_type::<MetadataTestType>(),
745 }),
746 ..default_pallet()
747 },
748 v15::PalletMetadata {
749 name: "Second",
750 index: 1,
751 calls: Some(v15::PalletCallMetadata {
752 ty: meta_type::<(DigestItem, AccountId32, A)>(),
753 }),
754 ..default_pallet()
755 },
756 ]
757 }
758
759 fn pallets_to_metadata(pallets: Vec<v15::PalletMetadata>) -> Metadata {
760 v15::RuntimeMetadataV15::new(
761 pallets,
762 build_default_extrinsic(),
763 meta_type::<()>(),
764 vec![],
765 v15::OuterEnums {
766 call_enum_ty: meta_type::<()>(),
767 event_enum_ty: meta_type::<()>(),
768 error_enum_ty: meta_type::<()>(),
769 },
770 v15::CustomMetadata {
771 map: Default::default(),
772 },
773 )
774 .try_into()
775 .expect("can build valid metadata")
776 }
777
778 #[test]
779 fn different_pallet_index() {
780 let pallets = build_default_pallets();
781 let mut pallets_swap = pallets.clone();
782
783 let metadata = pallets_to_metadata(pallets);
784
785 pallets_swap.swap(0, 1);
787 pallets_swap[0].index = 0;
788 pallets_swap[1].index = 1;
789 let metadata_swap = pallets_to_metadata(pallets_swap);
790
791 let hash = MetadataHasher::new(&metadata).hash();
792 let hash_swap = MetadataHasher::new(&metadata_swap).hash();
793
794 assert_eq!(hash, hash_swap);
796 }
797
798 #[test]
799 fn recursive_type() {
800 let mut pallet = default_pallet();
801 pallet.calls = Some(v15::PalletCallMetadata {
802 ty: meta_type::<A>(),
803 });
804 let metadata = pallets_to_metadata(vec![pallet]);
805
806 MetadataHasher::new(&metadata).hash();
808 }
809
810 #[test]
811 fn recursive_types_different_order() {
817 let mut pallets = build_default_pallets();
818 pallets[0].calls = Some(v15::PalletCallMetadata {
819 ty: meta_type::<A>(),
820 });
821 pallets[1].calls = Some(v15::PalletCallMetadata {
822 ty: meta_type::<B>(),
823 });
824 pallets[1].index = 1;
825 let mut pallets_swap = pallets.clone();
826 let metadata = pallets_to_metadata(pallets);
827
828 pallets_swap.swap(0, 1);
829 pallets_swap[0].index = 0;
830 pallets_swap[1].index = 1;
831 let metadata_swap = pallets_to_metadata(pallets_swap);
832
833 let hash = MetadataHasher::new(&metadata).hash();
834 let hash_swap = MetadataHasher::new(&metadata_swap).hash();
835
836 assert_eq!(hash, hash_swap);
838 }
839
840 #[allow(dead_code)]
841 #[derive(scale_info::TypeInfo)]
842 struct Aba {
843 ab: (A, B),
844 other: A,
845 }
846
847 #[allow(dead_code)]
848 #[derive(scale_info::TypeInfo)]
849 struct Abb {
850 ab: (A, B),
851 other: B,
852 }
853
854 #[test]
855 fn do_not_reuse_visited_type_ids() {
857 let metadata_hash_with_type = |ty| {
858 let mut pallets = build_default_pallets();
859 pallets[0].calls = Some(v15::PalletCallMetadata { ty });
860 let metadata = pallets_to_metadata(pallets);
861 MetadataHasher::new(&metadata).hash()
862 };
863
864 let aba_hash = metadata_hash_with_type(meta_type::<Aba>());
865 let abb_hash = metadata_hash_with_type(meta_type::<Abb>());
866
867 assert_ne!(aba_hash, abb_hash);
868 }
869
870 #[test]
871 fn hash_cache_gets_filled_with_correct_hashes() {
872 let mut registry = Registry::new();
873 let a_type_id = registry.register_type(&meta_type::<A>()).id;
874 let b_type_id = registry.register_type(&meta_type::<B>()).id;
875 let registry: PortableRegistry = registry.into();
876
877 let mut cache = HashMap::new();
878 let ignored_enums = &OuterEnumHashes::empty();
879
880 let a_hash = get_type_hash_recurse(®istry, a_type_id, &mut cache, ignored_enums);
881 let a_hash2 = get_type_hash_recurse(®istry, a_type_id, &mut cache, ignored_enums);
882 let b_hash = get_type_hash_recurse(®istry, b_type_id, &mut cache, ignored_enums);
883
884 let CachedHash::Hash(a_cache_hash) = cache[&a_type_id] else {
885 panic!()
886 };
887 let CachedHash::Hash(b_cache_hash) = cache[&b_type_id] else {
888 panic!()
889 };
890
891 assert_eq!(a_hash, a_cache_hash);
892 assert_eq!(b_hash, b_cache_hash);
893
894 assert_eq!(a_hash, a_hash2);
895 assert_ne!(a_hash, b_hash);
896 }
897
898 #[test]
899 #[allow(clippy::redundant_clone)]
901 fn pallet_hash_correctness() {
902 let compare_pallets_hash = |lhs: &v15::PalletMetadata, rhs: &v15::PalletMetadata| {
903 let metadata = pallets_to_metadata(vec![lhs.clone()]);
904 let hash = MetadataHasher::new(&metadata).hash();
905
906 let metadata = pallets_to_metadata(vec![rhs.clone()]);
907 let new_hash = MetadataHasher::new(&metadata).hash();
908
909 assert_ne!(hash, new_hash);
910 };
911
912 let mut pallet = default_pallet();
914 let pallet_lhs = pallet.clone();
915 pallet.storage = Some(v15::PalletStorageMetadata {
916 prefix: "Storage",
917 entries: vec![v15::StorageEntryMetadata {
918 name: "BlockWeight",
919 modifier: v15::StorageEntryModifier::Default,
920 ty: v15::StorageEntryType::Plain(meta_type::<u8>()),
921 default: vec![],
922 docs: vec![],
923 }],
924 });
925 compare_pallets_hash(&pallet_lhs, &pallet);
926
927 let pallet_lhs = pallet.clone();
928 pallet.calls = Some(v15::PalletCallMetadata {
937 ty: meta_type::<Call>(),
938 });
939 compare_pallets_hash(&pallet_lhs, &pallet);
940
941 let pallet_lhs = pallet.clone();
942 pallet.event = Some(v15::PalletEventMetadata {
944 ty: meta_type::<Call>(),
945 });
946 compare_pallets_hash(&pallet_lhs, &pallet);
947
948 let pallet_lhs = pallet.clone();
949 pallet.constants = vec![v15::PalletConstantMetadata {
950 name: "BlockHashCount",
951 ty: meta_type::<u64>(),
952 value: vec![96u8, 0, 0, 0],
953 docs: vec![],
954 }];
955 compare_pallets_hash(&pallet_lhs, &pallet);
956
957 let pallet_lhs = pallet.clone();
958 pallet.error = Some(v15::PalletErrorMetadata {
959 ty: meta_type::<MetadataTestType>(),
960 });
961 compare_pallets_hash(&pallet_lhs, &pallet);
962 }
963
964 #[test]
965 fn metadata_per_pallet_hash_correctness() {
966 let pallets = build_default_pallets();
967
968 let metadata_one = pallets_to_metadata(vec![pallets[0].clone()]);
970 let metadata_both = pallets_to_metadata(pallets);
972
973 let hash = MetadataHasher::new(&metadata_one)
975 .only_these_pallets(&["First", "Second"])
976 .hash();
977 let hash_rhs = MetadataHasher::new(&metadata_one)
978 .only_these_pallets(&["First"])
979 .hash();
980 assert_eq!(hash, hash_rhs, "hashing should ignore non-existant pallets");
981
982 let hash_second = MetadataHasher::new(&metadata_both)
984 .only_these_pallets(&["First"])
985 .hash();
986 assert_eq!(
987 hash_second, hash,
988 "hashing one pallet should ignore the others"
989 );
990
991 let hash_second = MetadataHasher::new(&metadata_both)
993 .only_these_pallets(&["First", "Second"])
994 .hash();
995 assert_ne!(
996 hash_second, hash,
997 "hashing both pallets should produce a different result from hashing just one pallet"
998 );
999 }
1000
1001 #[test]
1002 fn field_semantic_changes() {
1003 let to_hash = |meta_ty| {
1006 let pallet = v15::PalletMetadata {
1007 calls: Some(v15::PalletCallMetadata { ty: meta_ty }),
1008 ..default_pallet()
1009 };
1010 let metadata = pallets_to_metadata(vec![pallet]);
1011 MetadataHasher::new(&metadata).hash()
1012 };
1013
1014 #[allow(dead_code)]
1015 #[derive(scale_info::TypeInfo)]
1016 enum EnumA1 {
1017 First { hi: u8, bye: String },
1018 Second(u32),
1019 Third,
1020 }
1021 #[allow(dead_code)]
1022 #[derive(scale_info::TypeInfo)]
1023 enum EnumA2 {
1024 Second(u32),
1025 Third,
1026 First { bye: String, hi: u8 },
1027 }
1028
1029 assert_eq!(
1032 to_hash(meta_type::<EnumA1>()),
1033 to_hash(meta_type::<EnumA2>())
1034 );
1035
1036 #[allow(dead_code)]
1037 #[derive(scale_info::TypeInfo)]
1038 struct StructB1 {
1039 hello: bool,
1040 another: [u8; 32],
1041 }
1042 #[allow(dead_code)]
1043 #[derive(scale_info::TypeInfo)]
1044 struct StructB2 {
1045 another: [u8; 32],
1046 hello: bool,
1047 }
1048
1049 assert_eq!(
1052 to_hash(meta_type::<StructB1>()),
1053 to_hash(meta_type::<StructB2>())
1054 );
1055
1056 #[allow(dead_code)]
1057 #[derive(scale_info::TypeInfo)]
1058 enum EnumC1 {
1059 First(u8),
1060 }
1061 #[allow(dead_code)]
1062 #[derive(scale_info::TypeInfo)]
1063 enum EnumC2 {
1064 Second(u8),
1065 }
1066
1067 assert_ne!(
1070 to_hash(meta_type::<EnumC1>()),
1071 to_hash(meta_type::<EnumC2>())
1072 );
1073
1074 #[allow(dead_code)]
1075 #[derive(scale_info::TypeInfo)]
1076 enum EnumD1 {
1077 First { a: u8 },
1078 }
1079 #[allow(dead_code)]
1080 #[derive(scale_info::TypeInfo)]
1081 enum EnumD2 {
1082 First { b: u8 },
1083 }
1084
1085 assert_ne!(
1088 to_hash(meta_type::<EnumD1>()),
1089 to_hash(meta_type::<EnumD2>())
1090 );
1091
1092 #[allow(dead_code)]
1093 #[derive(scale_info::TypeInfo)]
1094 struct StructE1 {
1095 a: u32,
1096 }
1097 #[allow(dead_code)]
1098 #[derive(scale_info::TypeInfo)]
1099 struct StructE2 {
1100 b: u32,
1101 }
1102
1103 assert_ne!(
1106 to_hash(meta_type::<StructE1>()),
1107 to_hash(meta_type::<StructE2>())
1108 );
1109 }
1110
1111 use frame_metadata::v15::{
1112 PalletEventMetadata, PalletStorageMetadata, StorageEntryMetadata, StorageEntryModifier,
1113 };
1114
1115 fn metadata_with_pallet_events() -> Metadata {
1116 #[allow(dead_code)]
1117 #[derive(scale_info::TypeInfo)]
1118 struct FirstEvent {
1119 s: String,
1120 }
1121
1122 #[allow(dead_code)]
1123 #[derive(scale_info::TypeInfo)]
1124 struct SecondEvent {
1125 n: u8,
1126 }
1127
1128 #[allow(dead_code)]
1129 #[derive(scale_info::TypeInfo)]
1130 enum Events {
1131 First(FirstEvent),
1132 Second(SecondEvent),
1133 }
1134
1135 #[allow(dead_code)]
1136 #[derive(scale_info::TypeInfo)]
1137 enum Errors {
1138 First(DispatchError),
1139 Second(DispatchError),
1140 }
1141
1142 #[allow(dead_code)]
1143 #[derive(scale_info::TypeInfo)]
1144 enum Calls {
1145 First(u8),
1146 Second(u8),
1147 }
1148
1149 #[allow(dead_code)]
1150 enum DispatchError {
1151 A,
1152 B,
1153 C,
1154 }
1155
1156 impl scale_info::TypeInfo for DispatchError {
1157 type Identity = DispatchError;
1158
1159 fn type_info() -> scale_info::Type {
1160 scale_info::Type {
1161 path: scale_info::Path {
1162 segments: vec!["sp_runtime", "DispatchError"],
1163 },
1164 type_params: vec![],
1165 type_def: TypeDef::Variant(TypeDefVariant { variants: vec![] }),
1166 docs: vec![],
1167 }
1168 }
1169 }
1170
1171 let pallets = vec![
1172 v15::PalletMetadata {
1173 name: "First",
1174 index: 0,
1175 calls: Some(v15::PalletCallMetadata {
1176 ty: meta_type::<u8>(),
1177 }),
1178 storage: Some(PalletStorageMetadata {
1179 prefix: "___",
1180 entries: vec![StorageEntryMetadata {
1181 name: "Hello",
1182 modifier: StorageEntryModifier::Optional,
1183 ty: frame_metadata::v14::StorageEntryType::Plain(meta_type::<Vec<Events>>()),
1187 default: vec![],
1188 docs: vec![],
1189 }],
1190 }),
1191 event: Some(PalletEventMetadata {
1192 ty: meta_type::<FirstEvent>(),
1193 }),
1194 constants: vec![],
1195 error: None,
1196 docs: vec![],
1197 },
1198 v15::PalletMetadata {
1199 name: "Second",
1200 index: 1,
1201 calls: Some(v15::PalletCallMetadata {
1202 ty: meta_type::<u64>(),
1203 }),
1204 storage: None,
1205 event: Some(PalletEventMetadata {
1206 ty: meta_type::<SecondEvent>(),
1207 }),
1208 constants: vec![],
1209 error: None,
1210 docs: vec![],
1211 },
1212 ];
1213
1214 v15::RuntimeMetadataV15::new(
1215 pallets,
1216 build_default_extrinsic(),
1217 meta_type::<()>(),
1218 vec![],
1219 v15::OuterEnums {
1220 call_enum_ty: meta_type::<Calls>(),
1221 event_enum_ty: meta_type::<Events>(),
1222 error_enum_ty: meta_type::<Errors>(),
1223 },
1224 v15::CustomMetadata {
1225 map: Default::default(),
1226 },
1227 )
1228 .try_into()
1229 .expect("can build valid metadata")
1230 }
1231
1232 #[test]
1233 fn hash_comparison_trimmed_metadata() {
1234 let metadata = metadata_with_pallet_events();
1236 let trimmed_metadata = {
1237 let mut m = metadata.clone();
1238 m.retain(|e| e == "First", |_| true);
1239 m
1240 };
1241
1242 let hash = MetadataHasher::new(&metadata)
1244 .only_these_pallets(&["First"])
1245 .hash();
1246 let hash_trimmed = MetadataHasher::new(&trimmed_metadata).hash();
1247
1248 assert_eq!(hash, hash_trimmed);
1249 }
1250}