feat: Vendor pezkuwi-subxt and pezkuwi-zombienet-sdk into monorepo

- Add pezkuwi-subxt crates to vendor/pezkuwi-subxt
- Add pezkuwi-zombienet-sdk crates to vendor/pezkuwi-zombienet-sdk
- Convert git dependencies to path dependencies
- Add vendor crates to workspace members
- Remove test/example crates from vendor (not needed for SDK)
- Fix feature propagation issues detected by zepter
- Fix workspace inheritance for internal dependencies
- All 606 crates now in workspace
- All 6919 internal dependency links verified correct
- No git dependencies remaining
This commit is contained in:
2025-12-22 23:31:24 +03:00
parent 4c8f281051
commit 70ddb6516f
386 changed files with 76759 additions and 36 deletions
+57
View File
@@ -0,0 +1,57 @@
[package]
name = "pezkuwi-subxt-metadata"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
publish = true
autotests = false
license.workspace = true
repository.workspace = true
documentation.workspace = true
homepage.workspace = true
description = "Command line utilities for checking metadata compatibility between nodes."
[features]
default = ["legacy", "std"]
std = ["frame-metadata/std", "scale-info/std"]
# Enable decoding of legacy metadata, too.
# std required by frame-metadata to decode <V14.
legacy = [
"dep:scale-info-legacy",
"dep:scale-type-resolver",
"frame-decode/legacy",
"frame-metadata/legacy",
"std",
]
[dependencies]
codec = { package = "parity-scale-codec", workspace = true, default-features = false, features = ["derive"] }
frame-decode = { workspace = true }
frame-metadata = { workspace = true, default-features = false, features = ["current", "decode"] }
hashbrown = { workspace = true }
pezsp-crypto-hashing = { workspace = true }
scale-info = { workspace = true, default-features = false }
scale-info-legacy = { workspace = true, optional = true }
scale-type-resolver = { workspace = true, optional = true }
thiserror = { workspace = true, default-features = false }
[dev-dependencies]
bitvec = { workspace = true, features = ["alloc"] }
criterion = { workspace = true }
frame-decode = { workspace = true, features = ["legacy-types"] }
pezkuwi-subxt-codegen = { workspace = true }
scale-info = { workspace = true, features = ["bit-vec"] }
[lib]
# Without this, libtest cli opts interfere with criterion benches:
bench = false
[[bench]]
name = "bench"
harness = false
[lints]
workspace = true
+92
View File
@@ -0,0 +1,92 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Benchmarks for metadata hashing.
#![allow(missing_docs)]
use codec::Decode;
use criterion::*;
use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed};
use pezkuwi_subxt_metadata::Metadata;
use std::{fs, path::Path};
fn load_metadata() -> Metadata {
let bytes = fs::read(Path::new("../artifacts/pezkuwi_metadata_full.scale"))
.expect("Cannot read metadata blob");
let meta: RuntimeMetadataPrefixed =
Decode::decode(&mut &*bytes).expect("Cannot decode scale metadata");
match meta.1 {
RuntimeMetadata::V14(v14) => v14.try_into().unwrap(),
RuntimeMetadata::V15(v15) => v15.try_into().unwrap(),
_ => panic!("Unsupported metadata version {:?}", meta.1),
}
}
fn bench_get_metadata_hash(c: &mut Criterion) {
let metadata = load_metadata();
c.bench_function("get_metadata_hash", |b| b.iter(|| metadata.hasher().hash()));
}
fn bench_get_call_hash(c: &mut Criterion) {
let metadata = load_metadata();
let mut group = c.benchmark_group("get_call_hash");
for pallet in metadata.pallets() {
let pallet_name = pallet.name();
let Some(variants) = pallet.call_variants() else {
continue;
};
for variant in variants {
let call_name = &variant.name;
let bench_name = format!("{pallet_name}/{call_name}");
group.bench_function(&bench_name, |b| b.iter(|| pallet.call_hash(call_name)));
}
}
}
fn bench_get_constant_hash(c: &mut Criterion) {
let metadata = load_metadata();
let mut group = c.benchmark_group("get_constant_hash");
for pallet in metadata.pallets() {
let pallet_name = pallet.name();
for constant in pallet.constants() {
let constant_name = constant.name();
let bench_name = format!("{pallet_name}/{constant_name}");
group.bench_function(&bench_name, |b| b.iter(|| pallet.constant_hash(constant_name)));
}
}
}
fn bench_get_storage_hash(c: &mut Criterion) {
let metadata = load_metadata();
let mut group = c.benchmark_group("get_storage_hash");
for pallet in metadata.pallets() {
let pallet_name = pallet.name();
let Some(storage_entries) = pallet.storage() else {
continue;
};
for storage in storage_entries.entries() {
let entry_name = storage.name();
let bench_name = format!("{pallet_name}/{entry_name}");
group.bench_function(&bench_name, |b| b.iter(|| pallet.storage_hash(entry_name)));
}
}
}
criterion_group!(
name = benches;
config = Criterion::default();
targets =
bench_get_metadata_hash,
bench_get_call_hash,
bench_get_constant_hash,
bench_get_storage_hash,
);
criterion_main!(benches);
+410
View File
@@ -0,0 +1,410 @@
mod portable_registry_builder;
#[cfg(test)]
mod tests;
use crate::{
Metadata,
utils::{ordered_map::OrderedMap, variant_index::VariantIndex},
};
use alloc::{borrow::ToOwned, collections::BTreeMap, format, string::ToString, vec::Vec};
use frame_decode::{
constants::{ConstantEntryInfo, ConstantTypeInfo},
extrinsics::ExtrinsicTypeInfo,
runtime_apis::RuntimeApiTypeInfo,
storage::{StorageEntryInfo, StorageTypeInfo},
};
use frame_metadata::v15;
use portable_registry_builder::PortableRegistryBuilder;
use scale_info_legacy::{TypeRegistrySet, type_registry::RuntimeApiName};
/// Options to configure the legacy translating.
pub(crate) struct Opts {
pub sanitize_paths: bool,
pub ignore_not_found: bool,
}
impl Opts {
/// Opts tuned for best compatibility translating.
pub(crate) fn compat() -> Self {
Opts { sanitize_paths: true, ignore_not_found: true }
}
}
macro_rules! from_historic {
($vis:vis fn $fn_name:ident($metadata:path $(, builtin_index: $builtin_index:ident)? )) => {
$vis fn $fn_name(metadata: &$metadata, types: &TypeRegistrySet<'_>, opts: Opts) -> Result<Metadata, Error> {
// This will be used to construct our `PortableRegistry` from old-style types.
let mut portable_registry_builder = PortableRegistryBuilder::new(&types);
portable_registry_builder.ignore_not_found(opts.ignore_not_found);
portable_registry_builder.sanitize_paths(opts.sanitize_paths);
// We use this type in a few places to denote that we don't know how to decode it.
let unknown_type_id = portable_registry_builder.add_type_str("special::Unknown", None)
.map_err(|e| Error::add_type("constructing 'Unknown' type", e))?;
// Pallet metadata
let mut call_index = 0u8;
let mut error_index = 0u8;
let mut event_index = 0u8;
let new_pallets = as_decoded(&metadata.modules).iter().map(|pallet| {
// In older metadatas, calls and event enums can have different indexes
// in a given pallet. Pallets without calls or events don't increment
// the respective index for them.
//
// We assume since errors are non optional, that the pallet index _always_
// increments for errors (no `None`s to skip).
let (call_index, event_index, error_index) = {
let out = (call_index, event_index, error_index);
if pallet.calls.is_some() {
call_index += 1;
}
if pallet.event.is_some() {
event_index += 1;
}
error_index += 1;
out
};
// For v12 and v13 metadata, there is a builtin index for everything in a pallet.
// We enable this logic for those metadatas to get the correct index.
$(
let $builtin_index = true;
let (call_index, event_index, error_index) = if $builtin_index {
(pallet.index, pallet.index, pallet.index)
} else {
(call_index, event_index, error_index)
};
)?
let pallet_name = as_decoded(&pallet.name).to_string();
// Storage entries:
let storage = pallet.storage.as_ref().map(|s| {
let storage = as_decoded(s);
let prefix = as_decoded(&storage.prefix);
let entries = metadata.storage_in_pallet(&pallet_name).map(|entry_name| {
let info = metadata
.storage_info(&pallet_name, &entry_name)
.map_err(|e| Error::StorageInfoError(e.into_owned()))?;
let entry_name = entry_name.into_owned();
let info = info.map_ids(|old_id| {
portable_registry_builder.add_type(old_id)
}).map_err(|e| {
let ctx = format!("adding type used in storage entry {pallet_name}.{entry_name}");
Error::add_type(ctx, e)
})?;
let entry = crate::StorageEntryMetadata {
name: entry_name.clone(),
info: info.into_owned(),
// We don't expose docs via our storage info yet.
docs: Vec::new(),
};
Ok((entry_name, entry))
}).collect::<Result<OrderedMap<_, _>, _>>()?;
Ok(crate::StorageMetadata {
prefix: prefix.clone(),
entries,
})
}).transpose()?;
// Pallet error type is just a builtin type:
let error_ty = portable_registry_builder.add_type_str(&format!("builtin::module::error::{pallet_name}"), None)
.map_err(|e| {
let ctx = format!("converting the error enum for pallet {pallet_name}");
Error::add_type(ctx, e)
})?;
// Pallet calls also just a builtin type:
let call_ty = pallet.calls.as_ref().map(|_| {
portable_registry_builder.add_type_str(&format!("builtin::module::call::{pallet_name}"), None)
.map_err(|e| {
let ctx = format!("converting the call enum for pallet {pallet_name}");
Error::add_type(ctx, e)
})
}).transpose()?;
// Pallet events also just a builtin type:
let event_ty = pallet.event.as_ref().map(|_| {
portable_registry_builder.add_type_str(&format!("builtin::module::event::{pallet_name}"), None)
.map_err(|e| {
let ctx = format!("converting the event enum for pallet {pallet_name}");
Error::add_type(ctx, e)
})
}).transpose()?;
let call_variant_index =
VariantIndex::build(call_ty, portable_registry_builder.types());
let error_variant_index =
VariantIndex::build(Some(error_ty), portable_registry_builder.types());
let event_variant_index =
VariantIndex::build(event_ty, portable_registry_builder.types());
let constants = metadata.constants_in_pallet(&pallet_name).map(|name| {
let name = name.into_owned();
let info = metadata.constant_info(&pallet_name, &name)
.map_err(|e| Error::ConstantInfoError(e.into_owned()))?;
let new_type_id = portable_registry_builder.add_type(info.type_id)
.map_err(|e| {
let ctx = format!("converting the constant {name} for pallet {pallet_name}");
Error::add_type(ctx, e)
})?;
let constant = crate::ConstantMetadata {
name: name.clone(),
ty: new_type_id,
value: info.bytes.to_vec(),
// We don't expose docs via our constant info yet.
docs: Vec::new(),
};
Ok((name, constant))
}).collect::<Result<_,Error>>()?;
let pallet_metadata = crate::PalletMetadataInner {
name: pallet_name.clone(),
call_index,
event_index,
error_index,
storage,
error_ty: Some(error_ty),
call_ty,
event_ty,
call_variant_index,
error_variant_index,
event_variant_index,
constants,
view_functions: Default::default(),
associated_types: Default::default(),
// Pallets did not have docs prior to V15.
docs: Default::default(),
};
Ok((pallet_name, pallet_metadata))
}).collect::<Result<OrderedMap<_,_>,Error>>()?;
// Extrinsic metadata
let new_extrinsic = {
let signature_info = metadata
.extrinsic_signature_info()
.map_err(|e| Error::ExtrinsicInfoError(e.into_owned()))?;
let address_ty_id = portable_registry_builder.add_type(signature_info.address_id)
.map_err(|_| Error::CannotFindAddressType)?;
let signature_ty_id = portable_registry_builder.add_type(signature_info.signature_id)
.map_err(|_| Error::CannotFindCallType)?;
let transaction_extensions = metadata
.extrinsic_extension_info(None)
.map_err(|e| Error::ExtrinsicInfoError(e.into_owned()))?
.extension_ids
.into_iter()
.map(|ext| {
let ext_name = ext.name.into_owned();
let ext_type = portable_registry_builder.add_type(ext.id)
.map_err(|e| {
let ctx = format!("converting the signed extension {ext_name}");
Error::add_type(ctx, e)
})?;
Ok(crate::TransactionExtensionMetadataInner {
identifier: ext_name,
extra_ty: ext_type,
// This only started existing in V14+ metadata, but in any case,
// we don't need to know how to decode the signed payload for
// historic blocks (hopefully), so set to unknown.
additional_ty: unknown_type_id.into()
})
})
.collect::<Result<Vec<_>,Error>>()?;
let transaction_extensions_by_version = BTreeMap::from_iter([(
0,
(0..transaction_extensions.len() as u32).collect()
)]);
crate::ExtrinsicMetadata {
address_ty: address_ty_id.into(),
signature_ty: signature_ty_id.into(),
supported_versions: Vec::from_iter([4]),
transaction_extensions,
transaction_extensions_by_version,
}
};
// Outer enum types
let outer_enums = crate::OuterEnumsMetadata {
call_enum_ty: portable_registry_builder.add_type_str("builtin::Call", None)
.map_err(|e| {
let ctx = format!("constructing the 'builtin::Call' type to put in the OuterEnums metadata");
Error::add_type(ctx, e)
})?,
event_enum_ty: portable_registry_builder.add_type_str("builtin::Event", None)
.map_err(|e| {
let ctx = format!("constructing the 'builtin::Event' type to put in the OuterEnums metadata");
Error::add_type(ctx, e)
})?,
error_enum_ty: portable_registry_builder.add_type_str("builtin::Error", None)
.map_err(|e| {
let ctx = format!("constructing the 'builtin::Error' type to put in the OuterEnums metadata");
Error::add_type(ctx, e)
})?,
};
// These are all the same in V13, but be explicit anyway for clarity.
let pallets_by_call_index = new_pallets
.values()
.iter()
.enumerate()
.map(|(idx,p)| (p.call_index, idx))
.collect();
let pallets_by_error_index = new_pallets
.values()
.iter()
.enumerate()
.map(|(idx,p)| (p.error_index, idx))
.collect();
let pallets_by_event_index = new_pallets
.values()
.iter()
.enumerate()
.map(|(idx,p)| (p.event_index, idx))
.collect();
// This is optional in the sense that Subxt will return an error if it needs to decode this type,
// and I think for historic metadata we wouldn't end up down that path anyway. Historic metadata
// tends to call it just "DispatchError" but search more specific paths first.
let dispatch_error_ty = portable_registry_builder
.try_add_type_str("hardcoded::DispatchError", None)
.or_else(|| portable_registry_builder.try_add_type_str("sp_runtime::DispatchError", None))
.or_else(|| portable_registry_builder.try_add_type_str("DispatchError", None))
.transpose()
.map_err(|e| Error::add_type("constructing DispatchError", e))?;
// Runtime API definitions live with type definitions.
let apis = type_registry_to_runtime_apis(&types, &mut portable_registry_builder)?;
Ok(crate::Metadata {
types: portable_registry_builder.finish(),
pallets: new_pallets,
pallets_by_call_index,
pallets_by_error_index,
pallets_by_event_index,
extrinsic: new_extrinsic,
outer_enums,
dispatch_error_ty,
apis,
// Nothing custom existed in V13
custom: v15::CustomMetadata { map: Default::default() },
})
}}
}
from_historic!(pub fn from_v13(frame_metadata::v13::RuntimeMetadataV13, builtin_index: yes));
from_historic!(pub fn from_v12(frame_metadata::v12::RuntimeMetadataV12, builtin_index: yes));
from_historic!(pub fn from_v11(frame_metadata::v11::RuntimeMetadataV11));
from_historic!(pub fn from_v10(frame_metadata::v10::RuntimeMetadataV10));
from_historic!(pub fn from_v9(frame_metadata::v9::RuntimeMetadataV9));
from_historic!(pub fn from_v8(frame_metadata::v8::RuntimeMetadataV8));
fn as_decoded<A, B>(item: &frame_metadata::decode_different::DecodeDifferent<A, B>) -> &B {
match item {
frame_metadata::decode_different::DecodeDifferent::Encode(_a) => {
panic!("Expecting decoded data")
},
frame_metadata::decode_different::DecodeDifferent::Decoded(b) => b,
}
}
// Obtain Runtime API information from some type registry.
pub fn type_registry_to_runtime_apis(
types: &TypeRegistrySet<'_>,
portable_registry_builder: &mut PortableRegistryBuilder,
) -> Result<OrderedMap<String, crate::RuntimeApiMetadataInner>, Error> {
let mut apis = OrderedMap::new();
let mut trait_name = "";
let mut trait_methods = OrderedMap::new();
for api in types.runtime_apis() {
match api {
RuntimeApiName::Trait(name) => {
if !trait_methods.is_empty() {
apis.push_insert(
trait_name.into(),
crate::RuntimeApiMetadataInner {
name: trait_name.into(),
methods: trait_methods,
docs: Vec::new(),
},
);
}
trait_methods = OrderedMap::new();
trait_name = name;
},
RuntimeApiName::Method(name) => {
let info = types
.runtime_api_info(trait_name, name)
.map_err(|e| Error::RuntimeApiInfoError(e.into_owned()))?;
let info = info.map_ids(|id| {
portable_registry_builder.add_type(id).map_err(|e| {
let c = format!("converting type for runtime API {trait_name}.{name}");
Error::add_type(c, e)
})
})?;
trait_methods.push_insert(
name.to_owned(),
crate::RuntimeApiMethodMetadataInner {
name: name.into(),
info,
docs: Vec::new(),
},
);
},
}
}
Ok(apis)
}
/// An error encountered converting some legacy metadata to our internal format.
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Cannot add a type.
#[error("Cannot add type ({context}): {error}")]
AddTypeError { context: String, error: portable_registry_builder::PortableRegistryAddTypeError },
#[error("Cannot find 'hardcoded::ExtrinsicAddress' type in legacy types")]
CannotFindAddressType,
#[error("Cannot find 'hardcoded::ExtrinsicSignature' type in legacy types")]
CannotFindSignatureType,
#[error(
"Cannot find 'builtin::Call' type in legacy types (this should have been automatically added)"
)]
CannotFindCallType,
#[error("Cannot obtain the storage information we need to convert storage entries")]
StorageInfoError(frame_decode::storage::StorageInfoError<'static>),
#[error("Cannot obtain the extrinsic information we need to convert transaction extensions")]
ExtrinsicInfoError(frame_decode::extrinsics::ExtrinsicInfoError<'static>),
#[error("Cannot obtain the Runtime API information we need")]
RuntimeApiInfoError(frame_decode::runtime_apis::RuntimeApiInfoError<'static>),
#[error("Cannot obtain the Constant information we need")]
ConstantInfoError(frame_decode::constants::ConstantInfoError<'static>),
}
impl Error {
/// A shorthand for the [`Error::AddTypeError`] variant.
fn add_type(
context: impl Into<String>,
error: impl Into<portable_registry_builder::PortableRegistryAddTypeError>,
) -> Self {
Error::AddTypeError { context: context.into(), error: error.into() }
}
}
@@ -0,0 +1,502 @@
use alloc::{
borrow::ToOwned,
collections::{BTreeMap, BTreeSet},
string::ToString,
vec::Vec,
};
use scale_info::{PortableRegistry, PortableType, form::PortableForm};
use scale_info_legacy::{LookupName, TypeRegistrySet, type_registry::TypeRegistryResolveError};
use scale_type_resolver::{
BitsOrderFormat, BitsStoreFormat, FieldIter, PathIter, Primitive, ResolvedTypeVisitor,
UnhandledKind, VariantIter,
};
#[derive(thiserror::Error, Debug)]
pub enum PortableRegistryAddTypeError {
#[error("Error resolving type: {0}")]
ResolveError(#[from] TypeRegistryResolveError),
#[error("Cannot find type '{0}'")]
TypeNotFound(LookupName),
}
/// the purpose of this is to convert a (subset of) [`scale_info_legacy::TypeRegistrySet`]
/// into a [`scale_info::PortableRegistry`]. Type IDs from the former are passed in, and
/// type IDs from the latter are handed back. Calling [`PortableRegistryBuilder::finish()`]
/// then hands back a [`scale_info::PortableRegistry`] which these Ids can be used with.
pub struct PortableRegistryBuilder<'info> {
legacy_types: &'info TypeRegistrySet<'info>,
scale_info_types: PortableRegistry,
old_to_new: BTreeMap<LookupName, u32>,
ignore_not_found: bool,
sanitize_paths: bool,
seen_names_in_default_path: BTreeSet<String>,
}
impl<'info> PortableRegistryBuilder<'info> {
/// Instantiate a new [`PortableRegistryBuilder`], providing the set of
/// legacy types you wish to use to construct modern types from.
pub fn new(legacy_types: &'info TypeRegistrySet<'info>) -> Self {
PortableRegistryBuilder {
legacy_types,
scale_info_types: PortableRegistry { types: Default::default() },
old_to_new: Default::default(),
ignore_not_found: false,
sanitize_paths: false,
seen_names_in_default_path: Default::default(),
}
}
/// If this is enabled, any type that isn't found will be replaced by a "special::Unknown" type
/// instead of a "type not found" error being emitted.
///
/// Default: false
pub fn ignore_not_found(&mut self, ignore: bool) {
self.ignore_not_found = ignore;
}
/// Should type paths be sanitized to make them more amenable to things like codegen?
///
/// Default: false
pub fn sanitize_paths(&mut self, sanitize: bool) {
self.sanitize_paths = sanitize;
}
/// Try adding a type, given its string name and optionally the pallet it's scoped to.
pub fn try_add_type_str(
&mut self,
id: &str,
pallet: Option<&str>,
) -> Option<Result<u32, TypeRegistryResolveError>> {
let mut id = match LookupName::parse(id) {
Ok(id) => id,
Err(e) => {
return Some(Err(TypeRegistryResolveError::LookupNameInvalid(id.to_owned(), e)));
},
};
if let Some(pallet) = pallet {
id = id.in_pallet(pallet);
}
self.try_add_type(id)
}
/// Try adding a type, returning `None` if the type doesn't exist.
pub fn try_add_type(
&mut self,
id: LookupName,
) -> Option<Result<u32, TypeRegistryResolveError>> {
match self.add_type(id) {
Ok(id) => Some(Ok(id)),
Err(PortableRegistryAddTypeError::TypeNotFound(_)) => None,
Err(PortableRegistryAddTypeError::ResolveError(e)) => Some(Err(e)),
}
}
/// Add a new legacy type, giving its string ID/name and, if applicable, the pallet that it's
/// seen in, returning the corresponding "modern" type ID to use in its place, or an error if
/// something does wrong.
pub fn add_type_str(
&mut self,
id: &str,
pallet: Option<&str>,
) -> Result<u32, PortableRegistryAddTypeError> {
let mut id = LookupName::parse(id)
.map_err(|e| TypeRegistryResolveError::LookupNameInvalid(id.to_owned(), e))?;
if let Some(pallet) = pallet {
id = id.in_pallet(pallet);
}
self.add_type(id)
}
/// Add a new legacy type, returning the corresponding "modern" type ID to use in
/// its place, or an error if something does wrong.
pub fn add_type(&mut self, id: LookupName) -> Result<u32, PortableRegistryAddTypeError> {
if let Some(new_id) = self.old_to_new.get(&id) {
return Ok(*new_id);
}
// Assign a new ID immediately to prevent any recursion. If we don't do this, then
// recursive types (ie types that contain themselves) will lead to a stack overflow.
// with this, we assign IDs up front, so the ID is returned immediately on recursing.
let new_id = self.scale_info_types.types.len() as u32;
// Add a placeholder type to "reserve" this ID.
self.scale_info_types.types.push(PortableType {
id: new_id,
ty: scale_info::Type::new(
scale_info::Path { segments: vec![] },
core::iter::empty(),
scale_info::TypeDef::Variant(scale_info::TypeDefVariant { variants: vec![] }),
Default::default(),
),
});
// Cache the ID so that recursing calls bail early.
self.old_to_new.insert(id.clone(), new_id);
let visitor = PortableRegistryVisitor { builder: &mut *self, current_type: &id };
match visitor.builder.legacy_types.resolve_type(id.clone(), visitor) {
Ok(Ok(ty)) => {
self.scale_info_types.types[new_id as usize].ty = ty;
Ok(new_id)
},
Ok(Err(e)) => {
self.old_to_new.remove(&id);
Err(e)
},
Err(e) => {
self.old_to_new.remove(&id);
Err(e.into())
},
}
}
/// Return the current [`scale_info::PortableRegistry`].
pub fn types(&self) -> &PortableRegistry {
&self.scale_info_types
}
/// Finish adding types and return the modern type registry.
pub fn finish(self) -> PortableRegistry {
self.scale_info_types
}
}
struct PortableRegistryVisitor<'a, 'info> {
builder: &'a mut PortableRegistryBuilder<'info>,
current_type: &'a LookupName,
}
impl<'a, 'info> ResolvedTypeVisitor<'info> for PortableRegistryVisitor<'a, 'info> {
type TypeId = LookupName;
type Value = Result<scale_info::Type<PortableForm>, PortableRegistryAddTypeError>;
fn visit_unhandled(self, kind: UnhandledKind) -> Self::Value {
panic!("A handler exists for every type, but visit_unhandled({kind:?}) was called");
}
fn visit_not_found(self) -> Self::Value {
if self.builder.ignore_not_found {
// Return the "unknown" type if we're ignoring not found types:
Ok(unknown_type())
} else {
// Otherwise just return an error at this point:
Err(PortableRegistryAddTypeError::TypeNotFound(self.current_type.clone()))
}
}
fn visit_primitive(self, primitive: Primitive) -> Self::Value {
let p = match primitive {
Primitive::Bool => scale_info::TypeDefPrimitive::Bool,
Primitive::Char => scale_info::TypeDefPrimitive::Char,
Primitive::Str => scale_info::TypeDefPrimitive::Str,
Primitive::U8 => scale_info::TypeDefPrimitive::U8,
Primitive::U16 => scale_info::TypeDefPrimitive::U16,
Primitive::U32 => scale_info::TypeDefPrimitive::U32,
Primitive::U64 => scale_info::TypeDefPrimitive::U64,
Primitive::U128 => scale_info::TypeDefPrimitive::U128,
Primitive::U256 => scale_info::TypeDefPrimitive::U256,
Primitive::I8 => scale_info::TypeDefPrimitive::I8,
Primitive::I16 => scale_info::TypeDefPrimitive::I16,
Primitive::I32 => scale_info::TypeDefPrimitive::I32,
Primitive::I64 => scale_info::TypeDefPrimitive::I64,
Primitive::I128 => scale_info::TypeDefPrimitive::I128,
Primitive::I256 => scale_info::TypeDefPrimitive::I256,
};
Ok(scale_info::Type::new(
Default::default(),
core::iter::empty(),
scale_info::TypeDef::Primitive(p),
Default::default(),
))
}
fn visit_sequence<Path: PathIter<'info>>(
self,
path: Path,
inner_type_id: Self::TypeId,
) -> Self::Value {
let inner_id = self.builder.add_type(inner_type_id)?;
let path = scale_info::Path { segments: prepare_path(path, self.builder) };
Ok(scale_info::Type::new(
path,
core::iter::empty(),
scale_info::TypeDef::Sequence(scale_info::TypeDefSequence {
type_param: inner_id.into(),
}),
Default::default(),
))
}
fn visit_composite<Path, Fields>(self, path: Path, fields: Fields) -> Self::Value
where
Path: PathIter<'info>,
Fields: FieldIter<'info, Self::TypeId>,
{
let path = scale_info::Path { segments: prepare_path(path, self.builder) };
let mut scale_info_fields = Vec::<scale_info::Field<_>>::new();
for field in fields {
let type_name = field.id.to_string();
let id = self.builder.add_type(field.id)?;
scale_info_fields.push(scale_info::Field {
name: field.name.map(Into::into),
ty: id.into(),
type_name: Some(type_name),
docs: Default::default(),
});
}
Ok(scale_info::Type::new(
path,
core::iter::empty(),
scale_info::TypeDef::Composite(scale_info::TypeDefComposite {
fields: scale_info_fields,
}),
Default::default(),
))
}
fn visit_array(self, inner_type_id: LookupName, len: usize) -> Self::Value {
let inner_id = self.builder.add_type(inner_type_id)?;
Ok(scale_info::Type::new(
Default::default(),
core::iter::empty(),
scale_info::TypeDef::Array(scale_info::TypeDefArray {
len: len as u32,
type_param: inner_id.into(),
}),
Default::default(),
))
}
fn visit_tuple<TypeIds>(self, type_ids: TypeIds) -> Self::Value
where
TypeIds: ExactSizeIterator<Item = Self::TypeId>,
{
let mut scale_info_fields = Vec::new();
for old_id in type_ids {
let new_id = self.builder.add_type(old_id)?;
scale_info_fields.push(new_id.into());
}
Ok(scale_info::Type::new(
Default::default(),
core::iter::empty(),
scale_info::TypeDef::Tuple(scale_info::TypeDefTuple { fields: scale_info_fields }),
Default::default(),
))
}
fn visit_variant<Path, Fields, Var>(self, path: Path, variants: Var) -> Self::Value
where
Path: PathIter<'info>,
Fields: FieldIter<'info, Self::TypeId>,
Var: VariantIter<'info, Fields>,
{
let path = scale_info::Path { segments: prepare_path(path, self.builder) };
let mut scale_info_variants = Vec::new();
for variant in variants {
let mut scale_info_variant_fields = Vec::<scale_info::Field<_>>::new();
for field in variant.fields {
let type_name = field.id.to_string();
let id = self.builder.add_type(field.id)?;
scale_info_variant_fields.push(scale_info::Field {
name: field.name.map(Into::into),
ty: id.into(),
type_name: Some(type_name),
docs: Default::default(),
});
}
scale_info_variants.push(scale_info::Variant {
name: variant.name.to_owned(),
index: variant.index,
fields: scale_info_variant_fields,
docs: Default::default(),
})
}
Ok(scale_info::Type::new(
path,
core::iter::empty(),
scale_info::TypeDef::Variant(scale_info::TypeDefVariant {
variants: scale_info_variants,
}),
Default::default(),
))
}
fn visit_compact(self, inner_type_id: Self::TypeId) -> Self::Value {
let inner_id = self.builder.add_type(inner_type_id)?;
// Configure the path and type params to maximise compat.
let path = ["parity_scale_codec", "Compact"].into_iter().map(ToOwned::to_owned).collect();
let type_params =
[scale_info::TypeParameter { name: "T".to_owned(), ty: Some(inner_id.into()) }];
Ok(scale_info::Type::new(
scale_info::Path { segments: path },
type_params,
scale_info::TypeDef::Compact(scale_info::TypeDefCompact {
type_param: inner_id.into(),
}),
Default::default(),
))
}
fn visit_bit_sequence(
self,
store_format: BitsStoreFormat,
order_format: BitsOrderFormat,
) -> Self::Value {
// These order types are added by default into a `TypeRegistry`, so we
// expect them to exist. Parsing should always succeed.
let order_ty_str = match order_format {
BitsOrderFormat::Lsb0 => "bitvec::order::Lsb0",
BitsOrderFormat::Msb0 => "bitvec::order::Msb0",
};
let order_ty = LookupName::parse(order_ty_str).unwrap();
let new_order_ty = self.builder.add_type(order_ty)?;
// The store types also exist by default. Parsing should always succeed.
let store_ty_str = match store_format {
BitsStoreFormat::U8 => "u8",
BitsStoreFormat::U16 => "u16",
BitsStoreFormat::U32 => "u32",
BitsStoreFormat::U64 => "u64",
};
let store_ty = LookupName::parse(store_ty_str).unwrap();
let new_store_ty = self.builder.add_type(store_ty)?;
// Configure the path and type params to look like BitVec's to try
// and maximise compatibility.
let path = ["bitvec", "vec", "BitVec"].into_iter().map(ToOwned::to_owned).collect();
let type_params = [
scale_info::TypeParameter { name: "Store".to_owned(), ty: Some(new_store_ty.into()) },
scale_info::TypeParameter { name: "Order".to_owned(), ty: Some(new_order_ty.into()) },
];
Ok(scale_info::Type::new(
scale_info::Path { segments: path },
type_params,
scale_info::TypeDef::BitSequence(scale_info::TypeDefBitSequence {
bit_order_type: new_order_ty.into(),
bit_store_type: new_store_ty.into(),
}),
Default::default(),
))
}
}
fn prepare_path<'info, Path: PathIter<'info>>(
path: Path,
builder: &mut PortableRegistryBuilder<'_>,
) -> Vec<String> {
// If no sanitizint, just return the path as-is.
if !builder.sanitize_paths {
return path.map(|p| p.to_owned()).collect();
}
/// Names of prelude types. For codegen to work, any type that _isn't_ one of these must
/// have a path that is sensible and can be converted to module names.
static PRELUDE_TYPE_NAMES: [&str; 24] = [
"Vec",
"Option",
"Result",
"Cow",
"BTreeMap",
"BTreeSet",
"BinaryHeap",
"VecDeque",
"LinkedList",
"Range",
"RangeInclusive",
"NonZeroI8",
"NonZeroU8",
"NonZeroI16",
"NonZeroU16",
"NonZeroI32",
"NonZeroU32",
"NonZeroI64",
"NonZeroU64",
"NonZeroI128",
"NonZeroU128",
"NonZeroIsize",
"NonZeroUsize",
"Duration",
];
let path: Vec<&str> = path.collect();
// No path should be empty; at least the type name should be present.
if path.is_empty() {
panic!(
"Empty path is not expected when converting legacy type; type name expected at least"
);
}
// The special::Unknown type can be returned as is; dupe paths allowed.
if path.len() == 2 && path[0] == "special" && path[1] == "Unknown" {
return vec!["special".to_owned(), "Unknown".to_owned()];
}
// If non-prelude type has no path, give it one.
if path.len() == 1 && !PRELUDE_TYPE_NAMES.contains(&path[0]) {
return vec![
"other".to_owned(),
prepare_ident(path[0], &mut builder.seen_names_in_default_path),
];
}
// Non-compliant paths are converted to our default path
let non_compliant_path = path[0..path.len() - 1].iter().any(|&p| {
p.is_empty() ||
p.starts_with(|c: char| !c.is_ascii_alphabetic()) ||
p.contains(|c: char| !c.is_ascii_alphanumeric() || c.is_ascii_uppercase())
});
if non_compliant_path {
let last = *path.last().unwrap();
return vec![
"other".to_owned(),
prepare_ident(last, &mut builder.seen_names_in_default_path),
];
}
// If path happens by chance to be ["other", Foo] then ensure Foo isn't duped
if path.len() == 2 && path[0] == "other" {
return vec![
"other".to_owned(),
prepare_ident(path[1], &mut builder.seen_names_in_default_path),
];
}
path.iter().map(|&p| p.to_owned()).collect()
}
fn prepare_ident(base_ident: &str, seen: &mut BTreeSet<String>) -> String {
let mut n = 1;
let mut ident = base_ident.to_owned();
while !seen.insert(ident.clone()) {
ident = format!("{base_ident}{n}");
n += 1;
}
ident
}
fn unknown_type() -> scale_info::Type<PortableForm> {
scale_info::Type::new(
scale_info::Path { segments: Vec::from_iter(["special".to_owned(), "Unknown".to_owned()]) },
core::iter::empty(),
scale_info::TypeDef::Variant(scale_info::TypeDefVariant { variants: Vec::new() }),
Default::default(),
)
}
+458
View File
@@ -0,0 +1,458 @@
use super::*;
use alloc::collections::BTreeSet;
use codec::Decode;
use core::str::FromStr;
use frame_decode::{constants::ConstantTypeInfo, runtime_apis::RuntimeApiEntryInfo};
use frame_metadata::RuntimeMetadata;
use scale_info_legacy::LookupName;
use scale_type_resolver::TypeResolver;
/// Load some legacy kusama metadata from our artifacts.
fn legacy_kusama_metadata(version: u8) -> (u64, RuntimeMetadata) {
const VERSIONS: [(u8, u64, &str); 5] = [
(9, 1021, "metadata_v9_1021.scale"),
(10, 1038, "metadata_v10_1038.scale"),
(11, 1045, "metadata_v11_1045.scale"),
(12, 2025, "metadata_v12_2025.scale"),
(13, 9030, "metadata_v13_9030.scale"),
];
let (spec_version, filename) = VERSIONS
.iter()
.find(|(v, _spec_version, _filename)| *v == version)
.map(|(_, spec_version, name)| (*spec_version, *name))
.unwrap_or_else(|| panic!("v{version} metadata artifact does not exist"));
let mut path = std::path::PathBuf::from_str("../artifacts/kusama/").unwrap();
path.push(filename);
let bytes = std::fs::read(path).expect("Could not read file");
let metadata = RuntimeMetadata::decode(&mut &*bytes).expect("Could not SCALE decode metadata");
(spec_version, metadata)
}
/// Load our kusama types.
/// TODO: This is WRONG at the moment; change to point to kusama types when they exist:
fn kusama_types() -> scale_info_legacy::ChainTypeRegistry {
frame_decode::legacy_types::pezkuwi::relay_chain()
}
/// Sanitizing paths changes things between old and new, so disable this in tests by default
/// so that we can compare paths and check that by default things translate identically.
/// Tests assume that ignore_not_found is enabled, which converts not found types to
/// special::Unknown instead of returning an error.
fn test_opts() -> super::Opts {
super::Opts { sanitize_paths: false, ignore_not_found: true }
}
/// Return a pair of original metadata + converted pezkuwi_subxt_metadata::Metadata
fn metadata_pair(
version: u8,
opts: super::Opts,
) -> (TypeRegistrySet<'static>, RuntimeMetadata, crate::Metadata) {
let (spec_version, metadata) = legacy_kusama_metadata(version);
let types = kusama_types();
// Extend the types with builtins.
let types_for_spec = {
let mut types_for_spec = types.for_spec_version(spec_version).to_owned();
let extended_types =
frame_decode::helpers::type_registry_from_metadata_any(&metadata).unwrap();
types_for_spec.prepend(extended_types);
types_for_spec
};
let pezkuwi_subxt_metadata = match &metadata {
RuntimeMetadata::V9(m) => super::from_v9(m, &types_for_spec, opts),
RuntimeMetadata::V10(m) => super::from_v10(m, &types_for_spec, opts),
RuntimeMetadata::V11(m) => super::from_v11(m, &types_for_spec, opts),
RuntimeMetadata::V12(m) => super::from_v12(m, &types_for_spec, opts),
RuntimeMetadata::V13(m) => super::from_v13(m, &types_for_spec, opts),
_ => panic!("Metadata version {} not expected", metadata.version()),
}
.expect("Could not convert to pezkuwi_subxt_metadata::Metadata");
(types_for_spec, metadata, pezkuwi_subxt_metadata)
}
/// A representation of the shape of some type that we can compare across metadatas.
#[derive(PartialEq, Debug, Clone)]
enum Shape {
Array(Box<Shape>, usize),
BitSequence(scale_type_resolver::BitsStoreFormat, scale_type_resolver::BitsOrderFormat),
Compact(Box<Shape>),
Composite(Vec<String>, Vec<(Option<String>, Shape)>),
Primitive(scale_type_resolver::Primitive),
Sequence(Vec<String>, Box<Shape>),
Tuple(Vec<Shape>),
Variant(Vec<String>, Vec<Variant>),
// This is very important for performance; if we've already seen a variant at some path,
// we'll return just the variant path next time in this, to avoid duplicating lots of
// variants. This also eliminates recursion, since variants allow for it.
SeenVariant(Vec<String>),
}
#[derive(PartialEq, Debug, Clone)]
struct Variant {
index: u8,
name: String,
fields: Vec<(Option<String>, Shape)>,
}
impl Shape {
/// convert some modern type definition into a [`Shape`].
fn from_modern_type(id: u32, types: &scale_info::PortableRegistry) -> Shape {
let mut seen_variants = BTreeSet::new();
Shape::from_modern_type_inner(id, &mut seen_variants, types)
}
fn from_modern_type_inner(
id: u32,
seen_variants: &mut BTreeSet<Vec<String>>,
types: &scale_info::PortableRegistry,
) -> Shape {
let visitor =
scale_type_resolver::visitor::new((seen_variants, types), |_, _| panic!("Unhandled"))
.visit_array(|(seen_variants, types), type_id, len| {
let inner = Shape::from_modern_type_inner(type_id, seen_variants, types);
Shape::Array(Box::new(inner), len)
})
.visit_bit_sequence(|_, store, order| Shape::BitSequence(store, order))
.visit_compact(|(seen_variants, types), type_id| {
let inner = Shape::from_modern_type_inner(type_id, seen_variants, types);
Shape::Compact(Box::new(inner))
})
.visit_composite(|(seen_variants, types), path, fields| {
let path = path.map(|p| p.to_owned()).collect();
let inners = fields
.map(|field| {
let name = field.name.map(|n| n.to_owned());
let inner =
Shape::from_modern_type_inner(field.id, seen_variants, types);
(name, inner)
})
.collect();
Shape::Composite(path, inners)
})
.visit_primitive(|_types, prim| Shape::Primitive(prim))
.visit_sequence(|(seen_variants, types), path, type_id| {
let path = path.map(|p| p.to_owned()).collect();
let inner = Shape::from_modern_type_inner(type_id, seen_variants, types);
Shape::Sequence(path, Box::new(inner))
})
.visit_tuple(|(seen_variants, types), fields| {
let inners = fields
.map(|field| Shape::from_modern_type_inner(field, seen_variants, types))
.collect();
Shape::Tuple(inners)
})
.visit_variant(|(seen_variants, types), path, variants| {
let path: Vec<String> = path.map(|p| p.to_owned()).collect();
// very important to avoid recursion and performance costs:
if !seen_variants.insert(path.clone()) {
return Shape::SeenVariant(path);
}
let variants = variants
.map(|v| Variant {
index: v.index,
name: v.name.to_owned(),
fields: v
.fields
.map(|field| {
let name = field.name.map(|n| n.to_owned());
let inner = Shape::from_modern_type_inner(
field.id,
seen_variants,
types,
);
(name, inner)
})
.collect(),
})
.collect();
Shape::Variant(path, variants)
})
.visit_not_found(|_types| {
panic!("PortableRegistry should not have a type which can't be found")
});
types.resolve_type(id, visitor).unwrap()
}
/// convert some historic type definition into a [`Shape`].
fn from_legacy_type(name: &LookupName, types: &TypeRegistrySet<'_>) -> Shape {
let mut seen_variants = BTreeSet::new();
Shape::from_legacy_type_inner(name.clone(), &mut seen_variants, types)
}
fn from_legacy_type_inner(
id: LookupName,
seen_variants: &mut BTreeSet<Vec<String>>,
types: &TypeRegistrySet<'_>,
) -> Shape {
let visitor =
scale_type_resolver::visitor::new((seen_variants, types), |_, _| panic!("Unhandled"))
.visit_array(|(seen_variants, types), type_id, len| {
let inner = Shape::from_legacy_type_inner(type_id, seen_variants, types);
Shape::Array(Box::new(inner), len)
})
.visit_bit_sequence(|_types, store, order| Shape::BitSequence(store, order))
.visit_compact(|(seen_variants, types), type_id| {
let inner = Shape::from_legacy_type_inner(type_id, seen_variants, types);
Shape::Compact(Box::new(inner))
})
.visit_composite(|(seen_variants, types), path, fields| {
let path = path.map(|p| p.to_owned()).collect();
let inners = fields
.map(|field| {
let name = field.name.map(|n| n.to_owned());
let inner =
Shape::from_legacy_type_inner(field.id, seen_variants, types);
(name, inner)
})
.collect();
Shape::Composite(path, inners)
})
.visit_primitive(|_types, prim| Shape::Primitive(prim))
.visit_sequence(|(seen_variants, types), path, type_id| {
let path = path.map(|p| p.to_owned()).collect();
let inner = Shape::from_legacy_type_inner(type_id, seen_variants, types);
Shape::Sequence(path, Box::new(inner))
})
.visit_tuple(|(seen_variants, types), fields| {
let inners = fields
.map(|field| Shape::from_legacy_type_inner(field, seen_variants, types))
.collect();
Shape::Tuple(inners)
})
.visit_variant(|(seen_variants, types), path, variants| {
let path: Vec<String> = path.map(|p| p.to_owned()).collect();
// very important to avoid recursion and performance costs:
if !seen_variants.insert(path.clone()) {
return Shape::SeenVariant(path);
}
let variants = variants
.map(|v| Variant {
index: v.index,
name: v.name.to_owned(),
fields: v
.fields
.map(|field| {
let name = field.name.map(|n| n.to_owned());
let inner = Shape::from_legacy_type_inner(
field.id,
seen_variants,
types,
);
(name, inner)
})
.collect(),
})
.collect();
Shape::Variant(path, variants)
})
.visit_not_found(|(seen_variants, _)| {
// When we convert legacy to modern types, any types we don't find
// are replaced with empty variants (since we can't have dangling types
// in our new PortableRegistry). Do the same here so they compare equal.
Shape::from_legacy_type_inner(
LookupName::parse("special::Unknown").unwrap(),
seen_variants,
types,
)
});
types.resolve_type(id, visitor).unwrap()
}
}
// Go over all of the constants listed via frame-decode and check that our old
// and new metadatas both have identical output.
macro_rules! constants_eq {
($name:ident, $version:literal, $version_path:ident) => {
#[test]
fn $name() {
let (old_types, old_md, new_md) = metadata_pair($version, test_opts());
let RuntimeMetadata::$version_path(old_md) = old_md else { panic!("Wrong version") };
let old: Vec<_> = old_md
.constant_tuples()
.map(|(p, n)| old_md.constant_info(&p, &n).unwrap())
.map(|c| (c.bytes.to_owned(), Shape::from_legacy_type(&c.type_id, &old_types)))
.collect();
let new: Vec<_> = new_md
.constant_tuples()
.map(|(p, n)| new_md.constant_info(&p, &n).unwrap())
.map(|c| (c.bytes.to_owned(), Shape::from_modern_type(c.type_id, new_md.types())))
.collect();
assert_eq!(old, new);
}
};
}
constants_eq!(v9_constants_eq, 9, V9);
constants_eq!(v10_constants_eq, 10, V10);
constants_eq!(v11_constants_eq, 11, V11);
constants_eq!(v12_constants_eq, 12, V12);
constants_eq!(v13_constants_eq, 13, V13);
/// Make sure all Runtime APIs are the same once translated.
#[test]
fn runtime_apis() {
for version in 9..=13 {
let (old_types, _old_md, new_md) = metadata_pair(version, test_opts());
let old: Vec<_> = old_types
.runtime_api_tuples()
.map(|(p, n)| {
old_types
.runtime_api_info(&p, &n)
.unwrap()
.map_ids(|id| Ok::<_, ()>(Shape::from_legacy_type(&id, &old_types)))
.unwrap()
})
.collect();
let new: Vec<_> = new_md
.runtime_api_tuples()
.map(|(p, n)| {
new_md
.runtime_api_info(&p, &n)
.unwrap()
.map_ids(|id| Ok::<_, ()>(Shape::from_modern_type(id, new_md.types())))
.unwrap()
})
.collect();
assert_eq!(old, new);
}
}
macro_rules! storage_eq {
($name:ident, $version:literal, $version_path:ident) => {
#[test]
fn $name() {
let (old_types, old_md, new_md) = metadata_pair($version, test_opts());
let RuntimeMetadata::$version_path(old_md) = old_md else { panic!("Wrong version") };
let old: Vec<_> = old_md
.storage_tuples()
.map(|(p, n)| {
let info = old_md
.storage_info(&p, &n)
.unwrap()
.map_ids(|id| Ok::<_, ()>(Shape::from_legacy_type(&id, &old_types)))
.unwrap();
(p.into_owned(), n.into_owned(), info)
})
.collect();
let new: Vec<_> = new_md
.storage_tuples()
.map(|(p, n)| {
let info = new_md
.storage_info(&p, &n)
.unwrap()
.map_ids(|id| Ok::<_, ()>(Shape::from_modern_type(id, new_md.types())))
.unwrap();
(p.into_owned(), n.into_owned(), info)
})
.collect();
if old.len() != new.len() {
panic!("Storage entries for version 9 metadata differ in length");
}
for (old, new) in old.into_iter().zip(new.into_iter()) {
assert_eq!((&old.0, &old.1), (&new.0, &new.1), "Storage entry mismatch");
assert_eq!(old.2, new.2, "Storage entry {}.{} does not match!", old.0, old.1);
}
}
};
}
storage_eq!(v9_storage_eq, 9, V9);
storage_eq!(v10_storage_eq, 10, V10);
storage_eq!(v11_storage_eq, 11, V11);
storage_eq!(v12_storage_eq, 12, V12);
storage_eq!(v13_storage_eq, 13, V13);
#[test]
fn builtin_call() {
for version in 9..=13 {
let (old_types, _old_md, new_md) = metadata_pair(version, test_opts());
let old = Shape::from_legacy_type(&LookupName::parse("builtin::Call").unwrap(), &old_types);
let new = Shape::from_modern_type(new_md.outer_enums.call_enum_ty, new_md.types());
assert_eq!(old, new, "Call types do not match in metadata V{version}!");
}
}
#[test]
fn builtin_error() {
for version in 9..=13 {
let (old_types, _old_md, new_md) = metadata_pair(version, test_opts());
let old =
Shape::from_legacy_type(&LookupName::parse("builtin::Error").unwrap(), &old_types);
let new = Shape::from_modern_type(new_md.outer_enums.error_enum_ty, new_md.types());
assert_eq!(old, new, "Error types do not match in metadata V{version}!");
}
}
#[test]
fn builtin_event() {
for version in 9..=13 {
let (old_types, _old_md, new_md) = metadata_pair(version, test_opts());
let old =
Shape::from_legacy_type(&LookupName::parse("builtin::Event").unwrap(), &old_types);
let new = Shape::from_modern_type(new_md.outer_enums.event_enum_ty, new_md.types());
assert_eq!(old, new, "Event types do not match in metadata V{version}!");
}
}
#[test]
fn codegen_works() {
for version in 9..=13 {
// We need to do this against `pezkuwi_subxt_codegen::Metadata` and so cannot re-use our
// test functions for it. This is because the compiler sees some difference between
// `subxct_codegen::Metadata` and `crate::Metadata` even though they should be identical.
let new_md = {
let (spec_version, metadata) = legacy_kusama_metadata(version);
let types = kusama_types();
let types_for_spec = {
let mut types_for_spec = types.for_spec_version(spec_version).to_owned();
let extended_types =
frame_decode::helpers::type_registry_from_metadata_any(&metadata).unwrap();
types_for_spec.prepend(extended_types);
types_for_spec
};
match &metadata {
RuntimeMetadata::V9(m) =>
pezkuwi_subxt_codegen::Metadata::from_v9(m, &types_for_spec),
RuntimeMetadata::V10(m) =>
pezkuwi_subxt_codegen::Metadata::from_v10(m, &types_for_spec),
RuntimeMetadata::V11(m) =>
pezkuwi_subxt_codegen::Metadata::from_v11(m, &types_for_spec),
RuntimeMetadata::V12(m) =>
pezkuwi_subxt_codegen::Metadata::from_v12(m, &types_for_spec),
RuntimeMetadata::V13(m) =>
pezkuwi_subxt_codegen::Metadata::from_v13(m, &types_for_spec),
_ => panic!("Metadata version {} not expected", metadata.version()),
}
.expect("Could not convert to pezkuwi_subxt_metadata::Metadata")
};
// We only test that generation succeeds without any errors, not necessarily that it's 100%
// useful:
let codegen = pezkuwi_subxt_codegen::CodegenBuilder::new();
let _ = codegen
.generate(new_md)
.map_err(|e| e.into_compile_error())
.unwrap_or_else(|e| panic!("Codegen failed for metadata V{version}: {e}"));
}
}
+89
View File
@@ -0,0 +1,89 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use alloc::string::String;
use thiserror::Error as DeriveError;
mod v14;
mod v15;
mod v16;
/// Legacy translation hidden behind the corresponding feature flag.
#[cfg(feature = "legacy")]
pub mod legacy;
/// The metadata versions that we support converting into [`crate::Metadata`].
/// These are ordest from highest to lowest, so that the metadata we'd want to
/// pick first is first in the array.
pub const SUPPORTED_METADATA_VERSIONS: [u32; 3] = [16, 15, 14];
/// An error emitted if something goes wrong converting [`frame_metadata`]
/// types into [`crate::Metadata`].
#[derive(Debug, PartialEq, Eq, DeriveError)]
#[non_exhaustive]
pub enum TryFromError {
/// Type missing from type registry
#[error("Type id {0} is expected but not found in the type registry")]
TypeNotFound(u32),
/// Type was not a variant/enum type
#[error("Type {0} was not a variant/enum type, but is expected to be one")]
VariantExpected(u32),
/// An unsupported metadata version was provided.
#[error("Cannot convert v{0} metadata into Metadata type")]
UnsupportedMetadataVersion(u32),
/// Type name missing from type registry
#[error("Type name {0} is expected but not found in the type registry")]
TypeNameNotFound(String),
/// Invalid type path.
#[error("Type has an invalid path {0}")]
InvalidTypePath(String),
/// Cannot decode storage entry information.
#[error("Error decoding storage entry information: {0}")]
StorageInfoError(#[from] frame_decode::storage::StorageInfoError<'static>),
/// Cannot decode Runtime API information.
#[error("Error decoding Runtime API information: {0}")]
RuntimeInfoError(#[from] frame_decode::runtime_apis::RuntimeApiInfoError<'static>),
/// Cannot decode View Function information.
#[error("Error decoding View Function information: {0}")]
ViewFunctionInfoError(#[from] frame_decode::view_functions::ViewFunctionInfoError<'static>),
}
impl TryFrom<frame_metadata::RuntimeMetadataPrefixed> for crate::Metadata {
type Error = TryFromError;
fn try_from(value: frame_metadata::RuntimeMetadataPrefixed) -> Result<Self, Self::Error> {
match value.1 {
frame_metadata::RuntimeMetadata::V0(_) =>
Err(TryFromError::UnsupportedMetadataVersion(0)),
frame_metadata::RuntimeMetadata::V1(_) =>
Err(TryFromError::UnsupportedMetadataVersion(1)),
frame_metadata::RuntimeMetadata::V2(_) =>
Err(TryFromError::UnsupportedMetadataVersion(2)),
frame_metadata::RuntimeMetadata::V3(_) =>
Err(TryFromError::UnsupportedMetadataVersion(3)),
frame_metadata::RuntimeMetadata::V4(_) =>
Err(TryFromError::UnsupportedMetadataVersion(4)),
frame_metadata::RuntimeMetadata::V5(_) =>
Err(TryFromError::UnsupportedMetadataVersion(5)),
frame_metadata::RuntimeMetadata::V6(_) =>
Err(TryFromError::UnsupportedMetadataVersion(6)),
frame_metadata::RuntimeMetadata::V7(_) =>
Err(TryFromError::UnsupportedMetadataVersion(7)),
frame_metadata::RuntimeMetadata::V8(_) =>
Err(TryFromError::UnsupportedMetadataVersion(8)),
frame_metadata::RuntimeMetadata::V9(_) =>
Err(TryFromError::UnsupportedMetadataVersion(9)),
frame_metadata::RuntimeMetadata::V10(_) =>
Err(TryFromError::UnsupportedMetadataVersion(10)),
frame_metadata::RuntimeMetadata::V11(_) =>
Err(TryFromError::UnsupportedMetadataVersion(11)),
frame_metadata::RuntimeMetadata::V12(_) =>
Err(TryFromError::UnsupportedMetadataVersion(12)),
frame_metadata::RuntimeMetadata::V13(_) =>
Err(TryFromError::UnsupportedMetadataVersion(13)),
frame_metadata::RuntimeMetadata::V14(m) => m.try_into(),
frame_metadata::RuntimeMetadata::V15(m) => m.try_into(),
frame_metadata::RuntimeMetadata::V16(m) => m.try_into(),
}
}
}
+305
View File
@@ -0,0 +1,305 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::TryFromError;
use crate::{
ConstantMetadata, CustomMetadataInner, ExtrinsicMetadata, Metadata, OuterEnumsMetadata,
PalletMetadataInner, StorageEntryMetadata, StorageMetadata, TransactionExtensionMetadataInner,
utils::{ordered_map::OrderedMap, variant_index::VariantIndex},
};
use alloc::{borrow::ToOwned, collections::BTreeMap, format, string::String, vec, vec::Vec};
use frame_decode::storage::StorageTypeInfo;
use frame_metadata::v14;
use hashbrown::HashMap;
use scale_info::form::PortableForm;
impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
type Error = TryFromError;
fn try_from(mut m: v14::RuntimeMetadataV14) -> Result<Self, TryFromError> {
let outer_enums = generate_outer_enums(&mut m)?;
let missing_extrinsic_type_ids = MissingExtrinsicTypeIds::generate_from(&m)?;
let mut pallets = OrderedMap::new();
let mut pallets_by_index = HashMap::new();
for (pos, p) in m.pallets.iter().enumerate() {
let name: String = p.name.clone();
let storage = match &p.storage {
None => None,
Some(s) => Some(StorageMetadata {
prefix: s.prefix.clone(),
entries: s
.entries
.iter()
.map(|s| {
let entry_name: String = s.name.clone();
let storage_info = m
.storage_info(&name, &entry_name)
.map_err(|e| e.into_owned())?
.into_owned();
let storage_entry = StorageEntryMetadata {
name: entry_name.clone(),
info: storage_info,
docs: s.docs.clone(),
};
Ok::<_, TryFromError>((entry_name, storage_entry))
})
.collect::<Result<_, TryFromError>>()?,
}),
};
let constants = p.constants.iter().map(|c| {
let name = c.name.clone();
(name, from_constant_metadata(c.clone()))
});
let call_variant_index =
VariantIndex::build(p.calls.as_ref().map(|c| c.ty.id), &m.types);
let error_variant_index =
VariantIndex::build(p.error.as_ref().map(|e| e.ty.id), &m.types);
let event_variant_index =
VariantIndex::build(p.event.as_ref().map(|e| e.ty.id), &m.types);
pallets_by_index.insert(p.index, pos);
pallets.push_insert(
name.clone(),
PalletMetadataInner {
name: name.clone(),
call_index: p.index,
event_index: p.index,
error_index: p.index,
storage,
call_ty: p.calls.as_ref().map(|c| c.ty.id),
call_variant_index,
event_ty: p.event.as_ref().map(|e| e.ty.id),
event_variant_index,
error_ty: p.error.as_ref().map(|e| e.ty.id),
error_variant_index,
constants: constants.collect(),
view_functions: Default::default(),
associated_types: Default::default(),
docs: vec![],
},
);
}
let dispatch_error_ty = m
.types
.types
.iter()
.find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"])
.map(|ty| ty.id);
Ok(Metadata {
types: m.types,
pallets,
pallets_by_call_index: pallets_by_index.clone(),
pallets_by_error_index: pallets_by_index.clone(),
pallets_by_event_index: pallets_by_index,
extrinsic: from_extrinsic_metadata(m.extrinsic, missing_extrinsic_type_ids),
dispatch_error_ty,
outer_enums: OuterEnumsMetadata {
call_enum_ty: outer_enums.call_enum_ty.id,
event_enum_ty: outer_enums.event_enum_ty.id,
error_enum_ty: outer_enums.error_enum_ty.id,
},
apis: Default::default(),
custom: CustomMetadataInner { map: Default::default() },
})
}
}
fn from_signed_extension_metadata(
value: v14::SignedExtensionMetadata<PortableForm>,
) -> TransactionExtensionMetadataInner {
TransactionExtensionMetadataInner {
identifier: value.identifier,
extra_ty: value.ty.id,
additional_ty: value.additional_signed.id,
}
}
fn from_extrinsic_metadata(
value: v14::ExtrinsicMetadata<PortableForm>,
missing_ids: MissingExtrinsicTypeIds,
) -> ExtrinsicMetadata {
let transaction_extensions: Vec<_> = value
.signed_extensions
.into_iter()
.map(from_signed_extension_metadata)
.collect();
let transaction_extension_indexes = (0..transaction_extensions.len() as u32).collect();
ExtrinsicMetadata {
supported_versions: vec![value.version],
transaction_extensions,
address_ty: missing_ids.address,
signature_ty: missing_ids.signature,
transaction_extensions_by_version: BTreeMap::from_iter([(
0,
transaction_extension_indexes,
)]),
}
}
fn from_constant_metadata(s: v14::PalletConstantMetadata<PortableForm>) -> ConstantMetadata {
ConstantMetadata { name: s.name, ty: s.ty.id, value: s.value, docs: s.docs }
}
fn generate_outer_enums(
metadata: &mut v14::RuntimeMetadataV14,
) -> Result<frame_metadata::v15::OuterEnums<scale_info::form::PortableForm>, TryFromError> {
let outer_enums = OuterEnums::find_in(&metadata.types);
let Some(call_enum_id) = outer_enums.call_ty else {
return Err(TryFromError::TypeNameNotFound("RuntimeCall".into()));
};
let Some(event_type_id) = outer_enums.event_ty else {
return Err(TryFromError::TypeNameNotFound("RuntimeEvent".into()));
};
let error_type_id = if let Some(id) = outer_enums.error_ty {
id
} else {
let call_enum = &metadata.types.types[call_enum_id as usize];
let mut error_path = call_enum.ty.path.segments.clone();
let Some(last) = error_path.last_mut() else {
return Err(TryFromError::InvalidTypePath("RuntimeCall".into()));
};
"RuntimeError".clone_into(last);
generate_outer_error_enum_type(metadata, error_path)
};
Ok(frame_metadata::v15::OuterEnums {
call_enum_ty: call_enum_id.into(),
event_enum_ty: event_type_id.into(),
error_enum_ty: error_type_id.into(),
})
}
/// Generates an outer `RuntimeError` enum type and adds it to the metadata.
///
/// Returns the id of the generated type from the registry.
fn generate_outer_error_enum_type(
metadata: &mut v14::RuntimeMetadataV14,
path_segments: Vec<String>,
) -> u32 {
let variants: Vec<_> = metadata
.pallets
.iter()
.filter_map(|pallet| {
let error = pallet.error.as_ref()?;
let path = format!("{}Error", pallet.name);
let ty = error.ty.id.into();
Some(scale_info::Variant {
name: pallet.name.clone(),
fields: vec![scale_info::Field {
name: None,
ty,
type_name: Some(path),
docs: vec![],
}],
index: pallet.index,
docs: vec![],
})
})
.collect();
let enum_type = scale_info::Type {
path: scale_info::Path { segments: path_segments },
type_params: vec![],
type_def: scale_info::TypeDef::Variant(scale_info::TypeDefVariant { variants }),
docs: vec![],
};
let enum_type_id = metadata.types.types.len() as u32;
metadata
.types
.types
.push(scale_info::PortableType { id: enum_type_id, ty: enum_type });
enum_type_id
}
/// The type IDs extracted from the metadata that represent the
/// generic type parameters passed to the `UncheckedExtrinsic` from
/// the bizinikiwi-based chain.
#[derive(Clone, Copy)]
struct MissingExtrinsicTypeIds {
address: u32,
signature: u32,
}
impl MissingExtrinsicTypeIds {
fn generate_from(
metadata: &v14::RuntimeMetadataV14,
) -> Result<MissingExtrinsicTypeIds, TryFromError> {
const ADDRESS: &str = "Address";
const SIGNATURE: &str = "Signature";
let extrinsic_id = metadata.extrinsic.ty.id;
let Some(extrinsic_ty) = metadata.types.resolve(extrinsic_id) else {
return Err(TryFromError::TypeNotFound(extrinsic_id));
};
let find_param = |name: &'static str| -> Option<u32> {
extrinsic_ty
.type_params
.iter()
.find(|param| param.name.as_str() == name)
.and_then(|param| param.ty.as_ref())
.map(|ty| ty.id)
};
let Some(address) = find_param(ADDRESS) else {
return Err(TryFromError::TypeNameNotFound(ADDRESS.into()));
};
let Some(signature) = find_param(SIGNATURE) else {
return Err(TryFromError::TypeNameNotFound(SIGNATURE.into()));
};
Ok(MissingExtrinsicTypeIds { address, signature })
}
}
/// Outer enum IDs, which are required in Subxt but are not present in V14 metadata.
pub struct OuterEnums {
/// The RuntimeCall type ID.
pub call_ty: Option<u32>,
/// The RuntimeEvent type ID.
pub event_ty: Option<u32>,
/// The RuntimeError type ID.
pub error_ty: Option<u32>,
}
impl OuterEnums {
pub fn find_in(types: &scale_info::PortableRegistry) -> OuterEnums {
let find_type = |name: &str| {
types.types.iter().find_map(|ty| {
let ident = ty.ty.path.ident()?;
if ident != name {
return None;
}
let scale_info::TypeDef::Variant(_) = &ty.ty.type_def else {
return None;
};
Some(ty.id)
})
};
OuterEnums {
call_ty: find_type("RuntimeCall"),
event_ty: find_type("RuntimeEvent"),
error_ty: find_type("RuntimeError"),
}
}
}
+177
View File
@@ -0,0 +1,177 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::TryFromError;
use crate::{
ConstantMetadata, ExtrinsicMetadata, Metadata, OuterEnumsMetadata, PalletMetadataInner,
RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner, StorageEntryMetadata, StorageMetadata,
TransactionExtensionMetadataInner,
utils::{ordered_map::OrderedMap, variant_index::VariantIndex},
};
use alloc::{collections::BTreeMap, vec, vec::Vec};
use frame_decode::{runtime_apis::RuntimeApiTypeInfo, storage::StorageTypeInfo};
use frame_metadata::v15;
use hashbrown::HashMap;
use scale_info::form::PortableForm;
impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
type Error = TryFromError;
fn try_from(m: v15::RuntimeMetadataV15) -> Result<Self, TryFromError> {
let mut pallets = OrderedMap::new();
let mut pallets_by_index = HashMap::new();
for (pos, p) in m.pallets.iter().enumerate() {
let name = p.name.clone();
let storage = match &p.storage {
None => None,
Some(s) => Some(StorageMetadata {
prefix: s.prefix.clone(),
entries: s
.entries
.iter()
.map(|s| {
let entry_name = s.name.clone();
let storage_info = m
.storage_info(&name, &entry_name)
.map_err(|e| e.into_owned())?
.into_owned();
let storage_entry = StorageEntryMetadata {
name: entry_name.clone(),
info: storage_info,
docs: s.docs.clone(),
};
Ok::<_, TryFromError>((entry_name, storage_entry))
})
.collect::<Result<_, TryFromError>>()?,
}),
};
let constants = p.constants.iter().map(|c| {
let name = c.name.clone();
(name, from_constant_metadata(c.clone()))
});
let call_variant_index =
VariantIndex::build(p.calls.as_ref().map(|c| c.ty.id), &m.types);
let error_variant_index =
VariantIndex::build(p.error.as_ref().map(|e| e.ty.id), &m.types);
let event_variant_index =
VariantIndex::build(p.event.as_ref().map(|e| e.ty.id), &m.types);
pallets_by_index.insert(p.index, pos);
pallets.push_insert(
name.clone(),
PalletMetadataInner {
name,
call_index: p.index,
event_index: p.index,
error_index: p.index,
storage,
call_ty: p.calls.as_ref().map(|c| c.ty.id),
call_variant_index,
event_ty: p.event.as_ref().map(|e| e.ty.id),
event_variant_index,
error_ty: p.error.as_ref().map(|e| e.ty.id),
error_variant_index,
constants: constants.collect(),
view_functions: Default::default(),
associated_types: Default::default(),
docs: p.docs.clone(),
},
);
}
let apis = m
.apis
.iter()
.map(|api| {
let trait_name = api.name.clone();
let methods = api
.methods
.iter()
.map(|method| {
let method_name = method.name.clone();
let method_info = RuntimeApiMethodMetadataInner {
info: m
.runtime_api_info(&trait_name, &method.name)
.map_err(|e| e.into_owned())?
.into_owned(),
name: method.name.clone(),
docs: method.docs.clone(),
};
Ok((method_name, method_info))
})
.collect::<Result<_, TryFromError>>()?;
let runtime_api_metadata = RuntimeApiMetadataInner {
name: trait_name.clone(),
methods,
docs: api.docs.clone(),
};
Ok((trait_name, runtime_api_metadata))
})
.collect::<Result<_, TryFromError>>()?;
let dispatch_error_ty = m
.types
.types
.iter()
.find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"])
.map(|ty| ty.id);
Ok(Metadata {
types: m.types,
pallets,
pallets_by_call_index: pallets_by_index.clone(),
pallets_by_error_index: pallets_by_index.clone(),
pallets_by_event_index: pallets_by_index,
extrinsic: from_extrinsic_metadata(m.extrinsic),
dispatch_error_ty,
apis,
outer_enums: OuterEnumsMetadata {
call_enum_ty: m.outer_enums.call_enum_ty.id,
event_enum_ty: m.outer_enums.event_enum_ty.id,
error_enum_ty: m.outer_enums.error_enum_ty.id,
},
custom: m.custom,
})
}
}
fn from_signed_extension_metadata(
value: v15::SignedExtensionMetadata<PortableForm>,
) -> TransactionExtensionMetadataInner {
TransactionExtensionMetadataInner {
identifier: value.identifier,
extra_ty: value.ty.id,
additional_ty: value.additional_signed.id,
}
}
fn from_extrinsic_metadata(value: v15::ExtrinsicMetadata<PortableForm>) -> ExtrinsicMetadata {
let transaction_extensions: Vec<_> = value
.signed_extensions
.into_iter()
.map(from_signed_extension_metadata)
.collect();
let transaction_extension_indexes = (0..transaction_extensions.len() as u32).collect();
ExtrinsicMetadata {
supported_versions: vec![value.version],
transaction_extensions,
address_ty: value.address_ty.id,
signature_ty: value.signature_ty.id,
transaction_extensions_by_version: BTreeMap::from_iter([(
0,
transaction_extension_indexes,
)]),
}
}
fn from_constant_metadata(s: v15::PalletConstantMetadata<PortableForm>) -> ConstantMetadata {
ConstantMetadata { name: s.name, ty: s.ty.id, value: s.value, docs: s.docs }
}
+203
View File
@@ -0,0 +1,203 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::TryFromError;
use crate::{
ConstantMetadata, ExtrinsicMetadata, Metadata, OuterEnumsMetadata, PalletMetadataInner,
RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner, StorageEntryMetadata, StorageMetadata,
TransactionExtensionMetadataInner, ViewFunctionMetadataInner,
utils::{ordered_map::OrderedMap, variant_index::VariantIndex},
};
use frame_decode::{
runtime_apis::RuntimeApiTypeInfo, storage::StorageTypeInfo,
view_functions::ViewFunctionTypeInfo,
};
use frame_metadata::{v15, v16};
use hashbrown::HashMap;
use scale_info::form::PortableForm;
impl TryFrom<v16::RuntimeMetadataV16> for Metadata {
type Error = TryFromError;
fn try_from(m: v16::RuntimeMetadataV16) -> Result<Self, TryFromError> {
let types = &m.types;
let mut pallets = OrderedMap::new();
let mut pallets_by_index = HashMap::new();
for (pos, p) in m.pallets.iter().enumerate() {
let name = p.name.clone();
let storage = match &p.storage {
None => None,
Some(s) => Some(StorageMetadata {
prefix: s.prefix.clone(),
entries: s
.entries
.iter()
.map(|s| {
let entry_name = s.name.clone();
let storage_info = m
.storage_info(&name, &entry_name)
.map_err(|e| e.into_owned())?
.into_owned();
let storage_entry = StorageEntryMetadata {
name: entry_name.clone(),
info: storage_info,
docs: s.docs.clone(),
};
Ok::<_, TryFromError>((entry_name, storage_entry))
})
.collect::<Result<_, TryFromError>>()?,
}),
};
let view_functions = p
.view_functions
.iter()
.map(|vf| {
let view_function_metadata = ViewFunctionMetadataInner {
name: vf.name.clone(),
info: m
.view_function_info(&name, &vf.name)
.map_err(|e| e.into_owned())?
.into_owned(),
docs: vf.docs.clone(),
};
Ok((vf.name.clone(), view_function_metadata))
})
.collect::<Result<_, TryFromError>>()?;
let constants = p.constants.iter().map(|c| {
let name = c.name.clone();
(name, from_constant_metadata(c.clone()))
});
let call_variant_index = VariantIndex::build(p.calls.as_ref().map(|c| c.ty.id), types);
let error_variant_index = VariantIndex::build(p.error.as_ref().map(|e| e.ty.id), types);
let event_variant_index = VariantIndex::build(p.event.as_ref().map(|e| e.ty.id), types);
let associated_types =
p.associated_types.iter().map(|t| (t.name.clone(), t.ty.id)).collect();
pallets_by_index.insert(p.index, pos);
pallets.push_insert(
name.clone(),
PalletMetadataInner {
name,
call_index: p.index,
event_index: p.index,
error_index: p.index,
storage,
call_ty: p.calls.as_ref().map(|c| c.ty.id),
call_variant_index,
event_ty: p.event.as_ref().map(|e| e.ty.id),
event_variant_index,
error_ty: p.error.as_ref().map(|e| e.ty.id),
error_variant_index,
constants: constants.collect(),
view_functions,
associated_types,
docs: p.docs.clone(),
},
);
}
let apis = m
.apis
.iter()
.map(|api| {
let trait_name = api.name.clone();
let methods = api
.methods
.iter()
.map(|method| {
let method_name = method.name.clone();
let method_info = RuntimeApiMethodMetadataInner {
info: m
.runtime_api_info(&trait_name, &method.name)
.map_err(|e| e.into_owned())?
.into_owned(),
name: method.name.clone(),
docs: method.docs.clone(),
};
Ok((method_name, method_info))
})
.collect::<Result<_, TryFromError>>()?;
let runtime_api_metadata = RuntimeApiMetadataInner {
name: trait_name.clone(),
methods,
docs: api.docs.clone(),
};
Ok((trait_name, runtime_api_metadata))
})
.collect::<Result<_, TryFromError>>()?;
let custom_map = m
.custom
.map
.into_iter()
.map(|(key, val)| {
let custom_val = v15::CustomValueMetadata { ty: val.ty, value: val.value };
(key, custom_val)
})
.collect();
let dispatch_error_ty = types
.types
.iter()
.find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"])
.map(|ty| ty.id);
Ok(Metadata {
types: m.types,
pallets,
pallets_by_call_index: pallets_by_index.clone(),
pallets_by_error_index: pallets_by_index.clone(),
pallets_by_event_index: pallets_by_index,
extrinsic: from_extrinsic_metadata(m.extrinsic),
dispatch_error_ty,
apis,
outer_enums: OuterEnumsMetadata {
call_enum_ty: m.outer_enums.call_enum_ty.id,
event_enum_ty: m.outer_enums.event_enum_ty.id,
error_enum_ty: m.outer_enums.error_enum_ty.id,
},
custom: v15::CustomMetadata { map: custom_map },
})
}
}
fn from_transaction_extension_metadata(
value: v16::TransactionExtensionMetadata<PortableForm>,
) -> TransactionExtensionMetadataInner {
TransactionExtensionMetadataInner {
identifier: value.identifier,
extra_ty: value.ty.id,
additional_ty: value.implicit.id,
}
}
fn from_extrinsic_metadata(value: v16::ExtrinsicMetadata<PortableForm>) -> ExtrinsicMetadata {
ExtrinsicMetadata {
supported_versions: value.versions,
transaction_extensions_by_version: value
.transaction_extensions_by_version
.into_iter()
.map(|(version, idxs)| (version, idxs.into_iter().map(|idx| idx.0).collect()))
.collect(),
transaction_extensions: value
.transaction_extensions
.into_iter()
.map(from_transaction_extension_metadata)
.collect(),
address_ty: value.address_ty.id,
signature_ty: value.signature_ty.id,
}
}
fn from_constant_metadata(s: v16::PalletConstantMetadata<PortableForm>) -> ConstantMetadata {
ConstantMetadata { name: s.name, ty: s.ty.id, value: s.value, docs: s.docs }
}
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
pub mod ordered_map;
pub mod validation;
pub mod variant_index;
+81
View File
@@ -0,0 +1,81 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use alloc::vec::Vec;
use hashbrown::HashMap;
/// A minimal ordered map to let one search for
/// things by key or get the values in insert order.
#[derive(Debug, Clone)]
pub struct OrderedMap<K, V> {
values: Vec<V>,
map: HashMap<K, usize>,
}
impl<K, V> Default for OrderedMap<K, V> {
fn default() -> Self {
Self { values: Default::default(), map: Default::default() }
}
}
impl<K, V> OrderedMap<K, V>
where
K: PartialEq + Eq + core::hash::Hash,
{
/// Create a new, empty [`OrderedMap`].
pub fn new() -> Self {
Self::default()
}
/// Number of entries in the map.
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.values.len()
}
/// Is the map empty.
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
/// Push/insert an item to the end of the map.
pub fn push_insert(&mut self, key: K, value: V) {
let idx = self.values.len();
self.values.push(value);
self.map.insert(key, idx);
}
/// Get an item by its key.
pub fn get_by_key<Q>(&self, key: &Q) -> Option<&V>
where
K: alloc::borrow::Borrow<Q>,
Q: core::hash::Hash + Eq + ?Sized,
{
self.map.get(key).and_then(|&v| self.values.get(v))
}
/// Get an item by its index.
pub fn get_by_index(&self, i: usize) -> Option<&V> {
self.values.get(i)
}
/// Access the underlying values.
pub fn values(&self) -> &[V] {
&self.values
}
}
impl<K, V> FromIterator<(K, V)> for OrderedMap<K, V>
where
K: PartialEq + Eq + core::hash::Hash,
{
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
let mut map = OrderedMap::new();
for (k, v) in iter {
map.push_insert(k, v)
}
map
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,82 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use alloc::{borrow::ToOwned, string::String};
use hashbrown::HashMap;
use scale_info::{PortableRegistry, TypeDef, Variant, form::PortableForm};
/// Given some type ID and type registry, build a couple of
/// indexes to look up variants by index or name. If the ID provided
/// is not a variant, the index will be empty.
///
/// API optimized for dealing with the `Option<u32>` variant type IDs
/// that we get in metadata pallets.
#[derive(Debug, Clone)]
pub struct VariantIndex {
by_name: HashMap<String, usize>,
by_index: HashMap<u8, usize>,
}
impl VariantIndex {
/// Build indexes from the optional variant ID.
pub fn build(variant_id: Option<u32>, types: &PortableRegistry) -> Self {
let Some(variants) = Self::get(variant_id, types) else {
return Self::empty();
};
let mut by_name = HashMap::new();
let mut by_index = HashMap::new();
for (pos, variant) in variants.iter().enumerate() {
by_name.insert(variant.name.to_owned(), pos);
by_index.insert(variant.index, pos);
}
Self { by_name, by_index }
}
/// Build an empty index.
pub fn empty() -> Self {
Self { by_name: Default::default(), by_index: Default::default() }
}
/// Get the variants we're pointing at; None if this isn't possible.
pub fn get(
variant_id: Option<u32>,
types: &PortableRegistry,
) -> Option<&[Variant<PortableForm>]> {
let variant_id = variant_id?;
let TypeDef::Variant(v) = &types.resolve(variant_id)?.type_def else {
return None;
};
Some(&v.variants)
}
/// Lookup a variant by name; `None` if the type is not a variant or name isn't found.
pub fn lookup_by_name<'a, K>(
&self,
name: &K,
variant_id: Option<u32>,
types: &'a PortableRegistry,
) -> Option<&'a Variant<PortableForm>>
where
String: alloc::borrow::Borrow<K>,
K: core::hash::Hash + Eq + ?Sized,
{
let pos = *self.by_name.get(name)?;
let variants = Self::get(variant_id, types)?;
variants.get(pos)
}
/// Lookup a variant by index; `None` if the type is not a variant or index isn't found.
pub fn lookup_by_index<'a>(
&self,
index: u8,
variant_id: Option<u32>,
types: &'a PortableRegistry,
) -> Option<&'a Variant<PortableForm>> {
let pos = *self.by_index.get(&index)?;
let variants = Self::get(variant_id, types)?;
variants.get(pos)
}
}