subxt_metadata/utils/
validation.rs

1// Copyright 2019-2025 Parity Technologies (UK) Ltd.
2// This file is dual-licensed as Apache-2.0 or GPL-3.0.
3// see LICENSE for license details.
4
5//! Utility functions for metadata validation.
6
7use 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
18// The number of bytes our `hash` function produces.
19pub(crate) const HASH_LEN: usize = 32;
20pub type Hash = [u8; HASH_LEN];
21
22/// Internal byte representation for various metadata types utilized for
23/// generating deterministic hashes between different rust versions.
24#[repr(u8)]
25enum TypeBeingHashed {
26    Composite,
27    Variant,
28    Sequence,
29    Array,
30    Tuple,
31    Primitive,
32    Compact,
33    BitSequence,
34}
35
36/// Hashing function utilized internally.
37fn hash(data: &[u8]) -> Hash {
38    sp_crypto_hashing::twox_256(data)
39}
40
41/// XOR two hashes together. Only use this when you don't care about the order
42/// of the things you're hashing together.
43fn 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
51// Combine some number of HASH_LEN byte hashes and output a single HASH_LEN
52// byte hash to uniquely represent the inputs.
53macro_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
77/// Obtain the hash representation of a `scale_info::Field`.
78fn 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
95/// Obtain the hash representation of a `scale_info::Variant`.
96fn 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        // EncodeAsType and DecodeAsType don't care about variant field ordering,
105        // so XOR the fields to ensure that it doesn't matter.
106        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        // With EncodeAsType and DecodeAsType we no longer care which order the variants are in,
125        // as long as all of the names+types are there. XOR to not care about ordering.
126        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
142/// Obtain the hash representation of a `scale_info::TypeDef`.
143fn 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                        // With EncodeAsType and DecodeAsType we no longer care which order the fields are in,
158                        // as long as all of the names+types are there. XOR to not care about ordering.
159                        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            // Take length into account too; different length must lead to different hash.
175            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            // Cloning the 'primitive' type should essentially be a copy.
198            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/// indicates whether a hash has been fully computed for a type or not
213#[derive(Clone, Debug)]
214pub enum CachedHash {
215    /// hash not known yet, but computation has already started
216    Recursive,
217    /// hash of the type, computation was finished
218    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], // some magical value
226        }
227    }
228}
229
230/// Obtain the hash representation of a `scale_info::Type` identified by id.
231///
232/// Hashes of the outer enums (call, event, error) should be computed prior to this
233/// and passed in as the `outer_enum_hashes` argument. Whenever a type is encountered that
234/// is one of the outer enums, the procomputed hash is used instead of computing a new one.
235///
236/// The reason for this unintuitive behavior is that we sometimes want to trim the outer enum types
237/// beforehand to only include certain pallets, which affects their hash values.
238pub 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
246/// Obtain the hash representation of a `scale_info::Type` identified by id.
247fn 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 the type is part of precomputed outer enum hashes, the respective hash is used instead:
254    if let Some(hash) = outer_enum_hashes.resolve(id) {
255        return hash;
256    }
257
258    // Guard against recursive types, with a 2 step caching approach:
259    //    if the cache has an entry for the id, just return a hash derived from it.
260    //    if the type has not been seen yet, mark it with `CachedHash::Recursive` in the cache and proceed to `get_type_def_hash()`.
261    //        -> During the execution of get_type_def_hash() we might get into get_type_hash(id) again for the original id
262    //            -> in this case the `CachedHash::Recursive` provokes an early return.
263    //        -> Once we return from `get_type_def_hash()` we need to update the cache entry:
264    //            -> We set the cache value to `CachedHash::Hash(type_hash)`, where `type_hash` was returned from `get_type_def_hash()`
265    //            -> It makes sure, that different types end up with different cache values.
266    //
267    // Values in the cache can be thought of as a mapping like this:
268    // type_id ->  not contained           = We haven't seen the type yet.
269    //         -> `CachedHash::Recursive`  = We have seen the type but hash calculation for it hasn't finished yet.
270    //         -> `CachedHash::Hash(hash)` = Hash calculation for the type was completed.
271    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
283/// Obtain the hash representation of a `frame_metadata::v15::ExtrinsicMetadata`.
284fn get_extrinsic_hash(
285    registry: &PortableRegistry,
286    extrinsic: &ExtrinsicMetadata,
287    outer_enum_hashes: &OuterEnumHashes,
288) -> Hash {
289    // Get the hashes of the extrinsic type.
290    let address_hash = get_type_hash(registry, extrinsic.address_ty, outer_enum_hashes);
291    // The `RuntimeCall` type is intentionally omitted and hashed by the outer enums instead.
292    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    // Supported versions are just u8s and we will likely never have more than 32 of these, so put them into
296    // an array of u8s and panic if more than 32.
297    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
325/// Get the hash corresponding to a single storage entry.
326fn 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        // Cloning 'entry.modifier' should essentially be a copy.
334        &[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                // Cloning the hasher should essentially be a copy.
349                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
360/// Get the hash corresponding to a single runtime API method.
361fn get_runtime_method_hash(
362    registry: &PortableRegistry,
363    trait_name: &str,
364    method_metadata: &RuntimeApiMethodMetadata,
365    outer_enum_hashes: &OuterEnumHashes,
366) -> Hash {
367    // The trait name is part of the runtime API call that is being
368    // generated for this method. Therefore the trait name is strongly
369    // connected to the method in the same way as a parameter is
370    // to the method.
371    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
392/// Obtain the hash of all of a runtime API trait, including all of its methods.
393pub 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            // We don't care what order the trait methods exist in, and want the hash to
402            // be identical regardless. For this, we can just XOR the hashes for each method
403            // together; we'll get the same output whichever order they are XOR'd together in,
404            // so long as each individual method is the same.
405            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
433/// Obtain the hash of some custom value in the metadata including it's name/key.
434///
435/// If the `custom_value` has a type id that is not present in the metadata,
436/// only the name and bytes are used for hashing.
437pub 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
456/// Obtain the hash for a specific storage item, or an error if it's not found.
457pub 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
464/// Obtain the hash for a specific constant, or an error if it's not found.
465pub fn get_constant_hash(pallet: &PalletMetadata, constant_name: &str) -> Option<Hash> {
466    let constant = pallet.constant_by_name(constant_name)?;
467
468    // We only need to check that the type of the constant asked for matches.
469    let bytes = get_type_hash(pallet.types, constant.ty, &OuterEnumHashes::empty());
470    Some(bytes)
471}
472
473/// Obtain the hash for a specific call, or an error if it's not found.
474pub fn get_call_hash(pallet: &PalletMetadata, call_name: &str) -> Option<Hash> {
475    let call_variant = pallet.call_variant_by_name(call_name)?;
476
477    // hash the specific variant representing the call we are interested in.
478    let hash = get_variant_hash(
479        pallet.types,
480        call_variant,
481        &mut HashMap::new(),
482        &OuterEnumHashes::empty(),
483    );
484    Some(hash)
485}
486
487/// Obtain the hash of a specific runtime API function, or an error if it's not found.
488pub 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
500/// Obtain the hash representation of a `frame_metadata::v15::PalletMetadata`.
501pub 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        // We don't care what order the constants occur in, so XOR together the combinations
518        // of (constantName, constantType) to make the order we see them irrelevant.
519        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                    // We don't care what order the storage entries occur in, so XOR them together
533                    // to make the order irrelevant.
534                    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    // Hash all of the above together:
545    concat_and_hash5(
546        &call_bytes,
547        &event_bytes,
548        &error_bytes,
549        &constant_bytes,
550        &storage_bytes,
551    )
552}
553
554/// Obtain a hash representation of our metadata or some part of it.
555/// This is obtained by calling [`crate::Metadata::hasher()`].
556pub 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    /// Create a new [`MetadataHasher`]
565    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    /// Only hash the provided pallets instead of hashing every pallet.
575    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    /// Only hash the provided runtime APIs instead of hashing every runtime API
581    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    /// Do not hash the custom values
591    pub fn ignore_custom_values(&mut self) -> &mut Self {
592        self.include_custom_values = false;
593        self
594    }
595
596    /// Hash the given metadata.
597    pub fn hash(&self) -> Hash {
598        let metadata = self.metadata;
599
600        // Get the hashes of outer enums, considering only `specific_pallets` (if any are set).
601        // If any of the typed that represent outer enums are encountered later, hashes from `top_level_enum_hashes` can be substituted.
602        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            // If specific pallets are given, only include this pallet if it is in the specific pallets.
610            let should_hash = self
611                .specific_pallets
612                .as_ref()
613                .map(|specific_pallets| specific_pallets.contains(&pallet.name()))
614                .unwrap_or(true);
615            // We don't care what order the pallets are seen in, so XOR their
616            // hashes together to be order independent.
617            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                // If specific runtime APIs are given, only include this pallet if it is in the specific runtime APIs.
628                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                // We don't care what order the runtime APIs are seen in, so XOR their
634                // hashes together to be order independent.
635                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    // Define recursive types.
670    #[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    // Define TypeDef supported types.
683    #[allow(dead_code)]
684    #[derive(scale_info::TypeInfo)]
685    // TypeDef::Composite with TypeDef::Array with Typedef::Primitive.
686    struct AccountId32(Hash);
687
688    #[allow(dead_code)]
689    #[derive(scale_info::TypeInfo)]
690    // TypeDef::Variant.
691    enum DigestItem {
692        PreRuntime(
693            // TypeDef::Array with primitive.
694            [::core::primitive::u8; 4usize],
695            // TypeDef::Sequence.
696            ::std::vec::Vec<::core::primitive::u8>,
697        ),
698        Other(::std::vec::Vec<::core::primitive::u8>),
699        // Nested TypeDef::Tuple.
700        RuntimeEnvironmentUpdated(((i8, i16), (u32, u64))),
701        // TypeDef::Compact.
702        Index(#[codec(compact)] ::core::primitive::u8),
703        // TypeDef::BitSequence.
704        BitSeq(BitVec<u8, Lsb0>),
705    }
706
707    #[allow(dead_code)]
708    #[derive(scale_info::TypeInfo)]
709    // Ensure recursive types and TypeDef variants are captured.
710    struct MetadataTestType {
711        recursive: A,
712        composite: AccountId32,
713        type_def: DigestItem,
714    }
715
716    #[allow(dead_code)]
717    #[derive(scale_info::TypeInfo)]
718    // Simulate a PalletCallMetadata.
719    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        // Change the order in which pallets are registered.
797        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        // Changing pallet order must still result in a deterministic unique hash.
806        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        // Check hashing algorithm finishes on a recursive type.
818        MetadataHasher::new(&metadata).hash();
819    }
820
821    #[test]
822    /// Ensure correctness of hashing when parsing the `metadata.types`.
823    ///
824    /// Having a recursive structure `A: { B }` and `B: { A }` registered in different order
825    /// `types: { { id: 0, A }, { id: 1, B } }` and `types: { { id: 0, B }, { id: 1, A } }`
826    /// must produce the same deterministic hashing value.
827    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        // Changing pallet order must still result in a deterministic unique hash.
848        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    /// Ensure ABB and ABA have a different structure:
867    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(&registry, a_type_id, &mut cache, ignored_enums);
892        let a_hash2 = get_type_hash_recurse(&registry, a_type_id, &mut cache, ignored_enums);
893        let b_hash = get_type_hash_recurse(&registry, 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    // Redundant clone clippy warning is a lie; https://github.com/rust-lang/rust-clippy/issues/10870
911    #[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        // Build metadata progressively from an empty pallet to a fully populated pallet.
924        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        // Calls are similar to:
940        //
941        // ```
942        // pub enum Call {
943        //     call_name_01 { arg01: type },
944        //     call_name_02 { arg01: type, arg02: type }
945        // }
946        // ```
947        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        // Events are similar to Calls.
954        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        // Build metadata with just the first pallet.
980        let metadata_one = pallets_to_metadata(vec![pallets[0].clone()]);
981        // Build metadata with both pallets.
982        let metadata_both = pallets_to_metadata(pallets);
983
984        // Hashing will ignore any non-existant pallet and return the same result.
985        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        // Hashing one pallet from metadata with 2 pallets inserted will ignore the second pallet.
994        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        // Check hashing with all pallets.
1003        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        // Get a hash representation of the provided meta type,
1015        // inserted in the context of pallet metadata call.
1016        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        // EncodeAsType and DecodeAsType only care about enum variant names
1041        // and not indexes or field ordering or the enum name itself..
1042        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        // As with enums, struct names and field orders are irrelevant as long as
1061        // the field names and types are the same.
1062        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        // The enums are binary compatible, but the variants have different names, so
1079        // semantically they are different and should not be equal.
1080        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        // Named fields contain a different semantic meaning ('a' and 'b')  despite
1097        // being binary compatible, so hashes should be different.
1098        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        // Similar to enums, struct fields that contain a different semantic meaning
1115        // ('a' and 'b') despite being binary compatible will have different hashes.
1116        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                        // Note: This is the important part here:
1195                        // The Events type will be trimmed down and this trimming needs to be reflected
1196                        // when the hash of this storage item is computed.
1197                        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        // trim the metadata:
1246        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        // test that the hashes are the same:
1254        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}