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