subxt_metadata/utils/
validation.rs

1// Copyright 2019-2023 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    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
314/// Get the hash corresponding to a single storage entry.
315fn 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        // Cloning 'entry.modifier' should essentially be a copy.
323        &[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                // Cloning the hasher should essentially be a copy.
338                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
349/// Get the hash corresponding to a single runtime API method.
350fn get_runtime_method_hash(
351    registry: &PortableRegistry,
352    trait_name: &str,
353    method_metadata: &RuntimeApiMethodMetadata,
354    outer_enum_hashes: &OuterEnumHashes,
355) -> Hash {
356    // The trait name is part of the runtime API call that is being
357    // generated for this method. Therefore the trait name is strongly
358    // connected to the method in the same way as a parameter is
359    // to the method.
360    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
381/// Obtain the hash of all of a runtime API trait, including all of its methods.
382pub 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            // We don't care what order the trait methods exist in, and want the hash to
391            // be identical regardless. For this, we can just XOR the hashes for each method
392            // together; we'll get the same output whichever order they are XOR'd together in,
393            // so long as each individual method is the same.
394            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
422/// Obtain the hash of some custom value in the metadata including it's name/key.
423///
424/// If the `custom_value` has a type id that is not present in the metadata,
425/// only the name and bytes are used for hashing.
426pub 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
445/// Obtain the hash for a specific storage item, or an error if it's not found.
446pub 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
453/// Obtain the hash for a specific constant, or an error if it's not found.
454pub fn get_constant_hash(pallet: &PalletMetadata, constant_name: &str) -> Option<Hash> {
455    let constant = pallet.constant_by_name(constant_name)?;
456
457    // We only need to check that the type of the constant asked for matches.
458    let bytes = get_type_hash(pallet.types, constant.ty, &OuterEnumHashes::empty());
459    Some(bytes)
460}
461
462/// Obtain the hash for a specific call, or an error if it's not found.
463pub fn get_call_hash(pallet: &PalletMetadata, call_name: &str) -> Option<Hash> {
464    let call_variant = pallet.call_variant_by_name(call_name)?;
465
466    // hash the specific variant representing the call we are interested in.
467    let hash = get_variant_hash(
468        pallet.types,
469        call_variant,
470        &mut HashMap::new(),
471        &OuterEnumHashes::empty(),
472    );
473    Some(hash)
474}
475
476/// Obtain the hash of a specific runtime API function, or an error if it's not found.
477pub 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
489/// Obtain the hash representation of a `frame_metadata::v15::PalletMetadata`.
490pub 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        // We don't care what order the constants occur in, so XOR together the combinations
507        // of (constantName, constantType) to make the order we see them irrelevant.
508        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                    // We don't care what order the storage entries occur in, so XOR them together
522                    // to make the order irrelevant.
523                    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    // Hash all of the above together:
534    concat_and_hash5(
535        &call_bytes,
536        &event_bytes,
537        &error_bytes,
538        &constant_bytes,
539        &storage_bytes,
540    )
541}
542
543/// Obtain a hash representation of our metadata or some part of it.
544/// This is obtained by calling [`crate::Metadata::hasher()`].
545pub 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    /// Create a new [`MetadataHasher`]
554    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    /// Only hash the provided pallets instead of hashing every pallet.
564    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    /// Only hash the provided runtime APIs instead of hashing every runtime API
570    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    /// Do not hash the custom values
580    pub fn ignore_custom_values(&mut self) -> &mut Self {
581        self.include_custom_values = false;
582        self
583    }
584
585    /// Hash the given metadata.
586    pub fn hash(&self) -> Hash {
587        let metadata = self.metadata;
588
589        // Get the hashes of outer enums, considering only `specific_pallets` (if any are set).
590        // If any of the typed that represent outer enums are encountered later, hashes from `top_level_enum_hashes` can be substituted.
591        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            // If specific pallets are given, only include this pallet if it is in the specific pallets.
599            let should_hash = self
600                .specific_pallets
601                .as_ref()
602                .map(|specific_pallets| specific_pallets.contains(&pallet.name()))
603                .unwrap_or(true);
604            // We don't care what order the pallets are seen in, so XOR their
605            // hashes together to be order independent.
606            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                // If specific runtime APIs are given, only include this pallet if it is in the specific runtime APIs.
617                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                // We don't care what order the runtime APIs are seen in, so XOR their
623                // hashes together to be order independent.
624                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    // Define recursive types.
659    #[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    // Define TypeDef supported types.
672    #[allow(dead_code)]
673    #[derive(scale_info::TypeInfo)]
674    // TypeDef::Composite with TypeDef::Array with Typedef::Primitive.
675    struct AccountId32(Hash);
676
677    #[allow(dead_code)]
678    #[derive(scale_info::TypeInfo)]
679    // TypeDef::Variant.
680    enum DigestItem {
681        PreRuntime(
682            // TypeDef::Array with primitive.
683            [::core::primitive::u8; 4usize],
684            // TypeDef::Sequence.
685            ::std::vec::Vec<::core::primitive::u8>,
686        ),
687        Other(::std::vec::Vec<::core::primitive::u8>),
688        // Nested TypeDef::Tuple.
689        RuntimeEnvironmentUpdated(((i8, i16), (u32, u64))),
690        // TypeDef::Compact.
691        Index(#[codec(compact)] ::core::primitive::u8),
692        // TypeDef::BitSequence.
693        BitSeq(BitVec<u8, Lsb0>),
694    }
695
696    #[allow(dead_code)]
697    #[derive(scale_info::TypeInfo)]
698    // Ensure recursive types and TypeDef variants are captured.
699    struct MetadataTestType {
700        recursive: A,
701        composite: AccountId32,
702        type_def: DigestItem,
703    }
704
705    #[allow(dead_code)]
706    #[derive(scale_info::TypeInfo)]
707    // Simulate a PalletCallMetadata.
708    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        // Change the order in which pallets are registered.
786        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        // Changing pallet order must still result in a deterministic unique hash.
795        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        // Check hashing algorithm finishes on a recursive type.
807        MetadataHasher::new(&metadata).hash();
808    }
809
810    #[test]
811    /// Ensure correctness of hashing when parsing the `metadata.types`.
812    ///
813    /// Having a recursive structure `A: { B }` and `B: { A }` registered in different order
814    /// `types: { { id: 0, A }, { id: 1, B } }` and `types: { { id: 0, B }, { id: 1, A } }`
815    /// must produce the same deterministic hashing value.
816    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        // Changing pallet order must still result in a deterministic unique hash.
837        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    /// Ensure ABB and ABA have a different structure:
856    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(&registry, a_type_id, &mut cache, ignored_enums);
881        let a_hash2 = get_type_hash_recurse(&registry, a_type_id, &mut cache, ignored_enums);
882        let b_hash = get_type_hash_recurse(&registry, 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    // Redundant clone clippy warning is a lie; https://github.com/rust-lang/rust-clippy/issues/10870
900    #[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        // Build metadata progressively from an empty pallet to a fully populated pallet.
913        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        // Calls are similar to:
929        //
930        // ```
931        // pub enum Call {
932        //     call_name_01 { arg01: type },
933        //     call_name_02 { arg01: type, arg02: type }
934        // }
935        // ```
936        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        // Events are similar to Calls.
943        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        // Build metadata with just the first pallet.
969        let metadata_one = pallets_to_metadata(vec![pallets[0].clone()]);
970        // Build metadata with both pallets.
971        let metadata_both = pallets_to_metadata(pallets);
972
973        // Hashing will ignore any non-existant pallet and return the same result.
974        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        // Hashing one pallet from metadata with 2 pallets inserted will ignore the second pallet.
983        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        // Check hashing with all pallets.
992        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        // Get a hash representation of the provided meta type,
1004        // inserted in the context of pallet metadata call.
1005        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        // EncodeAsType and DecodeAsType only care about enum variant names
1030        // and not indexes or field ordering or the enum name itself..
1031        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        // As with enums, struct names and field orders are irrelevant as long as
1050        // the field names and types are the same.
1051        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        // The enums are binary compatible, but the variants have different names, so
1068        // semantically they are different and should not be equal.
1069        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        // Named fields contain a different semantic meaning ('a' and 'b')  despite
1086        // being binary compatible, so hashes should be different.
1087        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        // Similar to enums, struct fields that contain a different semantic meaning
1104        // ('a' and 'b') despite being binary compatible will have different hashes.
1105        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                        // Note: This is the important part here:
1184                        // The Events type will be trimmed down and this trimming needs to be reflected
1185                        // when the hash of this storage item is computed.
1186                        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        // trim the metadata:
1235        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        // test that the hashes are the same:
1243        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}