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, ViewFunctionMetadata,
10};
11use alloc::vec::Vec;
12use hashbrown::HashMap;
13use scale_info::{Field, PortableRegistry, TypeDef, TypeDefVariant, Variant, form::PortableForm};
14
15// The number of bytes our `hash` function produces.
16pub(crate) const HASH_LEN: usize = 32;
17pub type Hash = [u8; HASH_LEN];
18
19/// Internal byte representation for various metadata types utilized for
20/// generating deterministic hashes between different rust versions.
21#[repr(u8)]
22enum TypeBeingHashed {
23    Composite,
24    Variant,
25    Sequence,
26    Array,
27    Tuple,
28    Primitive,
29    Compact,
30    BitSequence,
31}
32
33/// Hashing function utilized internally.
34fn hash(data: &[u8]) -> Hash {
35    sp_crypto_hashing::twox_256(data)
36}
37
38/// XOR two hashes together. Only use this when you don't care about the order
39/// of the things you're hashing together.
40fn xor(a: Hash, b: Hash) -> Hash {
41    let mut out = [0u8; HASH_LEN];
42    for (idx, (a, b)) in a.into_iter().zip(b).enumerate() {
43        out[idx] = a ^ b;
44    }
45    out
46}
47
48// Combine some number of HASH_LEN byte hashes and output a single HASH_LEN
49// byte hash to uniquely represent the inputs.
50macro_rules! count_idents {
51    () => { 0 };
52    ($n:ident $($rest:ident)*) => { 1 + count_idents!($($rest)*) }
53}
54macro_rules! concat_and_hash_n {
55    ($name:ident($($arg:ident)+)) => {
56        fn $name($($arg: &Hash),+) -> Hash {
57            let mut out = [0u8; HASH_LEN * count_idents!($($arg)+)];
58            let mut start = 0;
59            $(
60                out[start..start+HASH_LEN].copy_from_slice(&$arg[..]);
61                #[allow(unused_assignments)]
62                { start += HASH_LEN; }
63            )+
64            hash(&out)
65        }
66    }
67}
68concat_and_hash_n!(concat_and_hash2(a b));
69concat_and_hash_n!(concat_and_hash3(a b c));
70concat_and_hash_n!(concat_and_hash4(a b c d));
71concat_and_hash_n!(concat_and_hash5(a b c d e));
72concat_and_hash_n!(concat_and_hash6(a b c d e f));
73
74/// Obtain the hash representation of a `scale_info::Field`.
75fn get_field_hash(
76    registry: &PortableRegistry,
77    field: &Field<PortableForm>,
78    cache: &mut HashMap<u32, CachedHash>,
79) -> Hash {
80    let field_name_bytes = match &field.name {
81        Some(name) => hash(name.as_bytes()),
82        None => [0u8; HASH_LEN],
83    };
84
85    concat_and_hash2(
86        &field_name_bytes,
87        &get_type_hash_recurse(registry, field.ty.id, cache),
88    )
89}
90
91/// Obtain the hash representation of a `scale_info::Variant`.
92fn get_variant_hash(
93    registry: &PortableRegistry,
94    var: &Variant<PortableForm>,
95    cache: &mut HashMap<u32, CachedHash>,
96) -> Hash {
97    let variant_name_bytes = hash(var.name.as_bytes());
98    let variant_field_bytes = var.fields.iter().fold([0u8; HASH_LEN], |bytes, field| {
99        // EncodeAsType and DecodeAsType don't care about variant field ordering,
100        // so XOR the fields to ensure that it doesn't matter.
101        xor(bytes, get_field_hash(registry, field, cache))
102    });
103
104    concat_and_hash2(&variant_name_bytes, &variant_field_bytes)
105}
106
107fn get_type_def_variant_hash(
108    registry: &PortableRegistry,
109    variant: &TypeDefVariant<PortableForm>,
110    only_these_variants: Option<&[&str]>,
111    cache: &mut HashMap<u32, CachedHash>,
112) -> Hash {
113    let variant_id_bytes = [TypeBeingHashed::Variant as u8; HASH_LEN];
114    let variant_field_bytes = variant.variants.iter().fold([0u8; HASH_LEN], |bytes, var| {
115        // With EncodeAsType and DecodeAsType we no longer care which order the variants are in,
116        // as long as all of the names+types are there. XOR to not care about ordering.
117        let should_hash = only_these_variants
118            .as_ref()
119            .map(|only_these_variants| only_these_variants.contains(&var.name.as_str()))
120            .unwrap_or(true);
121        if should_hash {
122            xor(bytes, get_variant_hash(registry, var, cache))
123        } else {
124            bytes
125        }
126    });
127    concat_and_hash2(&variant_id_bytes, &variant_field_bytes)
128}
129
130/// Obtain the hash representation of a `scale_info::TypeDef`.
131fn get_type_def_hash(
132    registry: &PortableRegistry,
133    ty_def: &TypeDef<PortableForm>,
134    cache: &mut HashMap<u32, CachedHash>,
135) -> Hash {
136    match ty_def {
137        TypeDef::Composite(composite) => {
138            let composite_id_bytes = [TypeBeingHashed::Composite as u8; HASH_LEN];
139            let composite_field_bytes =
140                composite
141                    .fields
142                    .iter()
143                    .fold([0u8; HASH_LEN], |bytes, field| {
144                        // With EncodeAsType and DecodeAsType we no longer care which order the fields are in,
145                        // as long as all of the names+types are there. XOR to not care about ordering.
146                        xor(bytes, get_field_hash(registry, field, cache))
147                    });
148            concat_and_hash2(&composite_id_bytes, &composite_field_bytes)
149        }
150        TypeDef::Variant(variant) => get_type_def_variant_hash(registry, variant, None, cache),
151        TypeDef::Sequence(sequence) => concat_and_hash2(
152            &[TypeBeingHashed::Sequence as u8; HASH_LEN],
153            &get_type_hash_recurse(registry, sequence.type_param.id, cache),
154        ),
155        TypeDef::Array(array) => {
156            // Take length into account too; different length must lead to different hash.
157            let array_id_bytes = {
158                let mut a = [0u8; HASH_LEN];
159                a[0] = TypeBeingHashed::Array as u8;
160                a[1..5].copy_from_slice(&array.len.to_be_bytes());
161                a
162            };
163            concat_and_hash2(
164                &array_id_bytes,
165                &get_type_hash_recurse(registry, array.type_param.id, cache),
166            )
167        }
168        TypeDef::Tuple(tuple) => {
169            let mut bytes = hash(&[TypeBeingHashed::Tuple as u8]);
170            for field in &tuple.fields {
171                bytes = concat_and_hash2(&bytes, &get_type_hash_recurse(registry, field.id, cache));
172            }
173            bytes
174        }
175        TypeDef::Primitive(primitive) => {
176            // Cloning the 'primitive' type should essentially be a copy.
177            hash(&[TypeBeingHashed::Primitive as u8, primitive.clone() as u8])
178        }
179        TypeDef::Compact(compact) => concat_and_hash2(
180            &[TypeBeingHashed::Compact as u8; HASH_LEN],
181            &get_type_hash_recurse(registry, compact.type_param.id, cache),
182        ),
183        TypeDef::BitSequence(bitseq) => concat_and_hash3(
184            &[TypeBeingHashed::BitSequence as u8; HASH_LEN],
185            &get_type_hash_recurse(registry, bitseq.bit_order_type.id, cache),
186            &get_type_hash_recurse(registry, bitseq.bit_store_type.id, cache),
187        ),
188    }
189}
190
191/// indicates whether a hash has been fully computed for a type or not
192#[derive(Clone, Debug)]
193pub enum CachedHash {
194    /// hash not known yet, but computation has already started
195    Recursive,
196    /// hash of the type, computation was finished
197    Hash(Hash),
198}
199
200impl CachedHash {
201    fn hash(&self) -> Hash {
202        match &self {
203            CachedHash::Hash(hash) => *hash,
204            CachedHash::Recursive => [123; HASH_LEN], // some magical value
205        }
206    }
207}
208
209/// Obtain the hash representation of a `scale_info::Type` identified by id.
210///
211/// Hashes of the outer enums (call, event, error) should be computed prior to this
212/// and passed in as the `outer_enum_hashes` argument. Whenever a type is encountered that
213/// is one of the outer enums, the procomputed hash is used instead of computing a new one.
214///
215/// The reason for this unintuitive behavior is that we sometimes want to trim the outer enum types
216/// beforehand to only include certain pallets, which affects their hash values.
217pub fn get_type_hash(registry: &PortableRegistry, id: u32) -> Hash {
218    get_type_hash_recurse(registry, id, &mut HashMap::new())
219}
220
221/// Obtain the hash representation of a `scale_info::Type` identified by id.
222fn get_type_hash_recurse(
223    registry: &PortableRegistry,
224    id: u32,
225    cache: &mut HashMap<u32, CachedHash>,
226) -> Hash {
227    // Guard against recursive types, with a 2 step caching approach:
228    //    if the cache has an entry for the id, just return a hash derived from it.
229    //    if the type has not been seen yet, mark it with `CachedHash::Recursive` in the cache and proceed to `get_type_def_hash()`.
230    //        -> During the execution of get_type_def_hash() we might get into get_type_hash(id) again for the original id
231    //            -> in this case the `CachedHash::Recursive` provokes an early return.
232    //        -> Once we return from `get_type_def_hash()` we need to update the cache entry:
233    //            -> We set the cache value to `CachedHash::Hash(type_hash)`, where `type_hash` was returned from `get_type_def_hash()`
234    //            -> It makes sure, that different types end up with different cache values.
235    //
236    // Values in the cache can be thought of as a mapping like this:
237    // type_id ->  not contained           = We haven't seen the type yet.
238    //         -> `CachedHash::Recursive`  = We have seen the type but hash calculation for it hasn't finished yet.
239    //         -> `CachedHash::Hash(hash)` = Hash calculation for the type was completed.
240    if let Some(cached_hash) = cache.get(&id) {
241        return cached_hash.hash();
242    }
243    cache.insert(id, CachedHash::Recursive);
244    let ty = registry
245        .resolve(id)
246        .expect("Type ID provided by the metadata is registered; qed");
247    let type_hash = get_type_def_hash(registry, &ty.type_def, cache);
248    cache.insert(id, CachedHash::Hash(type_hash));
249    type_hash
250}
251
252/// Obtain the hash representation of a `frame_metadata::v15::ExtrinsicMetadata`.
253fn get_extrinsic_hash(registry: &PortableRegistry, extrinsic: &ExtrinsicMetadata) -> Hash {
254    // Get the hashes of the extrinsic type.
255    let address_hash = get_type_hash(registry, extrinsic.address_ty);
256    // The `RuntimeCall` type is intentionally omitted and hashed by the outer enums instead.
257    let signature_hash = get_type_hash(registry, extrinsic.signature_ty);
258
259    // Supported versions are just u8s and we will likely never have more than 32 of these, so put them into
260    // an array of u8s and panic if more than 32.
261    if extrinsic.supported_versions.len() > 32 {
262        panic!("The metadata validation logic does not support more than 32 extrinsic versions.");
263    }
264    let supported_extrinsic_versions = {
265        let mut a = [0u8; 32];
266        a[0..extrinsic.supported_versions.len()].copy_from_slice(&extrinsic.supported_versions);
267        a
268    };
269
270    let mut bytes = concat_and_hash3(
271        &address_hash,
272        &signature_hash,
273        &supported_extrinsic_versions,
274    );
275
276    for signed_extension in extrinsic.transaction_extensions.iter() {
277        bytes = concat_and_hash4(
278            &bytes,
279            &hash(signed_extension.identifier.as_bytes()),
280            &get_type_hash(registry, signed_extension.extra_ty),
281            &get_type_hash(registry, signed_extension.additional_ty),
282        )
283    }
284
285    bytes
286}
287
288/// Get the hash corresponding to a single storage entry.
289fn get_storage_entry_hash(registry: &PortableRegistry, entry: &StorageEntryMetadata) -> Hash {
290    let mut bytes = concat_and_hash3(
291        &hash(entry.name.as_bytes()),
292        &get_type_hash(registry, entry.info.value_id),
293        &hash(entry.info.default_value.as_deref().unwrap_or_default()),
294    );
295
296    for key in &*entry.info.keys {
297        bytes = concat_and_hash3(
298            &bytes,
299            &[key.hasher as u8; HASH_LEN],
300            &get_type_hash(registry, key.key_id),
301        )
302    }
303
304    bytes
305}
306
307fn get_custom_metadata_hash(custom_metadata: &CustomMetadata) -> Hash {
308    custom_metadata
309        .iter()
310        .fold([0u8; HASH_LEN], |bytes, custom_value| {
311            xor(bytes, get_custom_value_hash(&custom_value))
312        })
313}
314
315/// Obtain the hash of some custom value in the metadata including it's name/key.
316///
317/// If the `custom_value` has a type id that is not present in the metadata,
318/// only the name and bytes are used for hashing.
319pub fn get_custom_value_hash(custom_value: &CustomValueMetadata) -> Hash {
320    let name_hash = hash(custom_value.name.as_bytes());
321    if custom_value.types.resolve(custom_value.type_id()).is_none() {
322        hash(&name_hash)
323    } else {
324        concat_and_hash2(
325            &name_hash,
326            &get_type_hash(custom_value.types, custom_value.type_id()),
327        )
328    }
329}
330
331/// Obtain the hash for a specific storage item, or an error if it's not found.
332pub fn get_storage_hash(pallet: &PalletMetadata, entry_name: &str) -> Option<Hash> {
333    let storage = pallet.storage()?;
334    let entry = storage.entry_by_name(entry_name)?;
335    let hash = get_storage_entry_hash(pallet.types, entry);
336    Some(hash)
337}
338
339/// Obtain the hash for a specific constant, or an error if it's not found.
340pub fn get_constant_hash(pallet: &PalletMetadata, constant_name: &str) -> Option<Hash> {
341    let constant = pallet.constant_by_name(constant_name)?;
342
343    // We only need to check that the type of the constant asked for matches.
344    let bytes = get_type_hash(pallet.types, constant.ty);
345    Some(bytes)
346}
347
348/// Obtain the hash for a specific call, or an error if it's not found.
349pub fn get_call_hash(pallet: &PalletMetadata, call_name: &str) -> Option<Hash> {
350    let call_variant = pallet.call_variant_by_name(call_name)?;
351
352    // hash the specific variant representing the call we are interested in.
353    let hash = get_variant_hash(pallet.types, call_variant, &mut HashMap::new());
354    Some(hash)
355}
356
357/// Obtain the hash of a specific runtime API method, or an error if it's not found.
358pub fn get_runtime_api_hash(runtime_api: &RuntimeApiMethodMetadata) -> Hash {
359    let registry = runtime_api.types;
360
361    // The trait name is part of the runtime API call that is being
362    // generated for this method. Therefore the trait name is strongly
363    // connected to the method in the same way as a parameter is
364    // to the method.
365    let mut bytes = concat_and_hash2(
366        &hash(runtime_api.trait_name.as_bytes()),
367        &hash(runtime_api.name().as_bytes()),
368    );
369
370    for input in runtime_api.inputs() {
371        bytes = concat_and_hash3(
372            &bytes,
373            &hash(input.name.as_bytes()),
374            &get_type_hash(registry, input.id),
375        );
376    }
377
378    bytes = concat_and_hash2(&bytes, &get_type_hash(registry, runtime_api.output_ty()));
379
380    bytes
381}
382
383/// Obtain the hash of all of a runtime API trait, including all of its methods.
384pub fn get_runtime_apis_hash(trait_metadata: RuntimeApiMetadata) -> Hash {
385    // Each API is already hashed considering the trait name, so we don't need
386    // to consider the trait name again here.
387    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(bytes, get_runtime_api_hash(&method_metadata))
395        })
396}
397
398/// Obtain the hash of a specific view function, or an error if it's not found.
399pub fn get_view_function_hash(view_function: &ViewFunctionMetadata) -> Hash {
400    let registry = view_function.types;
401
402    // The Query ID is `twox_128(pallet_name) ++ twox_128("fn_name(fnarg_types) -> return_ty")`.
403    let mut bytes = *view_function.query_id();
404
405    // This only takes type _names_ into account, so we beef this up by combining with actual
406    // type hashes, in a similar approach to runtime APIs..
407    for input in view_function.inputs() {
408        bytes = concat_and_hash3(
409            &bytes,
410            &hash(input.name.as_bytes()),
411            &get_type_hash(registry, input.id),
412        );
413    }
414
415    bytes = concat_and_hash2(&bytes, &get_type_hash(registry, view_function.output_ty()));
416
417    bytes
418}
419
420/// Obtain the hash of all of the view functions in a pallet, including all of its methods.
421fn get_pallet_view_functions_hash(pallet_metadata: &PalletMetadata) -> Hash {
422    // Each API is already hashed considering the trait name, so we don't need
423    // to consider the trait name again here.
424    pallet_metadata
425        .view_functions()
426        .fold([0u8; HASH_LEN], |bytes, method_metadata| {
427            // We don't care what order the view functions are declared in, and want the hash to
428            // be identical regardless. For this, we can just XOR the hashes for each method
429            // together; we'll get the same output whichever order they are XOR'd together in,
430            // so long as each individual method is the same.
431            xor(bytes, get_view_function_hash(&method_metadata))
432        })
433}
434
435/// Obtain the hash representation of a `frame_metadata::v15::PalletMetadata`.
436pub fn get_pallet_hash(pallet: PalletMetadata) -> Hash {
437    let registry = pallet.types;
438
439    let call_bytes = match pallet.call_ty_id() {
440        Some(calls) => get_type_hash(registry, calls),
441        None => [0u8; HASH_LEN],
442    };
443    let event_bytes = match pallet.event_ty_id() {
444        Some(event) => get_type_hash(registry, event),
445        None => [0u8; HASH_LEN],
446    };
447    let error_bytes = match pallet.error_ty_id() {
448        Some(error) => get_type_hash(registry, error),
449        None => [0u8; HASH_LEN],
450    };
451    let constant_bytes = pallet.constants().fold([0u8; HASH_LEN], |bytes, constant| {
452        // We don't care what order the constants occur in, so XOR together the combinations
453        // of (constantName, constantType) to make the order we see them irrelevant.
454        let constant_hash = concat_and_hash2(
455            &hash(constant.name.as_bytes()),
456            &get_type_hash(registry, constant.ty()),
457        );
458        xor(bytes, constant_hash)
459    });
460    let storage_bytes = match pallet.storage() {
461        Some(storage) => {
462            let prefix_hash = hash(storage.prefix().as_bytes());
463            let entries_hash = storage
464                .entries()
465                .iter()
466                .fold([0u8; HASH_LEN], |bytes, entry| {
467                    // We don't care what order the storage entries occur in, so XOR them together
468                    // to make the order irrelevant.
469                    xor(bytes, get_storage_entry_hash(registry, entry))
470                });
471            concat_and_hash2(&prefix_hash, &entries_hash)
472        }
473        None => [0u8; HASH_LEN],
474    };
475    let view_functions_bytes = get_pallet_view_functions_hash(&pallet);
476
477    // Hash all of the above together:
478    concat_and_hash6(
479        &call_bytes,
480        &event_bytes,
481        &error_bytes,
482        &constant_bytes,
483        &storage_bytes,
484        &view_functions_bytes,
485    )
486}
487
488/// Obtain a hash representation of our metadata or some part of it.
489/// This is obtained by calling [`crate::Metadata::hasher()`].
490pub struct MetadataHasher<'a> {
491    metadata: &'a Metadata,
492    specific_pallets: Option<Vec<&'a str>>,
493    specific_runtime_apis: Option<Vec<&'a str>>,
494    include_custom_values: bool,
495}
496
497impl<'a> MetadataHasher<'a> {
498    /// Create a new [`MetadataHasher`]
499    pub(crate) fn new(metadata: &'a Metadata) -> Self {
500        Self {
501            metadata,
502            specific_pallets: None,
503            specific_runtime_apis: None,
504            include_custom_values: true,
505        }
506    }
507
508    /// Only hash the provided pallets instead of hashing every pallet.
509    pub fn only_these_pallets<S: AsRef<str>>(&mut self, specific_pallets: &'a [S]) -> &mut Self {
510        self.specific_pallets = Some(specific_pallets.iter().map(|n| n.as_ref()).collect());
511        self
512    }
513
514    /// Only hash the provided runtime APIs instead of hashing every runtime API
515    pub fn only_these_runtime_apis<S: AsRef<str>>(
516        &mut self,
517        specific_runtime_apis: &'a [S],
518    ) -> &mut Self {
519        self.specific_runtime_apis =
520            Some(specific_runtime_apis.iter().map(|n| n.as_ref()).collect());
521        self
522    }
523
524    /// Do not hash the custom values
525    pub fn ignore_custom_values(&mut self) -> &mut Self {
526        self.include_custom_values = false;
527        self
528    }
529
530    /// Hash the given metadata.
531    pub fn hash(&self) -> Hash {
532        let metadata = self.metadata;
533
534        let pallet_hash = metadata.pallets().fold([0u8; HASH_LEN], |bytes, pallet| {
535            // If specific pallets are given, only include this pallet if it is in the specific pallets.
536            let should_hash = self
537                .specific_pallets
538                .as_ref()
539                .map(|specific_pallets| specific_pallets.contains(&pallet.name()))
540                .unwrap_or(true);
541            // We don't care what order the pallets are seen in, so XOR their
542            // hashes together to be order independent.
543            if should_hash {
544                xor(bytes, get_pallet_hash(pallet))
545            } else {
546                bytes
547            }
548        });
549
550        let apis_hash = metadata
551            .runtime_api_traits()
552            .fold([0u8; HASH_LEN], |bytes, api| {
553                // If specific runtime APIs are given, only include this pallet if it is in the specific runtime APIs.
554                let should_hash = self
555                    .specific_runtime_apis
556                    .as_ref()
557                    .map(|specific_runtime_apis| specific_runtime_apis.contains(&api.name()))
558                    .unwrap_or(true);
559                // We don't care what order the runtime APIs are seen in, so XOR their
560                // hashes together to be order independent.
561                if should_hash {
562                    xor(bytes, get_runtime_apis_hash(api))
563                } else {
564                    bytes
565                }
566            });
567
568        let outer_enums_hash = concat_and_hash3(
569            &get_type_hash(&metadata.types, metadata.outer_enums.call_enum_ty),
570            &get_type_hash(&metadata.types, metadata.outer_enums.event_enum_ty),
571            &get_type_hash(&metadata.types, metadata.outer_enums.error_enum_ty),
572        );
573
574        let extrinsic_hash = get_extrinsic_hash(&metadata.types, &metadata.extrinsic);
575
576        let custom_values_hash = if self.include_custom_values {
577            get_custom_metadata_hash(&metadata.custom())
578        } else {
579            Default::default()
580        };
581
582        concat_and_hash5(
583            &pallet_hash,
584            &apis_hash,
585            &outer_enums_hash,
586            &extrinsic_hash,
587            &custom_values_hash,
588        )
589    }
590}
591
592#[cfg(test)]
593mod tests {
594    use super::*;
595    use bitvec::{order::Lsb0, vec::BitVec};
596    use frame_metadata::v15;
597    use scale_info::{Registry, meta_type};
598
599    // Define recursive types.
600    #[allow(dead_code)]
601    #[derive(scale_info::TypeInfo)]
602    struct A {
603        pub b: Box<B>,
604    }
605
606    #[allow(dead_code)]
607    #[derive(scale_info::TypeInfo)]
608    struct B {
609        pub a: Box<A>,
610    }
611
612    // Define TypeDef supported types.
613    #[allow(dead_code)]
614    #[derive(scale_info::TypeInfo)]
615    // TypeDef::Composite with TypeDef::Array with Typedef::Primitive.
616    struct AccountId32(Hash);
617
618    #[allow(dead_code)]
619    #[derive(scale_info::TypeInfo)]
620    // TypeDef::Variant.
621    enum DigestItem {
622        PreRuntime(
623            // TypeDef::Array with primitive.
624            [::core::primitive::u8; 4usize],
625            // TypeDef::Sequence.
626            ::std::vec::Vec<::core::primitive::u8>,
627        ),
628        Other(::std::vec::Vec<::core::primitive::u8>),
629        // Nested TypeDef::Tuple.
630        RuntimeEnvironmentUpdated(((i8, i16), (u32, u64))),
631        // TypeDef::Compact.
632        Index(#[codec(compact)] ::core::primitive::u8),
633        // TypeDef::BitSequence.
634        BitSeq(BitVec<u8, Lsb0>),
635    }
636
637    #[allow(dead_code)]
638    #[derive(scale_info::TypeInfo)]
639    // Ensure recursive types and TypeDef variants are captured.
640    struct MetadataTestType {
641        recursive: A,
642        composite: AccountId32,
643        type_def: DigestItem,
644    }
645
646    #[allow(dead_code)]
647    #[derive(scale_info::TypeInfo)]
648    // Simulate a PalletCallMetadata.
649    enum Call {
650        #[codec(index = 0)]
651        FillBlock { ratio: AccountId32 },
652        #[codec(index = 1)]
653        Remark { remark: DigestItem },
654    }
655
656    fn build_default_extrinsic() -> v15::ExtrinsicMetadata {
657        v15::ExtrinsicMetadata {
658            version: 0,
659            signed_extensions: vec![],
660            address_ty: meta_type::<()>(),
661            call_ty: meta_type::<()>(),
662            signature_ty: meta_type::<()>(),
663            extra_ty: meta_type::<()>(),
664        }
665    }
666
667    fn default_pallet() -> v15::PalletMetadata {
668        v15::PalletMetadata {
669            name: "Test",
670            storage: None,
671            calls: None,
672            event: None,
673            constants: vec![],
674            error: None,
675            index: 0,
676            docs: vec![],
677        }
678    }
679
680    fn build_default_pallets() -> Vec<v15::PalletMetadata> {
681        vec![
682            v15::PalletMetadata {
683                name: "First",
684                calls: Some(v15::PalletCallMetadata {
685                    ty: meta_type::<MetadataTestType>(),
686                }),
687                ..default_pallet()
688            },
689            v15::PalletMetadata {
690                name: "Second",
691                index: 1,
692                calls: Some(v15::PalletCallMetadata {
693                    ty: meta_type::<(DigestItem, AccountId32, A)>(),
694                }),
695                ..default_pallet()
696            },
697        ]
698    }
699
700    fn pallets_to_metadata(pallets: Vec<v15::PalletMetadata>) -> Metadata {
701        v15::RuntimeMetadataV15::new(
702            pallets,
703            build_default_extrinsic(),
704            meta_type::<()>(),
705            vec![],
706            v15::OuterEnums {
707                call_enum_ty: meta_type::<()>(),
708                event_enum_ty: meta_type::<()>(),
709                error_enum_ty: meta_type::<()>(),
710            },
711            v15::CustomMetadata {
712                map: Default::default(),
713            },
714        )
715        .try_into()
716        .expect("can build valid metadata")
717    }
718
719    #[test]
720    fn different_pallet_index() {
721        let pallets = build_default_pallets();
722        let mut pallets_swap = pallets.clone();
723
724        let metadata = pallets_to_metadata(pallets);
725
726        // Change the order in which pallets are registered.
727        pallets_swap.swap(0, 1);
728        pallets_swap[0].index = 0;
729        pallets_swap[1].index = 1;
730        let metadata_swap = pallets_to_metadata(pallets_swap);
731
732        let hash = MetadataHasher::new(&metadata).hash();
733        let hash_swap = MetadataHasher::new(&metadata_swap).hash();
734
735        // Changing pallet order must still result in a deterministic unique hash.
736        assert_eq!(hash, hash_swap);
737    }
738
739    #[test]
740    fn recursive_type() {
741        let mut pallet = default_pallet();
742        pallet.calls = Some(v15::PalletCallMetadata {
743            ty: meta_type::<A>(),
744        });
745        let metadata = pallets_to_metadata(vec![pallet]);
746
747        // Check hashing algorithm finishes on a recursive type.
748        MetadataHasher::new(&metadata).hash();
749    }
750
751    #[test]
752    /// Ensure correctness of hashing when parsing the `metadata.types`.
753    ///
754    /// Having a recursive structure `A: { B }` and `B: { A }` registered in different order
755    /// `types: { { id: 0, A }, { id: 1, B } }` and `types: { { id: 0, B }, { id: 1, A } }`
756    /// must produce the same deterministic hashing value.
757    fn recursive_types_different_order() {
758        let mut pallets = build_default_pallets();
759        pallets[0].calls = Some(v15::PalletCallMetadata {
760            ty: meta_type::<A>(),
761        });
762        pallets[1].calls = Some(v15::PalletCallMetadata {
763            ty: meta_type::<B>(),
764        });
765        pallets[1].index = 1;
766        let mut pallets_swap = pallets.clone();
767        let metadata = pallets_to_metadata(pallets);
768
769        pallets_swap.swap(0, 1);
770        pallets_swap[0].index = 0;
771        pallets_swap[1].index = 1;
772        let metadata_swap = pallets_to_metadata(pallets_swap);
773
774        let hash = MetadataHasher::new(&metadata).hash();
775        let hash_swap = MetadataHasher::new(&metadata_swap).hash();
776
777        // Changing pallet order must still result in a deterministic unique hash.
778        assert_eq!(hash, hash_swap);
779    }
780
781    #[allow(dead_code)]
782    #[derive(scale_info::TypeInfo)]
783    struct Aba {
784        ab: (A, B),
785        other: A,
786    }
787
788    #[allow(dead_code)]
789    #[derive(scale_info::TypeInfo)]
790    struct Abb {
791        ab: (A, B),
792        other: B,
793    }
794
795    #[test]
796    /// Ensure ABB and ABA have a different structure:
797    fn do_not_reuse_visited_type_ids() {
798        let metadata_hash_with_type = |ty| {
799            let mut pallets = build_default_pallets();
800            pallets[0].calls = Some(v15::PalletCallMetadata { ty });
801            let metadata = pallets_to_metadata(pallets);
802            MetadataHasher::new(&metadata).hash()
803        };
804
805        let aba_hash = metadata_hash_with_type(meta_type::<Aba>());
806        let abb_hash = metadata_hash_with_type(meta_type::<Abb>());
807
808        assert_ne!(aba_hash, abb_hash);
809    }
810
811    #[test]
812    fn hash_cache_gets_filled_with_correct_hashes() {
813        let mut registry = Registry::new();
814        let a_type_id = registry.register_type(&meta_type::<A>()).id;
815        let b_type_id = registry.register_type(&meta_type::<B>()).id;
816        let registry: PortableRegistry = registry.into();
817
818        let mut cache = HashMap::new();
819
820        let a_hash = get_type_hash_recurse(&registry, a_type_id, &mut cache);
821        let a_hash2 = get_type_hash_recurse(&registry, a_type_id, &mut cache);
822        let b_hash = get_type_hash_recurse(&registry, b_type_id, &mut cache);
823
824        let CachedHash::Hash(a_cache_hash) = cache[&a_type_id] else {
825            panic!()
826        };
827        let CachedHash::Hash(b_cache_hash) = cache[&b_type_id] else {
828            panic!()
829        };
830
831        assert_eq!(a_hash, a_cache_hash);
832        assert_eq!(b_hash, b_cache_hash);
833
834        assert_eq!(a_hash, a_hash2);
835        assert_ne!(a_hash, b_hash);
836    }
837
838    #[test]
839    // Redundant clone clippy warning is a lie; https://github.com/rust-lang/rust-clippy/issues/10870
840    #[allow(clippy::redundant_clone)]
841    fn pallet_hash_correctness() {
842        let compare_pallets_hash = |lhs: &v15::PalletMetadata, rhs: &v15::PalletMetadata| {
843            let metadata = pallets_to_metadata(vec![lhs.clone()]);
844            let hash = MetadataHasher::new(&metadata).hash();
845
846            let metadata = pallets_to_metadata(vec![rhs.clone()]);
847            let new_hash = MetadataHasher::new(&metadata).hash();
848
849            assert_ne!(hash, new_hash);
850        };
851
852        // Build metadata progressively from an empty pallet to a fully populated pallet.
853        let mut pallet = default_pallet();
854        let pallet_lhs = pallet.clone();
855        pallet.storage = Some(v15::PalletStorageMetadata {
856            prefix: "Storage",
857            entries: vec![v15::StorageEntryMetadata {
858                name: "BlockWeight",
859                modifier: v15::StorageEntryModifier::Default,
860                ty: v15::StorageEntryType::Plain(meta_type::<u8>()),
861                default: vec![],
862                docs: vec![],
863            }],
864        });
865        compare_pallets_hash(&pallet_lhs, &pallet);
866
867        let pallet_lhs = pallet.clone();
868        // Calls are similar to:
869        //
870        // ```
871        // pub enum Call {
872        //     call_name_01 { arg01: type },
873        //     call_name_02 { arg01: type, arg02: type }
874        // }
875        // ```
876        pallet.calls = Some(v15::PalletCallMetadata {
877            ty: meta_type::<Call>(),
878        });
879        compare_pallets_hash(&pallet_lhs, &pallet);
880
881        let pallet_lhs = pallet.clone();
882        // Events are similar to Calls.
883        pallet.event = Some(v15::PalletEventMetadata {
884            ty: meta_type::<Call>(),
885        });
886        compare_pallets_hash(&pallet_lhs, &pallet);
887
888        let pallet_lhs = pallet.clone();
889        pallet.constants = vec![v15::PalletConstantMetadata {
890            name: "BlockHashCount",
891            ty: meta_type::<u64>(),
892            value: vec![96u8, 0, 0, 0],
893            docs: vec![],
894        }];
895        compare_pallets_hash(&pallet_lhs, &pallet);
896
897        let pallet_lhs = pallet.clone();
898        pallet.error = Some(v15::PalletErrorMetadata {
899            ty: meta_type::<MetadataTestType>(),
900        });
901        compare_pallets_hash(&pallet_lhs, &pallet);
902    }
903
904    #[test]
905    fn metadata_per_pallet_hash_correctness() {
906        let pallets = build_default_pallets();
907
908        // Build metadata with just the first pallet.
909        let metadata_one = pallets_to_metadata(vec![pallets[0].clone()]);
910        // Build metadata with both pallets.
911        let metadata_both = pallets_to_metadata(pallets);
912
913        // Hashing will ignore any non-existent pallet and return the same result.
914        let hash = MetadataHasher::new(&metadata_one)
915            .only_these_pallets(&["First", "Second"])
916            .hash();
917        let hash_rhs = MetadataHasher::new(&metadata_one)
918            .only_these_pallets(&["First"])
919            .hash();
920        assert_eq!(hash, hash_rhs, "hashing should ignore non-existent pallets");
921
922        // Hashing one pallet from metadata with 2 pallets inserted will ignore the second pallet.
923        let hash_second = MetadataHasher::new(&metadata_both)
924            .only_these_pallets(&["First"])
925            .hash();
926        assert_eq!(
927            hash_second, hash,
928            "hashing one pallet should ignore the others"
929        );
930
931        // Check hashing with all pallets.
932        let hash_second = MetadataHasher::new(&metadata_both)
933            .only_these_pallets(&["First", "Second"])
934            .hash();
935        assert_ne!(
936            hash_second, hash,
937            "hashing both pallets should produce a different result from hashing just one pallet"
938        );
939    }
940
941    #[test]
942    fn field_semantic_changes() {
943        // Get a hash representation of the provided meta type,
944        // inserted in the context of pallet metadata call.
945        let to_hash = |meta_ty| {
946            let pallet = v15::PalletMetadata {
947                calls: Some(v15::PalletCallMetadata { ty: meta_ty }),
948                ..default_pallet()
949            };
950            let metadata = pallets_to_metadata(vec![pallet]);
951            MetadataHasher::new(&metadata).hash()
952        };
953
954        #[allow(dead_code)]
955        #[derive(scale_info::TypeInfo)]
956        enum EnumA1 {
957            First { hi: u8, bye: String },
958            Second(u32),
959            Third,
960        }
961        #[allow(dead_code)]
962        #[derive(scale_info::TypeInfo)]
963        enum EnumA2 {
964            Second(u32),
965            Third,
966            First { bye: String, hi: u8 },
967        }
968
969        // EncodeAsType and DecodeAsType only care about enum variant names
970        // and not indexes or field ordering or the enum name itself..
971        assert_eq!(
972            to_hash(meta_type::<EnumA1>()),
973            to_hash(meta_type::<EnumA2>())
974        );
975
976        #[allow(dead_code)]
977        #[derive(scale_info::TypeInfo)]
978        struct StructB1 {
979            hello: bool,
980            another: [u8; 32],
981        }
982        #[allow(dead_code)]
983        #[derive(scale_info::TypeInfo)]
984        struct StructB2 {
985            another: [u8; 32],
986            hello: bool,
987        }
988
989        // As with enums, struct names and field orders are irrelevant as long as
990        // the field names and types are the same.
991        assert_eq!(
992            to_hash(meta_type::<StructB1>()),
993            to_hash(meta_type::<StructB2>())
994        );
995
996        #[allow(dead_code)]
997        #[derive(scale_info::TypeInfo)]
998        enum EnumC1 {
999            First(u8),
1000        }
1001        #[allow(dead_code)]
1002        #[derive(scale_info::TypeInfo)]
1003        enum EnumC2 {
1004            Second(u8),
1005        }
1006
1007        // The enums are binary compatible, but the variants have different names, so
1008        // semantically they are different and should not be equal.
1009        assert_ne!(
1010            to_hash(meta_type::<EnumC1>()),
1011            to_hash(meta_type::<EnumC2>())
1012        );
1013
1014        #[allow(dead_code)]
1015        #[derive(scale_info::TypeInfo)]
1016        enum EnumD1 {
1017            First { a: u8 },
1018        }
1019        #[allow(dead_code)]
1020        #[derive(scale_info::TypeInfo)]
1021        enum EnumD2 {
1022            First { b: u8 },
1023        }
1024
1025        // Named fields contain a different semantic meaning ('a' and 'b')  despite
1026        // being binary compatible, so hashes should be different.
1027        assert_ne!(
1028            to_hash(meta_type::<EnumD1>()),
1029            to_hash(meta_type::<EnumD2>())
1030        );
1031
1032        #[allow(dead_code)]
1033        #[derive(scale_info::TypeInfo)]
1034        struct StructE1 {
1035            a: u32,
1036        }
1037        #[allow(dead_code)]
1038        #[derive(scale_info::TypeInfo)]
1039        struct StructE2 {
1040            b: u32,
1041        }
1042
1043        // Similar to enums, struct fields that contain a different semantic meaning
1044        // ('a' and 'b') despite being binary compatible will have different hashes.
1045        assert_ne!(
1046            to_hash(meta_type::<StructE1>()),
1047            to_hash(meta_type::<StructE2>())
1048        );
1049    }
1050
1051    use frame_metadata::v15::{
1052        PalletEventMetadata, PalletStorageMetadata, StorageEntryMetadata, StorageEntryModifier,
1053    };
1054
1055    fn metadata_with_pallet_events() -> v15::RuntimeMetadataV15 {
1056        #[allow(dead_code)]
1057        #[derive(scale_info::TypeInfo)]
1058        struct FirstEvent {
1059            s: String,
1060        }
1061
1062        #[allow(dead_code)]
1063        #[derive(scale_info::TypeInfo)]
1064        struct SecondEvent {
1065            n: u8,
1066        }
1067
1068        #[allow(dead_code)]
1069        #[derive(scale_info::TypeInfo)]
1070        enum Events {
1071            First(FirstEvent),
1072            Second(SecondEvent),
1073        }
1074
1075        #[allow(dead_code)]
1076        #[derive(scale_info::TypeInfo)]
1077        enum Errors {
1078            First(DispatchError),
1079            Second(DispatchError),
1080        }
1081
1082        #[allow(dead_code)]
1083        #[derive(scale_info::TypeInfo)]
1084        enum Calls {
1085            First(u8),
1086            Second(u8),
1087        }
1088
1089        #[allow(dead_code)]
1090        enum DispatchError {
1091            A,
1092            B,
1093            C,
1094        }
1095
1096        impl scale_info::TypeInfo for DispatchError {
1097            type Identity = DispatchError;
1098
1099            fn type_info() -> scale_info::Type {
1100                scale_info::Type {
1101                    path: scale_info::Path {
1102                        segments: vec!["sp_runtime", "DispatchError"],
1103                    },
1104                    type_params: vec![],
1105                    type_def: TypeDef::Variant(TypeDefVariant { variants: vec![] }),
1106                    docs: vec![],
1107                }
1108            }
1109        }
1110
1111        let pallets = vec![
1112            v15::PalletMetadata {
1113                name: "First",
1114                index: 0,
1115                calls: Some(v15::PalletCallMetadata {
1116                    ty: meta_type::<u8>(),
1117                }),
1118                storage: Some(PalletStorageMetadata {
1119                    prefix: "___",
1120                    entries: vec![StorageEntryMetadata {
1121                        name: "Hello",
1122                        modifier: StorageEntryModifier::Optional,
1123                        // Note: This is the important part here:
1124                        // The Events type will be trimmed down and this trimming needs to be reflected
1125                        // when the hash of this storage item is computed.
1126                        ty: frame_metadata::v14::StorageEntryType::Plain(meta_type::<Vec<Events>>()),
1127                        default: vec![],
1128                        docs: vec![],
1129                    }],
1130                }),
1131                event: Some(PalletEventMetadata {
1132                    ty: meta_type::<FirstEvent>(),
1133                }),
1134                constants: vec![],
1135                error: None,
1136                docs: vec![],
1137            },
1138            v15::PalletMetadata {
1139                name: "Second",
1140                index: 1,
1141                calls: Some(v15::PalletCallMetadata {
1142                    ty: meta_type::<u64>(),
1143                }),
1144                storage: None,
1145                event: Some(PalletEventMetadata {
1146                    ty: meta_type::<SecondEvent>(),
1147                }),
1148                constants: vec![],
1149                error: None,
1150                docs: vec![],
1151            },
1152        ];
1153
1154        v15::RuntimeMetadataV15::new(
1155            pallets,
1156            build_default_extrinsic(),
1157            meta_type::<()>(),
1158            vec![],
1159            v15::OuterEnums {
1160                call_enum_ty: meta_type::<Calls>(),
1161                event_enum_ty: meta_type::<Events>(),
1162                error_enum_ty: meta_type::<Errors>(),
1163            },
1164            v15::CustomMetadata {
1165                map: Default::default(),
1166            },
1167        )
1168    }
1169
1170    #[test]
1171    fn hash_comparison_trimmed_metadata() {
1172        use subxt_utils_stripmetadata::StripMetadata;
1173
1174        // trim the metadata:
1175        let metadata = metadata_with_pallet_events();
1176        let trimmed_metadata = {
1177            let mut m = metadata.clone();
1178            m.strip_metadata(|e| e == "First", |_| true);
1179            m
1180        };
1181
1182        // Now convert it into our inner repr:
1183        let metadata = Metadata::try_from(metadata).unwrap();
1184        let trimmed_metadata = Metadata::try_from(trimmed_metadata).unwrap();
1185
1186        // test that the hashes are the same:
1187        let hash = MetadataHasher::new(&metadata)
1188            .only_these_pallets(&["First"])
1189            .hash();
1190        let hash_trimmed = MetadataHasher::new(&trimmed_metadata).hash();
1191
1192        assert_eq!(hash, hash_trimmed);
1193    }
1194}