mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 19:51:05 +00:00
Merge v0.50.x to master (#2127)
* v0.50.0: Integrate frame-decode, redo storage APIs and break up Error. (#2100) * WIP integrating new frame-decode and working out new storage APIS * WIP: first pass adding new storage things to subxt-core * Second pass over Address type and start impl in Subxt * WIP new storage APIs * WIP New storage APIs roughly completed, lots of errors still * Remove PlainorMap enum; plain and map values now use same struct to simplify usage * Begin 'fixing' errors * WIP splitting errors and tidying payload/address traits * Get subxt-core compiling * Small fixes in subxt-core and remove metadata mod * subxt-core: cargo check --all-targets passes * Fix test * WIP starting to update subxt from subxt-core changes * WIP splitting up subxt errors into smaller variants * WIP errors: add DispatchError errors * Port new Storage APIs to subxt-core * cargo check -p subxt passes * Quick-fix errors in subxt-cli (explore subcommand) * fmt * Finish fixing codegen up and start fixing examples * get Subxt examples compiling and bytes_at for constants * Add some arcs to limit lifetimes in subxt/subxt-core storage APIs * A little Arcing to allow more method chaining in Storage APIs, aligning with Subxt * Update codegen test * cargo check --all-targets passing * cargo check --features 'unstable-light-client' passing * clippy * Remove unused dep in subxt * use published frame-decode * fix wasm-example * Add new tx extension to fix daily tests * Remove unused subxt_core::dynamic::DecodedValue type * Update book to match changes * Update docs to fix more broken bits * Add missing docs * fmt * allow larger result errs for now * Add missing alloc imports in subxt-core * Fix doc tests and fix bug getting constant info * Fix V14 -> Metadata transform for storage & constants * Fix parachain example * Fix FFI example * BlockLength decodes t ostruct, not u128 * use fetch/iter shorthands rather than entry in most storage tests * Fix some integration tests * Fix Runtime codegen tests * Expose the dynamic custom_value selecter and use in a UI test * Update codegen metadata * Tidy CLI storage query and support (str,str) as a storage address * Add (str,str) as valid constant address too * Show string tuple in constants example * Via the magic of traits, avoid needing any clones of queries/addresses and accept references to them * clippy * [v0.50] update scale-info-legacy and frame-decode to latest (#2119) * bump scale-info-legacy and frame-decode to latest * Remove something we don't need in this PR * Fully remove unused for now dep * [v0.50] Convert historic metadata to subxt::Metadata (#2120) * First pass converting historic metadatas to our subxt::Metadata type * use published frame-decode * fmt and rename legacy metadata macro * Enable legacy feature where needed in subxt_metadata so it compiles on its own * Use cargo hack more in CI and fix subxt-metadata features * Add tests for metadata conversion (need to optimise; some too expensive right now * Address performance and equality issues in metadata conversion testing * fmt * fmt all * clippy * Fix a doc link * Test codegen and fixes to make it work * Remove local frame-decode patch * bump frame-decode to latest * [v0.50.0] Allow visiting extrinsic fields in subxt_historic (#2124) * Allow visiting extrinsic fields * fmt * Don't use local scale-decode dep * Clippy and tidy * Extend 'subxt codegen' CLI to work with legacy metadatas * Simplify historic extrinsics example now that AccountId32s have paths/names * clippy * clippy * clippy.. * Allow visiting storage values, too, and clean up extrinsic visiting a little by narrowing lifetime * Try to fix flaky test * Add custom value decode to extrinsics example * Remove useless else branch ra thought I needed * Simplify examples * Prep to release v0.0.5 (#2126)
This commit is contained in:
@@ -0,0 +1,421 @@
|
||||
mod portable_registry_builder;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::Metadata;
|
||||
use crate::utils::ordered_map::OrderedMap;
|
||||
use crate::utils::variant_index::VariantIndex;
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::format;
|
||||
use alloc::string::ToString;
|
||||
use alloc::vec::Vec;
|
||||
use frame_decode::constants::{ConstantEntryInfo, ConstantTypeInfo};
|
||||
use frame_decode::extrinsics::ExtrinsicTypeInfo;
|
||||
use frame_decode::runtime_apis::RuntimeApiTypeInfo;
|
||||
use frame_decode::storage::{StorageEntryInfo, StorageTypeInfo};
|
||||
use frame_metadata::v15;
|
||||
use portable_registry_builder::PortableRegistryBuilder;
|
||||
use scale_info_legacy::TypeRegistrySet;
|
||||
use scale_info_legacy::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,541 @@
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
use alloc::string::ToString;
|
||||
use alloc::vec::Vec;
|
||||
use scale_info::PortableRegistry;
|
||||
use scale_info::{PortableType, form::PortableForm};
|
||||
use scale_info_legacy::type_registry::TypeRegistryResolveError;
|
||||
use scale_info_legacy::{LookupName, TypeRegistrySet};
|
||||
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(),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
use super::*;
|
||||
use alloc::collections::BTreeSet;
|
||||
use codec::Decode;
|
||||
use core::str::FromStr;
|
||||
use frame_decode::constants::ConstantTypeInfo;
|
||||
use frame_decode::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::polkadot::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 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 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 subxt_metadata::Metadata");
|
||||
|
||||
(types_for_spec, metadata, 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 `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) => subxt_codegen::Metadata::from_v9(m, &types_for_spec),
|
||||
RuntimeMetadata::V10(m) => subxt_codegen::Metadata::from_v10(m, &types_for_spec),
|
||||
RuntimeMetadata::V11(m) => subxt_codegen::Metadata::from_v11(m, &types_for_spec),
|
||||
RuntimeMetadata::V12(m) => subxt_codegen::Metadata::from_v12(m, &types_for_spec),
|
||||
RuntimeMetadata::V13(m) => subxt_codegen::Metadata::from_v13(m, &types_for_spec),
|
||||
_ => panic!("Metadata version {} not expected", metadata.version()),
|
||||
}
|
||||
.expect("Could not convert to subxt_metadata::Metadata")
|
||||
};
|
||||
|
||||
// We only test that generation succeeds without any errors, not necessarily that it's 100% useful:
|
||||
let codegen = 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}"));
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,10 @@ 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.
|
||||
@@ -33,6 +37,15 @@ pub enum TryFromError {
|
||||
/// 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 {
|
||||
|
||||
+45
-77
@@ -6,9 +6,8 @@ use super::TryFromError;
|
||||
|
||||
use crate::utils::variant_index::VariantIndex;
|
||||
use crate::{
|
||||
ArcStr, ConstantMetadata, CustomMetadataInner, ExtrinsicMetadata, Metadata, OuterEnumsMetadata,
|
||||
PalletMetadataInner, StorageEntryMetadata, StorageEntryModifier, StorageEntryType,
|
||||
StorageHasher, StorageMetadata, TransactionExtensionMetadataInner,
|
||||
ConstantMetadata, CustomMetadataInner, ExtrinsicMetadata, Metadata, OuterEnumsMetadata,
|
||||
PalletMetadataInner, StorageEntryMetadata, StorageMetadata, TransactionExtensionMetadataInner,
|
||||
utils::ordered_map::OrderedMap,
|
||||
};
|
||||
use alloc::borrow::ToOwned;
|
||||
@@ -16,6 +15,7 @@ use alloc::collections::BTreeMap;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use alloc::{format, vec};
|
||||
use frame_decode::storage::StorageTypeInfo;
|
||||
use frame_metadata::v14;
|
||||
use hashbrown::HashMap;
|
||||
use scale_info::form::PortableForm;
|
||||
@@ -28,23 +28,37 @@ impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
|
||||
|
||||
let mut pallets = OrderedMap::new();
|
||||
let mut pallets_by_index = HashMap::new();
|
||||
for (pos, p) in m.pallets.into_iter().enumerate() {
|
||||
let name: ArcStr = p.name.into();
|
||||
for (pos, p) in m.pallets.iter().enumerate() {
|
||||
let name: String = p.name.clone();
|
||||
|
||||
let storage = p.storage.map(|s| StorageMetadata {
|
||||
prefix: s.prefix,
|
||||
entries: s
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
let name: ArcStr = s.name.clone().into();
|
||||
(name.clone(), from_storage_entry_metadata(name, s))
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
let constants = p.constants.into_iter().map(|c| {
|
||||
let name: ArcStr = c.name.clone().into();
|
||||
(name.clone(), from_constant_metadata(name, c))
|
||||
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 =
|
||||
@@ -58,14 +72,16 @@ impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
|
||||
pallets.push_insert(
|
||||
name.clone(),
|
||||
PalletMetadataInner {
|
||||
name,
|
||||
index: p.index,
|
||||
name: name.clone(),
|
||||
call_index: p.index,
|
||||
event_index: p.index,
|
||||
error_index: p.index,
|
||||
storage,
|
||||
call_ty: p.calls.map(|c| c.ty.id),
|
||||
call_ty: p.calls.as_ref().map(|c| c.ty.id),
|
||||
call_variant_index,
|
||||
event_ty: p.event.map(|e| e.ty.id),
|
||||
event_ty: p.event.as_ref().map(|e| e.ty.id),
|
||||
event_variant_index,
|
||||
error_ty: p.error.map(|e| e.ty.id),
|
||||
error_ty: p.error.as_ref().map(|e| e.ty.id),
|
||||
error_variant_index,
|
||||
constants: constants.collect(),
|
||||
view_functions: Default::default(),
|
||||
@@ -85,7 +101,9 @@ impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
|
||||
Ok(Metadata {
|
||||
types: m.types,
|
||||
pallets,
|
||||
pallets_by_index,
|
||||
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 {
|
||||
@@ -135,59 +153,9 @@ fn from_extrinsic_metadata(
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_hasher(value: v14::StorageHasher) -> StorageHasher {
|
||||
match value {
|
||||
v14::StorageHasher::Blake2_128 => StorageHasher::Blake2_128,
|
||||
v14::StorageHasher::Blake2_256 => StorageHasher::Blake2_256,
|
||||
v14::StorageHasher::Blake2_128Concat => StorageHasher::Blake2_128Concat,
|
||||
v14::StorageHasher::Twox128 => StorageHasher::Twox128,
|
||||
v14::StorageHasher::Twox256 => StorageHasher::Twox256,
|
||||
v14::StorageHasher::Twox64Concat => StorageHasher::Twox64Concat,
|
||||
v14::StorageHasher::Identity => StorageHasher::Identity,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_type(value: v14::StorageEntryType<PortableForm>) -> StorageEntryType {
|
||||
match value {
|
||||
v14::StorageEntryType::Plain(ty) => StorageEntryType::Plain(ty.id),
|
||||
v14::StorageEntryType::Map {
|
||||
hashers,
|
||||
key,
|
||||
value,
|
||||
} => StorageEntryType::Map {
|
||||
hashers: hashers.into_iter().map(from_storage_hasher).collect(),
|
||||
key_ty: key.id,
|
||||
value_ty: value.id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_modifier(value: v14::StorageEntryModifier) -> StorageEntryModifier {
|
||||
match value {
|
||||
v14::StorageEntryModifier::Optional => StorageEntryModifier::Optional,
|
||||
v14::StorageEntryModifier::Default => StorageEntryModifier::Default,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_metadata(
|
||||
name: ArcStr,
|
||||
s: v14::StorageEntryMetadata<PortableForm>,
|
||||
) -> StorageEntryMetadata {
|
||||
StorageEntryMetadata {
|
||||
name,
|
||||
modifier: from_storage_entry_modifier(s.modifier),
|
||||
entry_type: from_storage_entry_type(s.ty),
|
||||
default: s.default,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_constant_metadata(
|
||||
name: ArcStr,
|
||||
s: v14::PalletConstantMetadata<PortableForm>,
|
||||
) -> ConstantMetadata {
|
||||
fn from_constant_metadata(s: v14::PalletConstantMetadata<PortableForm>) -> ConstantMetadata {
|
||||
ConstantMetadata {
|
||||
name,
|
||||
name: s.name,
|
||||
ty: s.ty.id,
|
||||
value: s.value,
|
||||
docs: s.docs,
|
||||
|
||||
+77
-125
@@ -6,14 +6,15 @@ use super::TryFromError;
|
||||
|
||||
use crate::utils::variant_index::VariantIndex;
|
||||
use crate::{
|
||||
ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata, MethodParamMetadata, OuterEnumsMetadata,
|
||||
PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner,
|
||||
StorageEntryMetadata, StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
|
||||
ConstantMetadata, ExtrinsicMetadata, Metadata, OuterEnumsMetadata, PalletMetadataInner,
|
||||
RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner, StorageEntryMetadata, StorageMetadata,
|
||||
TransactionExtensionMetadataInner, utils::ordered_map::OrderedMap,
|
||||
};
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use frame_decode::runtime_apis::RuntimeApiTypeInfo;
|
||||
use frame_decode::storage::StorageTypeInfo;
|
||||
use frame_metadata::v15;
|
||||
use hashbrown::HashMap;
|
||||
use scale_info::form::PortableForm;
|
||||
@@ -23,23 +24,37 @@ impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
|
||||
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.into_iter().enumerate() {
|
||||
let name: ArcStr = p.name.into();
|
||||
for (pos, p) in m.pallets.iter().enumerate() {
|
||||
let name = p.name.clone();
|
||||
|
||||
let storage = p.storage.map(|s| StorageMetadata {
|
||||
prefix: s.prefix,
|
||||
entries: s
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
let name: ArcStr = s.name.clone().into();
|
||||
(name.clone(), from_storage_entry_metadata(name, s))
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
let constants = p.constants.into_iter().map(|c| {
|
||||
let name: ArcStr = c.name.clone().into();
|
||||
(name.clone(), from_constant_metadata(name, c))
|
||||
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 =
|
||||
@@ -54,26 +69,54 @@ impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
|
||||
name.clone(),
|
||||
PalletMetadataInner {
|
||||
name,
|
||||
index: p.index,
|
||||
call_index: p.index,
|
||||
event_index: p.index,
|
||||
error_index: p.index,
|
||||
storage,
|
||||
call_ty: p.calls.map(|c| c.ty.id),
|
||||
call_ty: p.calls.as_ref().map(|c| c.ty.id),
|
||||
call_variant_index,
|
||||
event_ty: p.event.map(|e| e.ty.id),
|
||||
event_ty: p.event.as_ref().map(|e| e.ty.id),
|
||||
event_variant_index,
|
||||
error_ty: p.error.map(|e| e.ty.id),
|
||||
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,
|
||||
docs: p.docs.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let apis = m.apis.into_iter().map(|api| {
|
||||
let name: ArcStr = api.name.clone().into();
|
||||
(name.clone(), from_runtime_api_metadata(name, api))
|
||||
});
|
||||
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
|
||||
@@ -85,10 +128,12 @@ impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
|
||||
Ok(Metadata {
|
||||
types: m.types,
|
||||
pallets,
|
||||
pallets_by_index,
|
||||
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: apis.collect(),
|
||||
apis,
|
||||
outer_enums: OuterEnumsMetadata {
|
||||
call_enum_ty: m.outer_enums.call_enum_ty.id,
|
||||
event_enum_ty: m.outer_enums.event_enum_ty.id,
|
||||
@@ -130,104 +175,11 @@ fn from_extrinsic_metadata(value: v15::ExtrinsicMetadata<PortableForm>) -> Extri
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_hasher(value: v15::StorageHasher) -> StorageHasher {
|
||||
match value {
|
||||
v15::StorageHasher::Blake2_128 => StorageHasher::Blake2_128,
|
||||
v15::StorageHasher::Blake2_256 => StorageHasher::Blake2_256,
|
||||
v15::StorageHasher::Blake2_128Concat => StorageHasher::Blake2_128Concat,
|
||||
v15::StorageHasher::Twox128 => StorageHasher::Twox128,
|
||||
v15::StorageHasher::Twox256 => StorageHasher::Twox256,
|
||||
v15::StorageHasher::Twox64Concat => StorageHasher::Twox64Concat,
|
||||
v15::StorageHasher::Identity => StorageHasher::Identity,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_type(value: v15::StorageEntryType<PortableForm>) -> StorageEntryType {
|
||||
match value {
|
||||
v15::StorageEntryType::Plain(ty) => StorageEntryType::Plain(ty.id),
|
||||
v15::StorageEntryType::Map {
|
||||
hashers,
|
||||
key,
|
||||
value,
|
||||
} => StorageEntryType::Map {
|
||||
hashers: hashers.into_iter().map(from_storage_hasher).collect(),
|
||||
key_ty: key.id,
|
||||
value_ty: value.id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_modifier(value: v15::StorageEntryModifier) -> StorageEntryModifier {
|
||||
match value {
|
||||
v15::StorageEntryModifier::Optional => StorageEntryModifier::Optional,
|
||||
v15::StorageEntryModifier::Default => StorageEntryModifier::Default,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_metadata(
|
||||
name: ArcStr,
|
||||
s: v15::StorageEntryMetadata<PortableForm>,
|
||||
) -> StorageEntryMetadata {
|
||||
StorageEntryMetadata {
|
||||
name,
|
||||
modifier: from_storage_entry_modifier(s.modifier),
|
||||
entry_type: from_storage_entry_type(s.ty),
|
||||
default: s.default,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_constant_metadata(
|
||||
name: ArcStr,
|
||||
s: v15::PalletConstantMetadata<PortableForm>,
|
||||
) -> ConstantMetadata {
|
||||
fn from_constant_metadata(s: v15::PalletConstantMetadata<PortableForm>) -> ConstantMetadata {
|
||||
ConstantMetadata {
|
||||
name,
|
||||
name: s.name,
|
||||
ty: s.ty.id,
|
||||
value: s.value,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_runtime_api_metadata(
|
||||
name: ArcStr,
|
||||
s: v15::RuntimeApiMetadata<PortableForm>,
|
||||
) -> RuntimeApiMetadataInner {
|
||||
RuntimeApiMetadataInner {
|
||||
name,
|
||||
docs: s.docs,
|
||||
methods: s
|
||||
.methods
|
||||
.into_iter()
|
||||
.map(|m| {
|
||||
let name: ArcStr = m.name.clone().into();
|
||||
(name.clone(), from_runtime_api_method_metadata(name, m))
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_runtime_api_method_metadata(
|
||||
name: ArcStr,
|
||||
s: v15::RuntimeApiMethodMetadata<PortableForm>,
|
||||
) -> RuntimeApiMethodMetadataInner {
|
||||
RuntimeApiMethodMetadataInner {
|
||||
name,
|
||||
inputs: s
|
||||
.inputs
|
||||
.into_iter()
|
||||
.map(from_runtime_api_method_param_metadata)
|
||||
.collect(),
|
||||
output_ty: s.output.id,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_runtime_api_method_param_metadata(
|
||||
s: v15::RuntimeApiMethodParamMetadata<PortableForm>,
|
||||
) -> MethodParamMetadata {
|
||||
MethodParamMetadata {
|
||||
name: s.name,
|
||||
ty: s.ty.id,
|
||||
}
|
||||
}
|
||||
|
||||
+102
-153
@@ -6,11 +6,13 @@ use super::TryFromError;
|
||||
|
||||
use crate::utils::variant_index::VariantIndex;
|
||||
use crate::{
|
||||
ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata, MethodParamMetadata, OuterEnumsMetadata,
|
||||
PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner,
|
||||
StorageEntryMetadata, StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
|
||||
ConstantMetadata, ExtrinsicMetadata, Metadata, OuterEnumsMetadata, PalletMetadataInner,
|
||||
RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner, StorageEntryMetadata, StorageMetadata,
|
||||
TransactionExtensionMetadataInner, ViewFunctionMetadataInner, utils::ordered_map::OrderedMap,
|
||||
};
|
||||
use frame_decode::runtime_apis::RuntimeApiTypeInfo;
|
||||
use frame_decode::storage::StorageTypeInfo;
|
||||
use frame_decode::view_functions::ViewFunctionTypeInfo;
|
||||
use frame_metadata::{v15, v16};
|
||||
use hashbrown::HashMap;
|
||||
use scale_info::form::PortableForm;
|
||||
@@ -18,43 +20,67 @@ 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 types = &m.types;
|
||||
|
||||
let mut pallets = OrderedMap::new();
|
||||
let mut pallets_by_index = HashMap::new();
|
||||
for (pos, p) in m.pallets.into_iter().enumerate() {
|
||||
let name: ArcStr = p.name.into();
|
||||
for (pos, p) in m.pallets.iter().enumerate() {
|
||||
let name = p.name.clone();
|
||||
|
||||
let storage = p.storage.map(|s| StorageMetadata {
|
||||
prefix: s.prefix,
|
||||
entries: s
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
let name: ArcStr = s.name.clone().into();
|
||||
(name.clone(), from_storage_entry_metadata(name, s))
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
let constants = p.constants.into_iter().map(|c| {
|
||||
let name: ArcStr = c.name.clone().into();
|
||||
(name.clone(), from_constant_metadata(name, c))
|
||||
});
|
||||
let view_functions = p.view_functions.into_iter().map(|v| {
|
||||
let name: ArcStr = v.name.clone().into();
|
||||
(name.clone(), from_view_function_metadata(name, v))
|
||||
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 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
|
||||
.into_iter()
|
||||
.map(|t| (t.name, t.ty.id))
|
||||
.iter()
|
||||
.map(|t| (t.name.clone(), t.ty.id))
|
||||
.collect();
|
||||
|
||||
pallets_by_index.insert(p.index, pos);
|
||||
@@ -62,26 +88,54 @@ impl TryFrom<v16::RuntimeMetadataV16> for Metadata {
|
||||
name.clone(),
|
||||
PalletMetadataInner {
|
||||
name,
|
||||
index: p.index,
|
||||
call_index: p.index,
|
||||
event_index: p.index,
|
||||
error_index: p.index,
|
||||
storage,
|
||||
call_ty: p.calls.map(|c| c.ty.id),
|
||||
call_ty: p.calls.as_ref().map(|c| c.ty.id),
|
||||
call_variant_index,
|
||||
event_ty: p.event.map(|e| e.ty.id),
|
||||
event_ty: p.event.as_ref().map(|e| e.ty.id),
|
||||
event_variant_index,
|
||||
error_ty: p.error.map(|e| e.ty.id),
|
||||
error_ty: p.error.as_ref().map(|e| e.ty.id),
|
||||
error_variant_index,
|
||||
constants: constants.collect(),
|
||||
view_functions: view_functions.collect(),
|
||||
view_functions,
|
||||
associated_types,
|
||||
docs: p.docs,
|
||||
docs: p.docs.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let apis = m.apis.into_iter().map(|api| {
|
||||
let name: ArcStr = api.name.clone().into();
|
||||
(name.clone(), from_runtime_api_metadata(name, api))
|
||||
});
|
||||
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
|
||||
@@ -103,12 +157,14 @@ impl TryFrom<v16::RuntimeMetadataV16> for Metadata {
|
||||
.map(|ty| ty.id);
|
||||
|
||||
Ok(Metadata {
|
||||
types,
|
||||
types: m.types,
|
||||
pallets,
|
||||
pallets_by_index,
|
||||
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: apis.collect(),
|
||||
apis,
|
||||
outer_enums: OuterEnumsMetadata {
|
||||
call_enum_ty: m.outer_enums.call_enum_ty.id,
|
||||
event_enum_ty: m.outer_enums.event_enum_ty.id,
|
||||
@@ -147,118 +203,11 @@ fn from_extrinsic_metadata(value: v16::ExtrinsicMetadata<PortableForm>) -> Extri
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_hasher(value: v16::StorageHasher) -> StorageHasher {
|
||||
match value {
|
||||
v16::StorageHasher::Blake2_128 => StorageHasher::Blake2_128,
|
||||
v16::StorageHasher::Blake2_256 => StorageHasher::Blake2_256,
|
||||
v16::StorageHasher::Blake2_128Concat => StorageHasher::Blake2_128Concat,
|
||||
v16::StorageHasher::Twox128 => StorageHasher::Twox128,
|
||||
v16::StorageHasher::Twox256 => StorageHasher::Twox256,
|
||||
v16::StorageHasher::Twox64Concat => StorageHasher::Twox64Concat,
|
||||
v16::StorageHasher::Identity => StorageHasher::Identity,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_type(value: v16::StorageEntryType<PortableForm>) -> StorageEntryType {
|
||||
match value {
|
||||
v16::StorageEntryType::Plain(ty) => StorageEntryType::Plain(ty.id),
|
||||
v16::StorageEntryType::Map {
|
||||
hashers,
|
||||
key,
|
||||
value,
|
||||
} => StorageEntryType::Map {
|
||||
hashers: hashers.into_iter().map(from_storage_hasher).collect(),
|
||||
key_ty: key.id,
|
||||
value_ty: value.id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_modifier(value: v16::StorageEntryModifier) -> StorageEntryModifier {
|
||||
match value {
|
||||
v16::StorageEntryModifier::Optional => StorageEntryModifier::Optional,
|
||||
v16::StorageEntryModifier::Default => StorageEntryModifier::Default,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_metadata(
|
||||
name: ArcStr,
|
||||
s: v16::StorageEntryMetadata<PortableForm>,
|
||||
) -> StorageEntryMetadata {
|
||||
StorageEntryMetadata {
|
||||
name,
|
||||
modifier: from_storage_entry_modifier(s.modifier),
|
||||
entry_type: from_storage_entry_type(s.ty),
|
||||
default: s.default,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_constant_metadata(
|
||||
name: ArcStr,
|
||||
s: v16::PalletConstantMetadata<PortableForm>,
|
||||
) -> ConstantMetadata {
|
||||
fn from_constant_metadata(s: v16::PalletConstantMetadata<PortableForm>) -> ConstantMetadata {
|
||||
ConstantMetadata {
|
||||
name,
|
||||
name: s.name,
|
||||
ty: s.ty.id,
|
||||
value: s.value,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_runtime_api_metadata(
|
||||
name: ArcStr,
|
||||
s: v16::RuntimeApiMetadata<PortableForm>,
|
||||
) -> RuntimeApiMetadataInner {
|
||||
RuntimeApiMetadataInner {
|
||||
name,
|
||||
docs: s.docs,
|
||||
methods: s
|
||||
.methods
|
||||
.into_iter()
|
||||
.map(|m| {
|
||||
let name: ArcStr = m.name.clone().into();
|
||||
(name.clone(), from_runtime_api_method_metadata(name, m))
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_runtime_api_method_metadata(
|
||||
name: ArcStr,
|
||||
s: v16::RuntimeApiMethodMetadata<PortableForm>,
|
||||
) -> RuntimeApiMethodMetadataInner {
|
||||
RuntimeApiMethodMetadataInner {
|
||||
name,
|
||||
inputs: s
|
||||
.inputs
|
||||
.into_iter()
|
||||
.map(|param| MethodParamMetadata {
|
||||
name: param.name,
|
||||
ty: param.ty.id,
|
||||
})
|
||||
.collect(),
|
||||
output_ty: s.output.id,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_view_function_metadata(
|
||||
name: ArcStr,
|
||||
s: v16::PalletViewFunctionMetadata<PortableForm>,
|
||||
) -> ViewFunctionMetadataInner {
|
||||
ViewFunctionMetadataInner {
|
||||
name,
|
||||
query_id: s.id,
|
||||
inputs: s
|
||||
.inputs
|
||||
.into_iter()
|
||||
.map(|param| MethodParamMetadata {
|
||||
name: param.name,
|
||||
ty: param.ty.id,
|
||||
})
|
||||
.collect(),
|
||||
output_ty: s.output.id,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
+446
-168
@@ -24,13 +24,22 @@ mod utils;
|
||||
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::String;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
use frame_decode::constants::{ConstantEntry, ConstantInfo, ConstantInfoError};
|
||||
use frame_decode::custom_values::{CustomValue, CustomValueInfo, CustomValueInfoError};
|
||||
use frame_decode::extrinsics::{
|
||||
ExtrinsicCallInfo, ExtrinsicExtensionInfo, ExtrinsicInfoArg, ExtrinsicInfoError,
|
||||
ExtrinsicSignatureInfo,
|
||||
};
|
||||
use frame_decode::runtime_apis::{
|
||||
RuntimeApiEntry, RuntimeApiInfo, RuntimeApiInfoError, RuntimeApiInput,
|
||||
};
|
||||
use frame_decode::storage::{StorageEntry, StorageInfo, StorageInfoError, StorageKeyInfo};
|
||||
use frame_decode::view_functions::{
|
||||
ViewFunctionEntry, ViewFunctionInfo, ViewFunctionInfoError, ViewFunctionInput,
|
||||
};
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use scale_info::{PortableRegistry, Variant, form::PortableForm};
|
||||
use utils::{
|
||||
@@ -39,12 +48,14 @@ use utils::{
|
||||
variant_index::VariantIndex,
|
||||
};
|
||||
|
||||
type ArcStr = Arc<str>;
|
||||
|
||||
pub use frame_decode::storage::StorageHasher;
|
||||
pub use from::SUPPORTED_METADATA_VERSIONS;
|
||||
pub use from::TryFromError;
|
||||
pub use utils::validation::MetadataHasher;
|
||||
|
||||
#[cfg(feature = "legacy")]
|
||||
pub use from::legacy::Error as LegacyFromError;
|
||||
|
||||
type CustomMetadataInner = frame_metadata::v15::CustomMetadata<PortableForm>;
|
||||
|
||||
/// Node metadata. This can be constructed by providing some compatible [`frame_metadata`]
|
||||
@@ -55,9 +66,19 @@ pub struct Metadata {
|
||||
/// Type registry containing all types used in the metadata.
|
||||
types: PortableRegistry,
|
||||
/// Metadata of all the pallets.
|
||||
pallets: OrderedMap<ArcStr, PalletMetadataInner>,
|
||||
/// Find the location in the pallet Vec by pallet index.
|
||||
pallets_by_index: HashMap<u8, usize>,
|
||||
pallets: OrderedMap<String, PalletMetadataInner>,
|
||||
/// Find the pallet for a given call index.
|
||||
pallets_by_call_index: HashMap<u8, usize>,
|
||||
/// Find the pallet for a given event index.
|
||||
///
|
||||
/// for modern metadatas, this is the same as pallets_by_call_index,
|
||||
/// but for old metadatas this can vary.
|
||||
pallets_by_event_index: HashMap<u8, usize>,
|
||||
/// Find the pallet for a given error index.
|
||||
///
|
||||
/// for modern metadatas, this is the same as pallets_by_call_index,
|
||||
/// but for old metadatas this can vary.
|
||||
pallets_by_error_index: HashMap<u8, usize>,
|
||||
/// Metadata of the extrinsic.
|
||||
extrinsic: ExtrinsicMetadata,
|
||||
/// The types of the outer enums.
|
||||
@@ -65,7 +86,7 @@ pub struct Metadata {
|
||||
/// The type Id of the `DispatchError` type, which Subxt makes use of.
|
||||
dispatch_error_ty: Option<u32>,
|
||||
/// Details about each of the runtime API traits.
|
||||
apis: OrderedMap<ArcStr, RuntimeApiMetadataInner>,
|
||||
apis: OrderedMap<String, RuntimeApiMetadataInner>,
|
||||
/// Allows users to add custom types to the metadata. A map that associates a string key to a `CustomValueMetadata`.
|
||||
custom: CustomMetadataInner,
|
||||
}
|
||||
@@ -75,12 +96,12 @@ pub struct Metadata {
|
||||
impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
|
||||
type TypeId = u32;
|
||||
|
||||
fn get_call_info(
|
||||
fn extrinsic_call_info(
|
||||
&self,
|
||||
pallet_index: u8,
|
||||
call_index: u8,
|
||||
) -> Result<ExtrinsicCallInfo<'_, Self::TypeId>, ExtrinsicInfoError<'_>> {
|
||||
let pallet = self.pallet_by_index(pallet_index).ok_or({
|
||||
let pallet = self.pallet_by_call_index(pallet_index).ok_or({
|
||||
ExtrinsicInfoError::PalletNotFound {
|
||||
index: pallet_index,
|
||||
}
|
||||
@@ -108,7 +129,7 @@ impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_signature_info(
|
||||
fn extrinsic_signature_info(
|
||||
&self,
|
||||
) -> Result<ExtrinsicSignatureInfo<Self::TypeId>, ExtrinsicInfoError<'_>> {
|
||||
Ok(ExtrinsicSignatureInfo {
|
||||
@@ -117,7 +138,7 @@ impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_extension_info(
|
||||
fn extrinsic_extension_info(
|
||||
&self,
|
||||
extension_version: Option<u8>,
|
||||
) -> Result<ExtrinsicExtensionInfo<'_, Self::TypeId>, ExtrinsicInfoError<'_>> {
|
||||
@@ -142,8 +163,292 @@ impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
|
||||
Ok(ExtrinsicExtensionInfo { extension_ids })
|
||||
}
|
||||
}
|
||||
impl frame_decode::storage::StorageTypeInfo for Metadata {
|
||||
type TypeId = u32;
|
||||
|
||||
fn storage_info(
|
||||
&self,
|
||||
pallet_name: &str,
|
||||
storage_entry: &str,
|
||||
) -> Result<StorageInfo<'_, Self::TypeId>, StorageInfoError<'_>> {
|
||||
let pallet =
|
||||
self.pallet_by_name(pallet_name)
|
||||
.ok_or_else(|| StorageInfoError::PalletNotFound {
|
||||
pallet_name: pallet_name.to_string(),
|
||||
})?;
|
||||
let entry = pallet
|
||||
.storage()
|
||||
.and_then(|storage| storage.entry_by_name(storage_entry))
|
||||
.ok_or_else(|| StorageInfoError::StorageNotFound {
|
||||
name: storage_entry.to_string(),
|
||||
pallet_name: Cow::Borrowed(pallet.name()),
|
||||
})?;
|
||||
|
||||
let info = StorageInfo {
|
||||
keys: Cow::Borrowed(&*entry.info.keys),
|
||||
value_id: entry.info.value_id,
|
||||
default_value: entry
|
||||
.info
|
||||
.default_value
|
||||
.as_ref()
|
||||
.map(|def| Cow::Borrowed(&**def)),
|
||||
};
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
}
|
||||
impl frame_decode::storage::StorageEntryInfo for Metadata {
|
||||
fn storage_entries(&self) -> impl Iterator<Item = StorageEntry<'_>> {
|
||||
self.pallets().flat_map(|pallet| {
|
||||
let pallet_name = pallet.name();
|
||||
let pallet_iter = core::iter::once(StorageEntry::In(pallet_name.into()));
|
||||
let entries_iter = pallet.storage().into_iter().flat_map(|storage| {
|
||||
storage
|
||||
.entries()
|
||||
.iter()
|
||||
.map(|entry| StorageEntry::Name(entry.name().into()))
|
||||
});
|
||||
|
||||
pallet_iter.chain(entries_iter)
|
||||
})
|
||||
}
|
||||
}
|
||||
impl frame_decode::runtime_apis::RuntimeApiTypeInfo for Metadata {
|
||||
type TypeId = u32;
|
||||
|
||||
fn runtime_api_info(
|
||||
&self,
|
||||
trait_name: &str,
|
||||
method_name: &str,
|
||||
) -> Result<RuntimeApiInfo<'_, Self::TypeId>, RuntimeApiInfoError<'_>> {
|
||||
let api_trait =
|
||||
self.apis
|
||||
.get_by_key(trait_name)
|
||||
.ok_or_else(|| RuntimeApiInfoError::TraitNotFound {
|
||||
trait_name: trait_name.to_string(),
|
||||
})?;
|
||||
let api_method = api_trait.methods.get_by_key(method_name).ok_or_else(|| {
|
||||
RuntimeApiInfoError::MethodNotFound {
|
||||
trait_name: Cow::Borrowed(&api_trait.name),
|
||||
method_name: method_name.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
let info = RuntimeApiInfo {
|
||||
inputs: Cow::Borrowed(&api_method.info.inputs),
|
||||
output_id: api_method.info.output_id,
|
||||
};
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
}
|
||||
impl frame_decode::runtime_apis::RuntimeApiEntryInfo for Metadata {
|
||||
fn runtime_api_entries(&self) -> impl Iterator<Item = RuntimeApiEntry<'_>> {
|
||||
self.runtime_api_traits().flat_map(|api_trait| {
|
||||
let trait_name = api_trait.name();
|
||||
let trait_iter = core::iter::once(RuntimeApiEntry::In(trait_name.into()));
|
||||
let method_iter = api_trait
|
||||
.methods()
|
||||
.map(|method| RuntimeApiEntry::Name(method.name().into()));
|
||||
|
||||
trait_iter.chain(method_iter)
|
||||
})
|
||||
}
|
||||
}
|
||||
impl frame_decode::view_functions::ViewFunctionTypeInfo for Metadata {
|
||||
type TypeId = u32;
|
||||
|
||||
fn view_function_info(
|
||||
&self,
|
||||
pallet_name: &str,
|
||||
function_name: &str,
|
||||
) -> Result<ViewFunctionInfo<'_, Self::TypeId>, ViewFunctionInfoError<'_>> {
|
||||
let pallet = self.pallet_by_name(pallet_name).ok_or_else(|| {
|
||||
ViewFunctionInfoError::PalletNotFound {
|
||||
pallet_name: pallet_name.to_string(),
|
||||
}
|
||||
})?;
|
||||
let function = pallet.view_function_by_name(function_name).ok_or_else(|| {
|
||||
ViewFunctionInfoError::FunctionNotFound {
|
||||
pallet_name: Cow::Borrowed(pallet.name()),
|
||||
function_name: function_name.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
let info = ViewFunctionInfo {
|
||||
inputs: Cow::Borrowed(&function.inner.info.inputs),
|
||||
output_id: function.inner.info.output_id,
|
||||
query_id: *function.query_id(),
|
||||
};
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
}
|
||||
impl frame_decode::view_functions::ViewFunctionEntryInfo for Metadata {
|
||||
fn view_function_entries(&self) -> impl Iterator<Item = ViewFunctionEntry<'_>> {
|
||||
self.pallets().flat_map(|pallet| {
|
||||
let pallet_name = pallet.name();
|
||||
let pallet_iter = core::iter::once(ViewFunctionEntry::In(pallet_name.into()));
|
||||
let fn_iter = pallet
|
||||
.view_functions()
|
||||
.map(|function| ViewFunctionEntry::Name(function.name().into()));
|
||||
|
||||
pallet_iter.chain(fn_iter)
|
||||
})
|
||||
}
|
||||
}
|
||||
impl frame_decode::constants::ConstantTypeInfo for Metadata {
|
||||
type TypeId = u32;
|
||||
|
||||
fn constant_info(
|
||||
&self,
|
||||
pallet_name: &str,
|
||||
constant_name: &str,
|
||||
) -> Result<ConstantInfo<'_, Self::TypeId>, ConstantInfoError<'_>> {
|
||||
let pallet =
|
||||
self.pallet_by_name(pallet_name)
|
||||
.ok_or_else(|| ConstantInfoError::PalletNotFound {
|
||||
pallet_name: pallet_name.to_string(),
|
||||
})?;
|
||||
let constant = pallet.constant_by_name(constant_name).ok_or_else(|| {
|
||||
ConstantInfoError::ConstantNotFound {
|
||||
pallet_name: Cow::Borrowed(pallet.name()),
|
||||
constant_name: constant_name.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
let info = ConstantInfo {
|
||||
bytes: &constant.value,
|
||||
type_id: constant.ty,
|
||||
};
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
}
|
||||
impl frame_decode::constants::ConstantEntryInfo for Metadata {
|
||||
fn constant_entries(&self) -> impl Iterator<Item = ConstantEntry<'_>> {
|
||||
self.pallets().flat_map(|pallet| {
|
||||
let pallet_name = pallet.name();
|
||||
let pallet_iter = core::iter::once(ConstantEntry::In(pallet_name.into()));
|
||||
let constant_iter = pallet
|
||||
.constants()
|
||||
.map(|constant| ConstantEntry::Name(constant.name().into()));
|
||||
|
||||
pallet_iter.chain(constant_iter)
|
||||
})
|
||||
}
|
||||
}
|
||||
impl frame_decode::custom_values::CustomValueTypeInfo for Metadata {
|
||||
type TypeId = u32;
|
||||
|
||||
fn custom_value_info(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<CustomValueInfo<'_, Self::TypeId>, CustomValueInfoError> {
|
||||
let custom_value = self
|
||||
.custom()
|
||||
.get(name)
|
||||
.ok_or_else(|| CustomValueInfoError {
|
||||
not_found: name.to_string(),
|
||||
})?;
|
||||
|
||||
let info = CustomValueInfo {
|
||||
bytes: custom_value.data,
|
||||
type_id: custom_value.type_id,
|
||||
};
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
}
|
||||
impl frame_decode::custom_values::CustomValueEntryInfo for Metadata {
|
||||
fn custom_values(&self) -> impl Iterator<Item = CustomValue<'_>> {
|
||||
self.custom.map.keys().map(|name| CustomValue {
|
||||
name: Cow::Borrowed(name),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
/// This is essentially an alias for `<Metadata as codec::Decode>::decode(&mut bytes)`
|
||||
pub fn decode_from(mut bytes: &[u8]) -> Result<Self, codec::Error> {
|
||||
<Self as codec::Decode>::decode(&mut bytes)
|
||||
}
|
||||
|
||||
/// Convert V16 metadata into [`Metadata`].
|
||||
pub fn from_v16(
|
||||
metadata: frame_metadata::v16::RuntimeMetadataV16,
|
||||
) -> Result<Self, TryFromError> {
|
||||
metadata.try_into()
|
||||
}
|
||||
|
||||
/// Convert V15 metadata into [`Metadata`].
|
||||
pub fn from_v15(
|
||||
metadata: frame_metadata::v15::RuntimeMetadataV15,
|
||||
) -> Result<Self, TryFromError> {
|
||||
metadata.try_into()
|
||||
}
|
||||
|
||||
/// Convert V14 metadata into [`Metadata`].
|
||||
pub fn from_v14(
|
||||
metadata: frame_metadata::v14::RuntimeMetadataV14,
|
||||
) -> Result<Self, TryFromError> {
|
||||
metadata.try_into()
|
||||
}
|
||||
|
||||
/// Convert V13 metadata into [`Metadata`], given the necessary extra type information.
|
||||
#[cfg(feature = "legacy")]
|
||||
pub fn from_v13(
|
||||
metadata: &frame_metadata::v13::RuntimeMetadataV13,
|
||||
types: &scale_info_legacy::TypeRegistrySet<'_>,
|
||||
) -> Result<Self, LegacyFromError> {
|
||||
from::legacy::from_v13(metadata, types, from::legacy::Opts::compat())
|
||||
}
|
||||
|
||||
/// Convert V12 metadata into [`Metadata`], given the necessary extra type information.
|
||||
#[cfg(feature = "legacy")]
|
||||
pub fn from_v12(
|
||||
metadata: &frame_metadata::v12::RuntimeMetadataV12,
|
||||
types: &scale_info_legacy::TypeRegistrySet<'_>,
|
||||
) -> Result<Self, LegacyFromError> {
|
||||
from::legacy::from_v12(metadata, types, from::legacy::Opts::compat())
|
||||
}
|
||||
|
||||
/// Convert V13 metadata into [`Metadata`], given the necessary extra type information.
|
||||
#[cfg(feature = "legacy")]
|
||||
pub fn from_v11(
|
||||
metadata: &frame_metadata::v11::RuntimeMetadataV11,
|
||||
types: &scale_info_legacy::TypeRegistrySet<'_>,
|
||||
) -> Result<Self, LegacyFromError> {
|
||||
from::legacy::from_v11(metadata, types, from::legacy::Opts::compat())
|
||||
}
|
||||
|
||||
/// Convert V13 metadata into [`Metadata`], given the necessary extra type information.
|
||||
#[cfg(feature = "legacy")]
|
||||
pub fn from_v10(
|
||||
metadata: &frame_metadata::v10::RuntimeMetadataV10,
|
||||
types: &scale_info_legacy::TypeRegistrySet<'_>,
|
||||
) -> Result<Self, LegacyFromError> {
|
||||
from::legacy::from_v10(metadata, types, from::legacy::Opts::compat())
|
||||
}
|
||||
|
||||
/// Convert V9 metadata into [`Metadata`], given the necessary extra type information.
|
||||
#[cfg(feature = "legacy")]
|
||||
pub fn from_v9(
|
||||
metadata: &frame_metadata::v9::RuntimeMetadataV9,
|
||||
types: &scale_info_legacy::TypeRegistrySet<'_>,
|
||||
) -> Result<Self, LegacyFromError> {
|
||||
from::legacy::from_v9(metadata, types, from::legacy::Opts::compat())
|
||||
}
|
||||
|
||||
/// Convert V8 metadata into [`Metadata`], given the necessary extra type information.
|
||||
#[cfg(feature = "legacy")]
|
||||
pub fn from_v8(
|
||||
metadata: &frame_metadata::v8::RuntimeMetadataV8,
|
||||
types: &scale_info_legacy::TypeRegistrySet<'_>,
|
||||
) -> Result<Self, LegacyFromError> {
|
||||
from::legacy::from_v8(metadata, types, from::legacy::Opts::compat())
|
||||
}
|
||||
|
||||
/// Access the underlying type registry.
|
||||
pub fn types(&self) -> &PortableRegistry {
|
||||
&self.types
|
||||
@@ -177,10 +482,36 @@ impl Metadata {
|
||||
})
|
||||
}
|
||||
|
||||
/// Access a pallet given its encoded variant index.
|
||||
pub fn pallet_by_index(&self, variant_index: u8) -> Option<PalletMetadata<'_>> {
|
||||
/// Access a pallet given some call/extrinsic pallet index byte
|
||||
pub fn pallet_by_call_index(&self, variant_index: u8) -> Option<PalletMetadata<'_>> {
|
||||
let inner = self
|
||||
.pallets_by_index
|
||||
.pallets_by_call_index
|
||||
.get(&variant_index)
|
||||
.and_then(|i| self.pallets.get_by_index(*i))?;
|
||||
|
||||
Some(PalletMetadata {
|
||||
inner,
|
||||
types: self.types(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Access a pallet given some event pallet index byte
|
||||
pub fn pallet_by_event_index(&self, variant_index: u8) -> Option<PalletMetadata<'_>> {
|
||||
let inner = self
|
||||
.pallets_by_event_index
|
||||
.get(&variant_index)
|
||||
.and_then(|i| self.pallets.get_by_index(*i))?;
|
||||
|
||||
Some(PalletMetadata {
|
||||
inner,
|
||||
types: self.types(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Access a pallet given some error pallet index byte
|
||||
pub fn pallet_by_error_index(&self, variant_index: u8) -> Option<PalletMetadata<'_>> {
|
||||
let inner = self
|
||||
.pallets_by_error_index
|
||||
.get(&variant_index)
|
||||
.and_then(|i| self.pallets.get_by_index(*i))?;
|
||||
|
||||
@@ -217,20 +548,6 @@ impl Metadata {
|
||||
})
|
||||
}
|
||||
|
||||
/// Access a view function given its query ID, if any.
|
||||
pub fn view_function_by_query_id(
|
||||
&'_ self,
|
||||
query_id: &[u8; 32],
|
||||
) -> Option<ViewFunctionMetadata<'_>> {
|
||||
// Dev note: currently, we only have pallet view functions, and here
|
||||
// we just do a naive thing of iterating over the pallets to find the one
|
||||
// we're looking for. Eventually we should construct a separate map of view
|
||||
// functions for easy querying here.
|
||||
self.pallets()
|
||||
.flat_map(|p| p.view_functions())
|
||||
.find(|vf| vf.query_id() == query_id)
|
||||
}
|
||||
|
||||
/// Returns custom user defined types
|
||||
pub fn custom(&self) -> CustomMetadata<'_> {
|
||||
CustomMetadata {
|
||||
@@ -264,9 +581,19 @@ impl<'a> PalletMetadata<'a> {
|
||||
&self.inner.name
|
||||
}
|
||||
|
||||
/// The pallet index.
|
||||
pub fn index(&self) -> u8 {
|
||||
self.inner.index
|
||||
/// The index to use for calls in this pallet.
|
||||
pub fn call_index(&self) -> u8 {
|
||||
self.inner.call_index
|
||||
}
|
||||
|
||||
/// The index to use for events in this pallet.
|
||||
pub fn event_index(&self) -> u8 {
|
||||
self.inner.event_index
|
||||
}
|
||||
|
||||
/// The index to use for errors in this pallet.
|
||||
pub fn error_index(&self) -> u8 {
|
||||
self.inner.error_index
|
||||
}
|
||||
|
||||
/// The pallet docs.
|
||||
@@ -418,9 +745,19 @@ impl<'a> PalletMetadata<'a> {
|
||||
#[derive(Debug, Clone)]
|
||||
struct PalletMetadataInner {
|
||||
/// Pallet name.
|
||||
name: ArcStr,
|
||||
/// Pallet index.
|
||||
index: u8,
|
||||
name: String,
|
||||
/// The index for calls in the pallet.
|
||||
call_index: u8,
|
||||
/// The index for events in the pallet.
|
||||
///
|
||||
/// This is the same as `call_index` for modern metadatas,
|
||||
/// but can be different for older metadatas (pre-V12).
|
||||
event_index: u8,
|
||||
/// The index for errors in the pallet.
|
||||
///
|
||||
/// This is the same as `call_index` for modern metadatas,
|
||||
/// but can be different for older metadatas (pre-V12).
|
||||
error_index: u8,
|
||||
/// Pallet storage metadata.
|
||||
storage: Option<StorageMetadata>,
|
||||
/// Type ID for the pallet Call enum.
|
||||
@@ -436,9 +773,9 @@ struct PalletMetadataInner {
|
||||
/// Error variants by name/u8.
|
||||
error_variant_index: VariantIndex,
|
||||
/// Map from constant name to constant details.
|
||||
constants: OrderedMap<ArcStr, ConstantMetadata>,
|
||||
constants: OrderedMap<String, ConstantMetadata>,
|
||||
/// Details about each of the pallet view functions.
|
||||
view_functions: OrderedMap<ArcStr, ViewFunctionMetadataInner>,
|
||||
view_functions: OrderedMap<String, ViewFunctionMetadataInner>,
|
||||
/// Mapping from associated type to type ID describing its shape.
|
||||
associated_types: BTreeMap<String, u32>,
|
||||
/// Pallet documentation.
|
||||
@@ -451,7 +788,7 @@ pub struct StorageMetadata {
|
||||
/// The common prefix used by all storage entries.
|
||||
prefix: String,
|
||||
/// Map from storage entry name to details.
|
||||
entries: OrderedMap<ArcStr, StorageEntryMetadata>,
|
||||
entries: OrderedMap<String, StorageEntryMetadata>,
|
||||
}
|
||||
|
||||
impl StorageMetadata {
|
||||
@@ -475,13 +812,9 @@ impl StorageMetadata {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StorageEntryMetadata {
|
||||
/// Variable name of the storage entry.
|
||||
name: ArcStr,
|
||||
/// An `Option` modifier of that storage entry.
|
||||
modifier: StorageEntryModifier,
|
||||
/// Type of the value stored in the entry.
|
||||
entry_type: StorageEntryType,
|
||||
/// Default value (SCALE encoded).
|
||||
default: Vec<u8>,
|
||||
name: String,
|
||||
/// Information about the storage entry.
|
||||
info: StorageInfo<'static, u32>,
|
||||
/// Storage entry documentation.
|
||||
docs: Vec<String>,
|
||||
}
|
||||
@@ -491,17 +824,18 @@ impl StorageEntryMetadata {
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
/// Is the entry value optional or does it have a default value.
|
||||
pub fn modifier(&self) -> StorageEntryModifier {
|
||||
self.modifier
|
||||
/// Keys in this storage entry.
|
||||
pub fn keys(&self) -> impl ExactSizeIterator<Item = &StorageKeyInfo<u32>> {
|
||||
let keys = &*self.info.keys;
|
||||
keys.iter()
|
||||
}
|
||||
/// Type of the storage entry.
|
||||
pub fn entry_type(&self) -> &StorageEntryType {
|
||||
&self.entry_type
|
||||
/// Value type for this storage entry.
|
||||
pub fn value_ty(&self) -> u32 {
|
||||
self.info.value_id
|
||||
}
|
||||
/// The SCALE encoded default value for this entry.
|
||||
pub fn default_bytes(&self) -> &[u8] {
|
||||
&self.default
|
||||
/// The default value, if one exists, for this entry.
|
||||
pub fn default_value(&self) -> Option<&[u8]> {
|
||||
self.info.default_value.as_deref()
|
||||
}
|
||||
/// Storage entry documentation.
|
||||
pub fn docs(&self) -> &[String] {
|
||||
@@ -509,101 +843,11 @@ impl StorageEntryMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of a storage entry.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum StorageEntryType {
|
||||
/// Plain storage entry (just the value).
|
||||
Plain(u32),
|
||||
/// A storage map.
|
||||
Map {
|
||||
/// One or more hashers, should be one hasher per key element.
|
||||
hashers: Vec<StorageHasher>,
|
||||
/// The type of the key, can be a tuple with elements for each of the hashers.
|
||||
key_ty: u32,
|
||||
/// The type of the value.
|
||||
value_ty: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl StorageEntryType {
|
||||
/// The type of the value.
|
||||
pub fn value_ty(&self) -> u32 {
|
||||
match self {
|
||||
StorageEntryType::Map { value_ty, .. } | StorageEntryType::Plain(value_ty) => *value_ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of the key, can be a tuple with elements for each of the hashers. None for a Plain storage entry.
|
||||
pub fn key_ty(&self) -> Option<u32> {
|
||||
match self {
|
||||
StorageEntryType::Map { key_ty, .. } => Some(*key_ty),
|
||||
StorageEntryType::Plain(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Hasher used by storage maps.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum StorageHasher {
|
||||
/// 128-bit Blake2 hash.
|
||||
Blake2_128,
|
||||
/// 256-bit Blake2 hash.
|
||||
Blake2_256,
|
||||
/// Multiple 128-bit Blake2 hashes concatenated.
|
||||
Blake2_128Concat,
|
||||
/// 128-bit XX hash.
|
||||
Twox128,
|
||||
/// 256-bit XX hash.
|
||||
Twox256,
|
||||
/// Multiple 64-bit XX hashes concatenated.
|
||||
Twox64Concat,
|
||||
/// Identity hashing (no hashing).
|
||||
Identity,
|
||||
}
|
||||
|
||||
impl StorageHasher {
|
||||
/// The hash produced by a [`StorageHasher`] can have these two components, in order:
|
||||
///
|
||||
/// 1. A fixed size hash. (not present for [`StorageHasher::Identity`]).
|
||||
/// 2. The SCALE encoded key that was used as an input to the hasher (only present for
|
||||
/// [`StorageHasher::Twox64Concat`], [`StorageHasher::Blake2_128Concat`] or [`StorageHasher::Identity`]).
|
||||
///
|
||||
/// This function returns the number of bytes used to represent the first of these.
|
||||
pub fn len_excluding_key(&self) -> usize {
|
||||
match self {
|
||||
StorageHasher::Blake2_128Concat => 16,
|
||||
StorageHasher::Twox64Concat => 8,
|
||||
StorageHasher::Blake2_128 => 16,
|
||||
StorageHasher::Blake2_256 => 32,
|
||||
StorageHasher::Twox128 => 16,
|
||||
StorageHasher::Twox256 => 32,
|
||||
StorageHasher::Identity => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the key used to produce the hash is appended to the hash itself.
|
||||
pub fn ends_with_key(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
StorageHasher::Blake2_128Concat | StorageHasher::Twox64Concat | StorageHasher::Identity
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the storage entry optional, or does it have a default value.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum StorageEntryModifier {
|
||||
/// The storage entry returns an `Option<T>`, with `None` if the key is not present.
|
||||
Optional,
|
||||
/// The storage entry returns `T::Default` if the key is not present.
|
||||
Default,
|
||||
}
|
||||
|
||||
/// Metadata for a single constant.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConstantMetadata {
|
||||
/// Name of the pallet constant.
|
||||
name: ArcStr,
|
||||
name: String,
|
||||
/// Type of the pallet constant.
|
||||
ty: u32,
|
||||
/// Value stored in the constant (SCALE encoded).
|
||||
@@ -816,9 +1060,9 @@ impl<'a> RuntimeApiMetadata<'a> {
|
||||
#[derive(Debug, Clone)]
|
||||
struct RuntimeApiMetadataInner {
|
||||
/// Trait name.
|
||||
name: ArcStr,
|
||||
name: String,
|
||||
/// Trait methods.
|
||||
methods: OrderedMap<ArcStr, RuntimeApiMethodMetadataInner>,
|
||||
methods: OrderedMap<String, RuntimeApiMethodMetadataInner>,
|
||||
/// Trait documentation.
|
||||
docs: Vec<String>,
|
||||
}
|
||||
@@ -841,12 +1085,15 @@ impl<'a> RuntimeApiMethodMetadata<'a> {
|
||||
&self.inner.docs
|
||||
}
|
||||
/// Method inputs.
|
||||
pub fn inputs(&self) -> impl ExactSizeIterator<Item = &'a MethodParamMetadata> + use<'a> {
|
||||
self.inner.inputs.iter()
|
||||
pub fn inputs(
|
||||
&self,
|
||||
) -> impl ExactSizeIterator<Item = &'a RuntimeApiInput<'static, u32>> + use<'a> {
|
||||
let inputs = &*self.inner.info.inputs;
|
||||
inputs.iter()
|
||||
}
|
||||
/// Method return type.
|
||||
pub fn output_ty(&self) -> u32 {
|
||||
self.inner.output_ty
|
||||
self.inner.info.output_id
|
||||
}
|
||||
/// Return a hash for the method.
|
||||
pub fn hash(&self) -> [u8; HASH_LEN] {
|
||||
@@ -857,11 +1104,9 @@ impl<'a> RuntimeApiMethodMetadata<'a> {
|
||||
#[derive(Debug, Clone)]
|
||||
struct RuntimeApiMethodMetadataInner {
|
||||
/// Method name.
|
||||
name: ArcStr,
|
||||
/// Method parameters.
|
||||
inputs: Vec<MethodParamMetadata>,
|
||||
/// Method output type.
|
||||
output_ty: u32,
|
||||
name: String,
|
||||
/// Info.
|
||||
info: RuntimeApiInfo<'static, u32>,
|
||||
/// Method documentation.
|
||||
docs: Vec<String>,
|
||||
}
|
||||
@@ -882,19 +1127,22 @@ impl<'a> ViewFunctionMetadata<'a> {
|
||||
/// Query ID. This is used to query the function. Roughly, it is constructed by doing
|
||||
/// `twox_128(pallet_name) ++ twox_128("fn_name(fnarg_types) -> return_ty")` .
|
||||
pub fn query_id(&self) -> &'a [u8; 32] {
|
||||
&self.inner.query_id
|
||||
&self.inner.info.query_id
|
||||
}
|
||||
/// Method documentation.
|
||||
pub fn docs(&self) -> &'a [String] {
|
||||
&self.inner.docs
|
||||
}
|
||||
/// Method inputs.
|
||||
pub fn inputs(&self) -> impl ExactSizeIterator<Item = &'a MethodParamMetadata> + use<'a> {
|
||||
self.inner.inputs.iter()
|
||||
pub fn inputs(
|
||||
&self,
|
||||
) -> impl ExactSizeIterator<Item = &'a ViewFunctionInput<'static, u32>> + use<'a> {
|
||||
let inputs = &*self.inner.info.inputs;
|
||||
inputs.iter()
|
||||
}
|
||||
/// Method return type.
|
||||
pub fn output_ty(&self) -> u32 {
|
||||
self.inner.output_ty
|
||||
self.inner.info.output_id
|
||||
}
|
||||
/// Return a hash for the method. The query ID of a view function validates it to some
|
||||
/// degree, but only takes type _names_ into account. This hash takes into account the
|
||||
@@ -907,13 +1155,9 @@ impl<'a> ViewFunctionMetadata<'a> {
|
||||
#[derive(Debug, Clone)]
|
||||
struct ViewFunctionMetadataInner {
|
||||
/// View function name.
|
||||
name: ArcStr,
|
||||
/// View function query ID.
|
||||
query_id: [u8; 32],
|
||||
/// Input types.
|
||||
inputs: Vec<MethodParamMetadata>,
|
||||
/// Output type.
|
||||
output_ty: u32,
|
||||
name: String,
|
||||
/// Info.
|
||||
info: ViewFunctionInfo<'static, u32>,
|
||||
/// Documentation.
|
||||
docs: Vec<String>,
|
||||
}
|
||||
@@ -999,6 +1243,38 @@ impl<'a> CustomValueMetadata<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode SCALE encoded metadata.
|
||||
///
|
||||
/// - The default assumption is that metadata is encoded as [`frame_metadata::RuntimeMetadataPrefixed`]. This is the
|
||||
/// expected format that metadata is encoded into.
|
||||
/// - if this fails, we also try to decode as [`frame_metadata::RuntimeMetadata`].
|
||||
/// - If this all fails, we also try to decode as [`frame_metadata::OpaqueMetadata`].
|
||||
pub fn decode_runtime_metadata(
|
||||
input: &[u8],
|
||||
) -> Result<frame_metadata::RuntimeMetadata, codec::Error> {
|
||||
use codec::Decode;
|
||||
|
||||
let err = match frame_metadata::RuntimeMetadataPrefixed::decode(&mut &*input) {
|
||||
Ok(md) => return Ok(md.1),
|
||||
Err(e) => e,
|
||||
};
|
||||
|
||||
if let Ok(md) = frame_metadata::RuntimeMetadata::decode(&mut &*input) {
|
||||
return Ok(md);
|
||||
}
|
||||
|
||||
// frame_metadata::OpaqueMetadata is a vec of bytes. If we can decode the length, AND
|
||||
// the length definitely corresponds to the number of remaining bytes, then we try to
|
||||
// decode the inner bytes.
|
||||
if let Ok(len) = codec::Compact::<u64>::decode(&mut &*input) {
|
||||
if input.len() == len.0 as usize {
|
||||
return decode_runtime_metadata(input);
|
||||
}
|
||||
}
|
||||
|
||||
Err(err)
|
||||
}
|
||||
|
||||
// Support decoding metadata from the "wire" format directly into this.
|
||||
// Errors may be lost in the case that the metadata content is somehow invalid.
|
||||
impl codec::Decode for Metadata {
|
||||
@@ -1008,9 +1284,11 @@ impl codec::Decode for Metadata {
|
||||
frame_metadata::RuntimeMetadata::V14(md) => md.try_into(),
|
||||
frame_metadata::RuntimeMetadata::V15(md) => md.try_into(),
|
||||
frame_metadata::RuntimeMetadata::V16(md) => md.try_into(),
|
||||
_ => return Err("Cannot try_into() to Metadata: unsupported metadata version".into()),
|
||||
_ => {
|
||||
return Err("Metadata::decode failed: Cannot try_into() to Metadata: unsupported metadata version".into())
|
||||
},
|
||||
};
|
||||
|
||||
metadata.map_err(|_e| "Cannot try_into() to Metadata.".into())
|
||||
metadata.map_err(|_| "Metadata::decode failed: Cannot try_into() to Metadata".into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
|
||||
use crate::{
|
||||
CustomMetadata, CustomValueMetadata, ExtrinsicMetadata, Metadata, PalletMetadata,
|
||||
RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata, StorageEntryType,
|
||||
ViewFunctionMetadata,
|
||||
RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata, ViewFunctionMetadata,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
use hashbrown::HashMap;
|
||||
@@ -290,29 +289,19 @@ fn get_extrinsic_hash(registry: &PortableRegistry, extrinsic: &ExtrinsicMetadata
|
||||
fn get_storage_entry_hash(registry: &PortableRegistry, entry: &StorageEntryMetadata) -> Hash {
|
||||
let mut bytes = concat_and_hash3(
|
||||
&hash(entry.name.as_bytes()),
|
||||
// Cloning 'entry.modifier' should essentially be a copy.
|
||||
&[entry.modifier as u8; HASH_LEN],
|
||||
&hash(&entry.default),
|
||||
&get_type_hash(registry, entry.info.value_id),
|
||||
&hash(entry.info.default_value.as_deref().unwrap_or_default()),
|
||||
);
|
||||
|
||||
match &entry.entry_type {
|
||||
StorageEntryType::Plain(ty) => concat_and_hash2(&bytes, &get_type_hash(registry, *ty)),
|
||||
StorageEntryType::Map {
|
||||
hashers,
|
||||
key_ty,
|
||||
value_ty,
|
||||
} => {
|
||||
for hasher in hashers {
|
||||
// Cloning the hasher should essentially be a copy.
|
||||
bytes = concat_and_hash2(&bytes, &[*hasher as u8; HASH_LEN]);
|
||||
}
|
||||
concat_and_hash3(
|
||||
&bytes,
|
||||
&get_type_hash(registry, *key_ty),
|
||||
&get_type_hash(registry, *value_ty),
|
||||
)
|
||||
}
|
||||
for key in &*entry.info.keys {
|
||||
bytes = concat_and_hash3(
|
||||
&bytes,
|
||||
&[key.hasher as u8; HASH_LEN],
|
||||
&get_type_hash(registry, key.key_id),
|
||||
)
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
fn get_custom_metadata_hash(custom_metadata: &CustomMetadata) -> Hash {
|
||||
@@ -382,7 +371,7 @@ pub fn get_runtime_api_hash(runtime_api: &RuntimeApiMethodMetadata) -> Hash {
|
||||
bytes = concat_and_hash3(
|
||||
&bytes,
|
||||
&hash(input.name.as_bytes()),
|
||||
&get_type_hash(registry, input.ty),
|
||||
&get_type_hash(registry, input.id),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -419,7 +408,7 @@ pub fn get_view_function_hash(view_function: &ViewFunctionMetadata) -> Hash {
|
||||
bytes = concat_and_hash3(
|
||||
&bytes,
|
||||
&hash(input.name.as_bytes()),
|
||||
&get_type_hash(registry, input.ty),
|
||||
&get_type_hash(registry, input.id),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user