use crate::{
ExtrinsicMetadata, Metadata, OuterEnumsMetadata, PalletMetadata, RuntimeApiMetadata,
RuntimeApiMethodMetadata, StorageEntryMetadata, StorageEntryType,
};
use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, TypeDefVariant, Variant};
use std::collections::HashMap;
const HASH_LEN: usize = 32;
#[repr(u8)]
enum TypeBeingHashed {
Composite,
Variant,
Sequence,
Array,
Tuple,
Primitive,
Compact,
BitSequence,
}
fn hash(data: &[u8]) -> [u8; HASH_LEN] {
sp_core_hashing::twox_256(data)
}
fn xor(a: [u8; HASH_LEN], b: [u8; HASH_LEN]) -> [u8; HASH_LEN] {
let mut out = [0u8; HASH_LEN];
for (idx, (a, b)) in a.into_iter().zip(b).enumerate() {
out[idx] = a ^ b;
}
out
}
macro_rules! count_idents {
() => { 0 };
($n:ident $($rest:ident)*) => { 1 + count_idents!($($rest)*) }
}
macro_rules! concat_and_hash_n {
($name:ident($($arg:ident)+)) => {
fn $name($($arg: &[u8; HASH_LEN]),+) -> [u8; HASH_LEN] {
let mut out = [0u8; HASH_LEN * count_idents!($($arg)+)];
let mut start = 0;
$(
out[start..start+HASH_LEN].copy_from_slice(&$arg[..]);
#[allow(unused_assignments)]
{ start += HASH_LEN; }
)+
hash(&out)
}
}
}
concat_and_hash_n!(concat_and_hash2(a b));
concat_and_hash_n!(concat_and_hash3(a b c));
concat_and_hash_n!(concat_and_hash4(a b c d));
concat_and_hash_n!(concat_and_hash5(a b c d e));
fn get_field_hash(
registry: &PortableRegistry,
field: &Field<PortableForm>,
cache: &mut HashMap<u32, CachedHash>,
) -> [u8; HASH_LEN] {
let field_name_bytes = match &field.name {
Some(name) => hash(name.as_bytes()),
None => [0u8; HASH_LEN],
};
concat_and_hash2(
&field_name_bytes,
&get_type_hash(registry, field.ty.id, cache),
)
}
fn get_variant_hash(
registry: &PortableRegistry,
var: &Variant<PortableForm>,
cache: &mut HashMap<u32, CachedHash>,
) -> [u8; HASH_LEN] {
let variant_name_bytes = hash(var.name.as_bytes());
let variant_field_bytes = var.fields.iter().fold([0u8; HASH_LEN], |bytes, field| {
xor(bytes, get_field_hash(registry, field, cache))
});
concat_and_hash2(&variant_name_bytes, &variant_field_bytes)
}
fn get_type_def_variant_hash(
registry: &PortableRegistry,
variant: &TypeDefVariant<PortableForm>,
only_these_variants: Option<&[&str]>,
cache: &mut HashMap<u32, CachedHash>,
) -> [u8; HASH_LEN] {
let variant_id_bytes = [TypeBeingHashed::Variant as u8; HASH_LEN];
let variant_field_bytes = variant.variants.iter().fold([0u8; HASH_LEN], |bytes, var| {
let should_hash = only_these_variants
.as_ref()
.map(|only_these_variants| only_these_variants.contains(&var.name.as_str()))
.unwrap_or(true);
if should_hash {
xor(bytes, get_variant_hash(registry, var, cache))
} else {
bytes
}
});
concat_and_hash2(&variant_id_bytes, &variant_field_bytes)
}
fn get_type_def_hash(
registry: &PortableRegistry,
ty_def: &TypeDef<PortableForm>,
cache: &mut HashMap<u32, CachedHash>,
) -> [u8; HASH_LEN] {
match ty_def {
TypeDef::Composite(composite) => {
let composite_id_bytes = [TypeBeingHashed::Composite as u8; HASH_LEN];
let composite_field_bytes =
composite
.fields
.iter()
.fold([0u8; HASH_LEN], |bytes, field| {
xor(bytes, get_field_hash(registry, field, cache))
});
concat_and_hash2(&composite_id_bytes, &composite_field_bytes)
}
TypeDef::Variant(variant) => get_type_def_variant_hash(registry, variant, None, cache),
TypeDef::Sequence(sequence) => concat_and_hash2(
&[TypeBeingHashed::Sequence as u8; HASH_LEN],
&get_type_hash(registry, sequence.type_param.id, cache),
),
TypeDef::Array(array) => {
let array_id_bytes = {
let mut a = [0u8; HASH_LEN];
a[0] = TypeBeingHashed::Array as u8;
a[1..5].copy_from_slice(&array.len.to_be_bytes());
a
};
concat_and_hash2(
&array_id_bytes,
&get_type_hash(registry, array.type_param.id, cache),
)
}
TypeDef::Tuple(tuple) => {
let mut bytes = hash(&[TypeBeingHashed::Tuple as u8]);
for field in &tuple.fields {
bytes = concat_and_hash2(&bytes, &get_type_hash(registry, field.id, cache));
}
bytes
}
TypeDef::Primitive(primitive) => {
hash(&[TypeBeingHashed::Primitive as u8, primitive.clone() as u8])
}
TypeDef::Compact(compact) => concat_and_hash2(
&[TypeBeingHashed::Compact as u8; HASH_LEN],
&get_type_hash(registry, compact.type_param.id, cache),
),
TypeDef::BitSequence(bitseq) => concat_and_hash3(
&[TypeBeingHashed::BitSequence as u8; HASH_LEN],
&get_type_hash(registry, bitseq.bit_order_type.id, cache),
&get_type_hash(registry, bitseq.bit_store_type.id, cache),
),
}
}
#[derive(Clone, Debug)]
pub enum CachedHash {
Recursive,
Hash([u8; HASH_LEN]),
}
impl CachedHash {
fn hash(&self) -> [u8; HASH_LEN] {
match &self {
CachedHash::Hash(hash) => *hash,
CachedHash::Recursive => [123; HASH_LEN], }
}
}
pub fn get_type_hash(
registry: &PortableRegistry,
id: u32,
cache: &mut HashMap<u32, CachedHash>,
) -> [u8; HASH_LEN] {
if let Some(cached_hash) = cache.get(&id) {
return cached_hash.hash();
}
cache.insert(id, CachedHash::Recursive);
let ty = registry
.resolve(id)
.expect("Type ID provided by the metadata is registered; qed");
let type_hash = get_type_def_hash(registry, &ty.type_def, cache);
cache.insert(id, CachedHash::Hash(type_hash));
type_hash
}
fn get_extrinsic_hash(
registry: &PortableRegistry,
extrinsic: &ExtrinsicMetadata,
) -> [u8; HASH_LEN] {
let mut cache = HashMap::<u32, CachedHash>::new();
let address_hash = get_type_hash(registry, extrinsic.address_ty, &mut cache);
let signature_hash = get_type_hash(registry, extrinsic.signature_ty, &mut cache);
let extra_hash = get_type_hash(registry, extrinsic.extra_ty, &mut cache);
let mut bytes = concat_and_hash4(
&address_hash,
&signature_hash,
&extra_hash,
&[extrinsic.version; 32],
);
for signed_extension in extrinsic.signed_extensions.iter() {
bytes = concat_and_hash4(
&bytes,
&hash(signed_extension.identifier.as_bytes()),
&get_type_hash(registry, signed_extension.extra_ty, &mut cache),
&get_type_hash(registry, signed_extension.additional_ty, &mut cache),
)
}
bytes
}
fn get_outer_enums_hash(
registry: &PortableRegistry,
enums: &OuterEnumsMetadata,
only_these_variants: Option<&[&str]>,
) -> [u8; HASH_LEN] {
fn get_enum_hash(
registry: &PortableRegistry,
id: u32,
only_these_variants: Option<&[&str]>,
) -> [u8; HASH_LEN] {
let ty = registry
.types
.get(id as usize)
.expect("Metadata should contain enum type in registry");
if let TypeDef::Variant(variant) = &ty.ty.type_def {
get_type_def_variant_hash(registry, variant, only_these_variants, &mut HashMap::new())
} else {
get_type_hash(registry, id, &mut HashMap::new())
}
}
let call_hash = get_enum_hash(registry, enums.call_enum_ty, only_these_variants);
let event_hash = get_enum_hash(registry, enums.event_enum_ty, only_these_variants);
let error_hash = get_enum_hash(registry, enums.error_enum_ty, only_these_variants);
concat_and_hash3(&call_hash, &event_hash, &error_hash)
}
fn get_storage_entry_hash(
registry: &PortableRegistry,
entry: &StorageEntryMetadata,
cache: &mut HashMap<u32, CachedHash>,
) -> [u8; HASH_LEN] {
let mut bytes = concat_and_hash3(
&hash(entry.name.as_bytes()),
&[entry.modifier as u8; HASH_LEN],
&hash(&entry.default),
);
match &entry.entry_type {
StorageEntryType::Plain(ty) => {
concat_and_hash2(&bytes, &get_type_hash(registry, *ty, cache))
}
StorageEntryType::Map {
hashers,
key_ty,
value_ty,
} => {
for hasher in hashers {
bytes = concat_and_hash2(&bytes, &[*hasher as u8; HASH_LEN]);
}
concat_and_hash3(
&bytes,
&get_type_hash(registry, *key_ty, cache),
&get_type_hash(registry, *value_ty, cache),
)
}
}
}
fn get_runtime_method_hash(
registry: &PortableRegistry,
trait_name: &str,
method_metadata: &RuntimeApiMethodMetadata,
cache: &mut HashMap<u32, CachedHash>,
) -> [u8; HASH_LEN] {
let mut bytes = concat_and_hash2(
&hash(trait_name.as_bytes()),
&hash(method_metadata.name.as_bytes()),
);
for input in &method_metadata.inputs {
bytes = concat_and_hash3(
&bytes,
&hash(input.name.as_bytes()),
&get_type_hash(registry, input.ty, cache),
);
}
bytes = concat_and_hash2(
&bytes,
&get_type_hash(registry, method_metadata.output_ty, cache),
);
bytes
}
pub fn get_runtime_trait_hash(trait_metadata: RuntimeApiMetadata) -> [u8; HASH_LEN] {
let mut cache = HashMap::new();
let trait_name = &*trait_metadata.inner.name;
let method_bytes = trait_metadata
.methods()
.fold([0u8; HASH_LEN], |bytes, method_metadata| {
xor(
bytes,
get_runtime_method_hash(
trait_metadata.types,
trait_name,
method_metadata,
&mut cache,
),
)
});
concat_and_hash2(&hash(trait_name.as_bytes()), &method_bytes)
}
pub fn get_storage_hash(pallet: &PalletMetadata, entry_name: &str) -> Option<[u8; HASH_LEN]> {
let storage = pallet.storage()?;
let entry = storage.entry_by_name(entry_name)?;
let hash = get_storage_entry_hash(pallet.types, entry, &mut HashMap::new());
Some(hash)
}
pub fn get_constant_hash(pallet: &PalletMetadata, constant_name: &str) -> Option<[u8; HASH_LEN]> {
let constant = pallet.constant_by_name(constant_name)?;
let bytes = get_type_hash(pallet.types, constant.ty, &mut HashMap::new());
Some(bytes)
}
pub fn get_call_hash(pallet: &PalletMetadata, call_name: &str) -> Option<[u8; HASH_LEN]> {
let call_variant = pallet.call_variant_by_name(call_name)?;
let hash = get_variant_hash(pallet.types, call_variant, &mut HashMap::new());
Some(hash)
}
pub fn get_runtime_api_hash(
runtime_apis: &RuntimeApiMetadata,
method_name: &str,
) -> Option<[u8; HASH_LEN]> {
let trait_name = &*runtime_apis.inner.name;
let method_metadata = runtime_apis.method_by_name(method_name)?;
Some(get_runtime_method_hash(
runtime_apis.types,
trait_name,
method_metadata,
&mut HashMap::new(),
))
}
pub fn get_pallet_hash(pallet: PalletMetadata) -> [u8; HASH_LEN] {
let mut cache = HashMap::<u32, CachedHash>::new();
let registry = pallet.types;
let call_bytes = match pallet.call_ty_id() {
Some(calls) => get_type_hash(registry, calls, &mut cache),
None => [0u8; HASH_LEN],
};
let event_bytes = match pallet.event_ty_id() {
Some(event) => get_type_hash(registry, event, &mut cache),
None => [0u8; HASH_LEN],
};
let error_bytes = match pallet.error_ty_id() {
Some(error) => get_type_hash(registry, error, &mut cache),
None => [0u8; HASH_LEN],
};
let constant_bytes = pallet.constants().fold([0u8; HASH_LEN], |bytes, constant| {
let constant_hash = concat_and_hash2(
&hash(constant.name.as_bytes()),
&get_type_hash(registry, constant.ty(), &mut cache),
);
xor(bytes, constant_hash)
});
let storage_bytes = match pallet.storage() {
Some(storage) => {
let prefix_hash = hash(storage.prefix().as_bytes());
let entries_hash = storage
.entries()
.iter()
.fold([0u8; HASH_LEN], |bytes, entry| {
xor(bytes, get_storage_entry_hash(registry, entry, &mut cache))
});
concat_and_hash2(&prefix_hash, &entries_hash)
}
None => [0u8; HASH_LEN],
};
concat_and_hash5(
&call_bytes,
&event_bytes,
&error_bytes,
&constant_bytes,
&storage_bytes,
)
}
pub struct MetadataHasher<'a> {
metadata: &'a Metadata,
specific_pallets: Option<Vec<&'a str>>,
specific_runtime_apis: Option<Vec<&'a str>>,
}
impl<'a> MetadataHasher<'a> {
pub(crate) fn new(metadata: &'a Metadata) -> Self {
Self {
metadata,
specific_pallets: None,
specific_runtime_apis: None,
}
}
pub fn only_these_pallets<S: AsRef<str>>(&mut self, specific_pallets: &'a [S]) -> &mut Self {
self.specific_pallets = Some(specific_pallets.iter().map(|n| n.as_ref()).collect());
self
}
pub fn only_these_runtime_apis<S: AsRef<str>>(
&mut self,
specific_runtime_apis: &'a [S],
) -> &mut Self {
self.specific_runtime_apis =
Some(specific_runtime_apis.iter().map(|n| n.as_ref()).collect());
self
}
pub fn hash(&self) -> [u8; HASH_LEN] {
let metadata = self.metadata;
let pallet_hash = metadata.pallets().fold([0u8; HASH_LEN], |bytes, pallet| {
let should_hash = self
.specific_pallets
.as_ref()
.map(|specific_pallets| specific_pallets.contains(&pallet.name()))
.unwrap_or(true);
if should_hash {
xor(bytes, get_pallet_hash(pallet))
} else {
bytes
}
});
let apis_hash = metadata
.runtime_api_traits()
.fold([0u8; HASH_LEN], |bytes, api| {
let should_hash = self
.specific_runtime_apis
.as_ref()
.map(|specific_runtime_apis| specific_runtime_apis.contains(&api.name()))
.unwrap_or(true);
if should_hash {
xor(bytes, xor(bytes, get_runtime_trait_hash(api)))
} else {
bytes
}
});
let extrinsic_hash = get_extrinsic_hash(&metadata.types, &metadata.extrinsic);
let runtime_hash =
get_type_hash(&metadata.types, metadata.runtime_ty(), &mut HashMap::new());
let outer_enums_hash = get_outer_enums_hash(
&metadata.types,
&metadata.outer_enums(),
self.specific_pallets.as_deref(),
);
concat_and_hash5(
&pallet_hash,
&apis_hash,
&extrinsic_hash,
&runtime_hash,
&outer_enums_hash,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use bitvec::{order::Lsb0, vec::BitVec};
use frame_metadata::v15;
use scale_info::{meta_type, Registry};
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
struct A {
pub b: Box<B>,
}
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
struct B {
pub a: Box<A>,
}
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
struct AccountId32([u8; HASH_LEN]);
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
enum DigestItem {
PreRuntime(
[::core::primitive::u8; 4usize],
::std::vec::Vec<::core::primitive::u8>,
),
Other(::std::vec::Vec<::core::primitive::u8>),
RuntimeEnvironmentUpdated(((i8, i16), (u32, u64))),
Index(#[codec(compact)] ::core::primitive::u8),
BitSeq(BitVec<u8, Lsb0>),
}
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
struct MetadataTestType {
recursive: A,
composite: AccountId32,
type_def: DigestItem,
}
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
enum Call {
#[codec(index = 0)]
FillBlock { ratio: AccountId32 },
#[codec(index = 1)]
Remark { remark: DigestItem },
}
fn build_default_extrinsic() -> v15::ExtrinsicMetadata {
v15::ExtrinsicMetadata {
version: 0,
signed_extensions: vec![],
address_ty: meta_type::<()>(),
call_ty: meta_type::<()>(),
signature_ty: meta_type::<()>(),
extra_ty: meta_type::<()>(),
}
}
fn default_pallet() -> v15::PalletMetadata {
v15::PalletMetadata {
name: "Test",
storage: None,
calls: None,
event: None,
constants: vec![],
error: None,
index: 0,
docs: vec![],
}
}
fn build_default_pallets() -> Vec<v15::PalletMetadata> {
vec![
v15::PalletMetadata {
name: "First",
calls: Some(v15::PalletCallMetadata {
ty: meta_type::<MetadataTestType>(),
}),
..default_pallet()
},
v15::PalletMetadata {
name: "Second",
index: 1,
calls: Some(v15::PalletCallMetadata {
ty: meta_type::<(DigestItem, AccountId32, A)>(),
}),
..default_pallet()
},
]
}
fn pallets_to_metadata(pallets: Vec<v15::PalletMetadata>) -> Metadata {
v15::RuntimeMetadataV15::new(
pallets,
build_default_extrinsic(),
meta_type::<()>(),
vec![],
v15::OuterEnums {
call_enum_ty: meta_type::<()>(),
event_enum_ty: meta_type::<()>(),
error_enum_ty: meta_type::<()>(),
},
v15::CustomMetadata {
map: Default::default(),
},
)
.try_into()
.expect("can build valid metadata")
}
#[test]
fn different_pallet_index() {
let pallets = build_default_pallets();
let mut pallets_swap = pallets.clone();
let metadata = pallets_to_metadata(pallets);
pallets_swap.swap(0, 1);
pallets_swap[0].index = 0;
pallets_swap[1].index = 1;
let metadata_swap = pallets_to_metadata(pallets_swap);
let hash = MetadataHasher::new(&metadata).hash();
let hash_swap = MetadataHasher::new(&metadata_swap).hash();
assert_eq!(hash, hash_swap);
}
#[test]
fn recursive_type() {
let mut pallet = default_pallet();
pallet.calls = Some(v15::PalletCallMetadata {
ty: meta_type::<A>(),
});
let metadata = pallets_to_metadata(vec![pallet]);
MetadataHasher::new(&metadata).hash();
}
#[test]
fn recursive_types_different_order() {
let mut pallets = build_default_pallets();
pallets[0].calls = Some(v15::PalletCallMetadata {
ty: meta_type::<A>(),
});
pallets[1].calls = Some(v15::PalletCallMetadata {
ty: meta_type::<B>(),
});
pallets[1].index = 1;
let mut pallets_swap = pallets.clone();
let metadata = pallets_to_metadata(pallets);
pallets_swap.swap(0, 1);
pallets_swap[0].index = 0;
pallets_swap[1].index = 1;
let metadata_swap = pallets_to_metadata(pallets_swap);
let hash = MetadataHasher::new(&metadata).hash();
let hash_swap = MetadataHasher::new(&metadata_swap).hash();
assert_eq!(hash, hash_swap);
}
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
struct Aba {
ab: (A, B),
other: A,
}
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
struct Abb {
ab: (A, B),
other: B,
}
#[test]
fn do_not_reuse_visited_type_ids() {
let metadata_hash_with_type = |ty| {
let mut pallets = build_default_pallets();
pallets[0].calls = Some(v15::PalletCallMetadata { ty });
let metadata = pallets_to_metadata(pallets);
MetadataHasher::new(&metadata).hash()
};
let aba_hash = metadata_hash_with_type(meta_type::<Aba>());
let abb_hash = metadata_hash_with_type(meta_type::<Abb>());
assert_ne!(aba_hash, abb_hash);
}
#[test]
fn hash_cache_gets_filled_with_correct_hashes() {
let mut registry = Registry::new();
let a_type_id = registry.register_type(&meta_type::<A>()).id;
let b_type_id = registry.register_type(&meta_type::<B>()).id;
let registry: PortableRegistry = registry.into();
let mut cache = HashMap::new();
let a_hash = get_type_hash(®istry, a_type_id, &mut cache);
let a_hash2 = get_type_hash(®istry, a_type_id, &mut cache);
let b_hash = get_type_hash(®istry, b_type_id, &mut cache);
let CachedHash::Hash(a_cache_hash) = cache[&a_type_id] else { panic!() };
let CachedHash::Hash(b_cache_hash) = cache[&b_type_id] else { panic!() };
assert_eq!(a_hash, a_cache_hash);
assert_eq!(b_hash, b_cache_hash);
assert_eq!(a_hash, a_hash2);
assert_ne!(a_hash, b_hash);
}
#[test]
#[allow(clippy::redundant_clone)]
fn pallet_hash_correctness() {
let compare_pallets_hash = |lhs: &v15::PalletMetadata, rhs: &v15::PalletMetadata| {
let metadata = pallets_to_metadata(vec![lhs.clone()]);
let hash = MetadataHasher::new(&metadata).hash();
let metadata = pallets_to_metadata(vec![rhs.clone()]);
let new_hash = MetadataHasher::new(&metadata).hash();
assert_ne!(hash, new_hash);
};
let mut pallet = default_pallet();
let pallet_lhs = pallet.clone();
pallet.storage = Some(v15::PalletStorageMetadata {
prefix: "Storage",
entries: vec![v15::StorageEntryMetadata {
name: "BlockWeight",
modifier: v15::StorageEntryModifier::Default,
ty: v15::StorageEntryType::Plain(meta_type::<u8>()),
default: vec![],
docs: vec![],
}],
});
compare_pallets_hash(&pallet_lhs, &pallet);
let pallet_lhs = pallet.clone();
pallet.calls = Some(v15::PalletCallMetadata {
ty: meta_type::<Call>(),
});
compare_pallets_hash(&pallet_lhs, &pallet);
let pallet_lhs = pallet.clone();
pallet.event = Some(v15::PalletEventMetadata {
ty: meta_type::<Call>(),
});
compare_pallets_hash(&pallet_lhs, &pallet);
let pallet_lhs = pallet.clone();
pallet.constants = vec![v15::PalletConstantMetadata {
name: "BlockHashCount",
ty: meta_type::<u64>(),
value: vec![96u8, 0, 0, 0],
docs: vec![],
}];
compare_pallets_hash(&pallet_lhs, &pallet);
let pallet_lhs = pallet.clone();
pallet.error = Some(v15::PalletErrorMetadata {
ty: meta_type::<MetadataTestType>(),
});
compare_pallets_hash(&pallet_lhs, &pallet);
}
#[test]
fn metadata_per_pallet_hash_correctness() {
let pallets = build_default_pallets();
let metadata_one = pallets_to_metadata(vec![pallets[0].clone()]);
let metadata_both = pallets_to_metadata(pallets);
let hash = MetadataHasher::new(&metadata_one)
.only_these_pallets(&["First", "Second"])
.hash();
let hash_rhs = MetadataHasher::new(&metadata_one)
.only_these_pallets(&["First"])
.hash();
assert_eq!(hash, hash_rhs, "hashing should ignore non-existant pallets");
let hash_second = MetadataHasher::new(&metadata_both)
.only_these_pallets(&["First"])
.hash();
assert_eq!(
hash_second, hash,
"hashing one pallet should ignore the others"
);
let hash_second = MetadataHasher::new(&metadata_both)
.only_these_pallets(&["First", "Second"])
.hash();
assert_ne!(
hash_second, hash,
"hashing both pallets should produce a different result from hashing just one pallet"
);
}
#[test]
fn field_semantic_changes() {
let to_hash = |meta_ty| {
let pallet = v15::PalletMetadata {
calls: Some(v15::PalletCallMetadata { ty: meta_ty }),
..default_pallet()
};
let metadata = pallets_to_metadata(vec![pallet]);
MetadataHasher::new(&metadata).hash()
};
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
enum EnumA1 {
First { hi: u8, bye: String },
Second(u32),
Third,
}
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
enum EnumA2 {
Second(u32),
Third,
First { bye: String, hi: u8 },
}
assert_eq!(
to_hash(meta_type::<EnumA1>()),
to_hash(meta_type::<EnumA2>())
);
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
struct StructB1 {
hello: bool,
another: [u8; 32],
}
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
struct StructB2 {
another: [u8; 32],
hello: bool,
}
assert_eq!(
to_hash(meta_type::<StructB1>()),
to_hash(meta_type::<StructB2>())
);
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
enum EnumC1 {
First(u8),
}
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
enum EnumC2 {
Second(u8),
}
assert_ne!(
to_hash(meta_type::<EnumC1>()),
to_hash(meta_type::<EnumC2>())
);
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
enum EnumD1 {
First { a: u8 },
}
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
enum EnumD2 {
First { b: u8 },
}
assert_ne!(
to_hash(meta_type::<EnumD1>()),
to_hash(meta_type::<EnumD2>())
);
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
struct StructE1 {
a: u32,
}
#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
struct StructE2 {
b: u32,
}
assert_ne!(
to_hash(meta_type::<StructE1>()),
to_hash(meta_type::<StructE2>())
);
}
}