mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 10:41:09 +00:00
Support V16 metadata and refactor metadata code (#1967)
* WIP integrate unstable v16 metadata into Subxt * first pass moving retain to the CLI tool * Remove otuer enum variant stripping and move now simpler strip_metadata to new crate. test it * tidyup to use stripmetadata package etc * Fix / comment out tests * fmt * clippy * Fix wasm example * wasm-example fix * wasm-example fix * Maske sure to move IDs around after types.retain() * fmt * Tweak comment * Find dispatch error separately to avoid issues during mapping * Expose associated type information in pallet metadata * Hopefully fix flaky archive RPC * remove unwanted temp file * Address nits * Add back commented-otu tests and address review comments * use either, and simplify for_each
This commit is contained in:
Generated
+12
@@ -5255,6 +5255,7 @@ dependencies = [
|
||||
"subxt-codegen",
|
||||
"subxt-metadata",
|
||||
"subxt-utils-fetchmetadata",
|
||||
"subxt-utils-stripmetadata",
|
||||
"syn 2.0.87",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
@@ -5367,6 +5368,7 @@ dependencies = [
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"sp-crypto-hashing",
|
||||
"subxt-utils-stripmetadata",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
@@ -5453,6 +5455,15 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subxt-utils-stripmetadata"
|
||||
version = "0.41.0"
|
||||
dependencies = [
|
||||
"either",
|
||||
"frame-metadata 20.0.0",
|
||||
"scale-info",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
@@ -5934,6 +5945,7 @@ dependencies = [
|
||||
"scale-info",
|
||||
"subxt",
|
||||
"subxt-metadata",
|
||||
"subxt-utils-stripmetadata",
|
||||
"trybuild",
|
||||
]
|
||||
|
||||
|
||||
+3
-1
@@ -17,6 +17,7 @@ members = [
|
||||
"subxt",
|
||||
"scripts/artifacts",
|
||||
"utils/fetch-metadata",
|
||||
"utils/strip-metadata",
|
||||
]
|
||||
|
||||
# We exclude any crates that would depend on non mutually
|
||||
@@ -79,7 +80,7 @@ derive-where = "1.2.7"
|
||||
either = { version = "1.13.0", default-features = false }
|
||||
finito = { version = "0.1.0", default-features = false }
|
||||
frame-decode = { version = "0.7.0", default-features = false }
|
||||
frame-metadata = { version = "20.0.0", default-features = false }
|
||||
frame-metadata = { version = "20.0.0", default-features = false, features = ["unstable"] }
|
||||
futures = { version = "0.3.31", default-features = false, features = ["std"] }
|
||||
getrandom = { version = "0.2", default-features = false }
|
||||
hashbrown = "0.14.5"
|
||||
@@ -160,6 +161,7 @@ subxt-signer = { version = "0.41.0", path = "signer", default-features = false }
|
||||
subxt-rpcs = { version = "0.41.0", path = "rpcs", default-features = false }
|
||||
subxt-lightclient = { version = "0.41.0", path = "lightclient", default-features = false }
|
||||
subxt-utils-fetchmetadata = { version = "0.41.0", path = "utils/fetch-metadata", default-features = false }
|
||||
subxt-utils-stripmetadata = { version = "0.41.0", path = "utils/strip-metadata", default-features = false }
|
||||
test-runtime = { path = "testing/test-runtime" }
|
||||
substrate-runner = { path = "testing/substrate-runner" }
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ chain-spec-pruning = ["smoldot"]
|
||||
[dependencies]
|
||||
subxt-codegen = { workspace = true }
|
||||
subxt-utils-fetchmetadata = { workspace = true, features = ["url"] }
|
||||
subxt-utils-stripmetadata = { workspace = true }
|
||||
subxt-metadata = { workspace = true }
|
||||
subxt = { workspace = true, features = ["default"] }
|
||||
clap = { workspace = true }
|
||||
|
||||
@@ -6,9 +6,9 @@ use crate::utils::{validate_url_security, FileOrUrl};
|
||||
use clap::Parser as ClapParser;
|
||||
use codec::{Decode, Encode};
|
||||
use color_eyre::eyre::{self, bail};
|
||||
use frame_metadata::{v15::RuntimeMetadataV15, RuntimeMetadata, RuntimeMetadataPrefixed};
|
||||
use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed};
|
||||
use std::{io::Write, path::PathBuf};
|
||||
use subxt_metadata::Metadata;
|
||||
use subxt_utils_stripmetadata::StripMetadata;
|
||||
|
||||
/// Download metadata from a substrate node, for use with `subxt` codegen.
|
||||
#[derive(Debug, ClapParser)]
|
||||
@@ -43,35 +43,26 @@ pub struct Opts {
|
||||
pub async fn run(opts: Opts, output: &mut impl Write) -> color_eyre::Result<()> {
|
||||
validate_url_security(opts.file_or_url.url.as_ref(), opts.allow_insecure)?;
|
||||
let bytes = opts.file_or_url.fetch().await?;
|
||||
|
||||
let mut metadata = RuntimeMetadataPrefixed::decode(&mut &bytes[..])?;
|
||||
|
||||
let version = match &metadata.1 {
|
||||
RuntimeMetadata::V14(_) => Version::V14,
|
||||
RuntimeMetadata::V15(_) => Version::V15,
|
||||
_ => Version::Unknown,
|
||||
};
|
||||
|
||||
// Strip pallets or runtime APIs if names are provided:
|
||||
if opts.pallets.is_some() || opts.runtime_apis.is_some() {
|
||||
// convert to internal type:
|
||||
let mut md = Metadata::try_from(metadata)?;
|
||||
|
||||
// retain pallets and/or runtime APIs given:
|
||||
let retain_pallets_fn: Box<dyn Fn(&str) -> bool> = match opts.pallets.as_ref() {
|
||||
let keep_pallets_fn: Box<dyn Fn(&str) -> bool> = match opts.pallets.as_ref() {
|
||||
Some(pallets) => Box::new(|name| pallets.iter().any(|p| &**p == name)),
|
||||
None => Box::new(|_| true),
|
||||
};
|
||||
let retain_runtime_apis_fn: Box<dyn Fn(&str) -> bool> = match opts.runtime_apis.as_ref() {
|
||||
let keep_runtime_apis_fn: Box<dyn Fn(&str) -> bool> = match opts.runtime_apis.as_ref() {
|
||||
Some(apis) => Box::new(|name| apis.iter().any(|p| &**p == name)),
|
||||
None => Box::new(|_| true),
|
||||
};
|
||||
md.retain(retain_pallets_fn, retain_runtime_apis_fn);
|
||||
|
||||
// Convert back to wire format, preserving version:
|
||||
metadata = match version {
|
||||
Version::V14 => RuntimeMetadataV15::from(md).into(),
|
||||
Version::V15 => RuntimeMetadataV15::from(md).into(),
|
||||
Version::Unknown => {
|
||||
bail!("Unsupported metadata version; V14 or V15 metadata is expected.")
|
||||
match &mut metadata.1 {
|
||||
RuntimeMetadata::V14(md) => md.strip_metadata(keep_pallets_fn, keep_runtime_apis_fn),
|
||||
RuntimeMetadata::V15(md) => md.strip_metadata(keep_pallets_fn, keep_runtime_apis_fn),
|
||||
RuntimeMetadata::V16(md) => md.strip_metadata(keep_pallets_fn, keep_runtime_apis_fn),
|
||||
_ => {
|
||||
bail!("Unsupported metadata version for stripping pallets/runtime APIs: V14, V15 or V16 metadata is expected.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,9 +94,3 @@ pub async fn run(opts: Opts, output: &mut impl Write) -> color_eyre::Result<()>
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
enum Version {
|
||||
V14,
|
||||
V15,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
@@ -157,8 +157,6 @@ impl RuntimeGenerator {
|
||||
.collect();
|
||||
let runtime_api_names_len = runtime_api_names.len();
|
||||
|
||||
let metadata_hash = self.metadata.hasher().hash();
|
||||
|
||||
let modules = pallets_with_mod_names
|
||||
.iter()
|
||||
.map(|(pallet, mod_name)| {
|
||||
@@ -219,7 +217,6 @@ impl RuntimeGenerator {
|
||||
|
||||
// Fetch the paths of the outer enums.
|
||||
// Substrate exposes those under `kitchensink_runtime`, while Polkadot under `polkadot_runtime`.
|
||||
|
||||
let call_path = type_gen
|
||||
.resolve_type_path(self.metadata.outer_enums().call_enum_ty())?
|
||||
.to_token_stream(type_gen.settings());
|
||||
@@ -230,6 +227,8 @@ impl RuntimeGenerator {
|
||||
.resolve_type_path(self.metadata.outer_enums().error_enum_ty())?
|
||||
.to_token_stream(type_gen.settings());
|
||||
|
||||
let metadata_hash = self.metadata.hasher().hash();
|
||||
|
||||
let custom_values = generate_custom_values(&self.metadata, &type_gen, &crate_path);
|
||||
|
||||
Ok(quote! {
|
||||
|
||||
@@ -33,7 +33,7 @@ fn generate_runtime_api(
|
||||
.then_some(quote! { #( #[doc = #docs ] )* })
|
||||
.unwrap_or_default();
|
||||
|
||||
let structs_and_methods: Vec<_> = api
|
||||
let structs_and_methods = api
|
||||
.methods()
|
||||
.map(|method| {
|
||||
let method_name = format_ident!("{}", method.name());
|
||||
@@ -126,13 +126,7 @@ fn generate_runtime_api(
|
||||
}
|
||||
);
|
||||
|
||||
let Some(call_hash) = api.method_hash(method.name()) else {
|
||||
return Err(CodegenError::MissingRuntimeApiMetadata(
|
||||
trait_name_str.to_owned(),
|
||||
method_name_str.to_owned(),
|
||||
))
|
||||
};
|
||||
|
||||
let call_hash = method.hash();
|
||||
let method = quote!(
|
||||
#docs
|
||||
pub fn #method_name(&self, #( #fn_params, )* ) -> #crate_path::runtime_api::payload::StaticPayload<types::#struct_name, types::#method_name::output::Output> {
|
||||
@@ -147,7 +141,7 @@ fn generate_runtime_api(
|
||||
|
||||
Ok((struct_input, method))
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
.collect::<Result<Vec<_>, CodegenError>>()?;
|
||||
|
||||
let trait_name = format_ident!("{}", trait_name_str);
|
||||
|
||||
|
||||
@@ -587,7 +587,7 @@ macro_rules! impl_tuples {
|
||||
// there is one, and add it to a map with that index as the key.
|
||||
let mut exts_by_index = HashMap::new();
|
||||
$({
|
||||
for (idx, e) in metadata.extrinsic().transaction_extensions().iter().enumerate() {
|
||||
for (idx, e) in metadata.extrinsic().transaction_extensions_to_use_for_encoding().enumerate() {
|
||||
// Skip over any exts that have a match already:
|
||||
if exts_by_index.contains_key(&idx) {
|
||||
continue
|
||||
@@ -604,7 +604,7 @@ macro_rules! impl_tuples {
|
||||
|
||||
// Next, turn these into an ordered vec, erroring if we haven't matched on any exts yet.
|
||||
let mut params = Vec::new();
|
||||
for (idx, e) in metadata.extrinsic().transaction_extensions().iter().enumerate() {
|
||||
for (idx, e) in metadata.extrinsic().transaction_extensions_to_use_for_encoding().enumerate() {
|
||||
let Some(ext) = exts_by_index.remove(&idx) else {
|
||||
if is_type_empty(e.extra_ty(), types) {
|
||||
continue
|
||||
|
||||
@@ -61,10 +61,11 @@ pub fn validate<P: Payload>(payload: &P, metadata: &Metadata) -> Result<(), Erro
|
||||
};
|
||||
|
||||
let api_trait = metadata.runtime_api_trait_by_name_err(payload.trait_name())?;
|
||||
|
||||
let Some(runtime_hash) = api_trait.method_hash(payload.method_name()) else {
|
||||
let Some(api_method) = api_trait.method_by_name(payload.method_name()) else {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
};
|
||||
|
||||
let runtime_hash = api_method.hash();
|
||||
if static_hash != runtime_hash {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
}
|
||||
|
||||
+2
-3
@@ -205,12 +205,11 @@ pub fn create_v5_general<T: Config, Call: Payload>(
|
||||
// with a hash allowing us to do so.
|
||||
validate(call, &client_state.metadata)?;
|
||||
|
||||
// 2. Work out which TX extension version to target based on metadata (unless we
|
||||
// explicitly ask for a specific transaction version at a later step).
|
||||
// 2. Work out which TX extension version to target based on metadata.
|
||||
let tx_extensions_version = client_state
|
||||
.metadata
|
||||
.extrinsic()
|
||||
.transaction_extensions_version();
|
||||
.transaction_extension_version_to_use_for_encoding();
|
||||
|
||||
// 3. SCALE encode call data to bytes (pallet u8, call u8, call params).
|
||||
let call_data = call_data(call, &client_state.metadata)?;
|
||||
|
||||
Generated
+14
-14
@@ -665,9 +665,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "frame-decode"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8a784e501ed2cec99eb00a1e78e42740fcb05f1aea7bbea90bf46f0a9f255bb"
|
||||
checksum = "50c554ce2394e2c04426a070b4cb133c72f6f14c86b665f4e13094addd8e8958"
|
||||
dependencies = [
|
||||
"frame-metadata",
|
||||
"parity-scale-codec",
|
||||
@@ -679,9 +679,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "frame-metadata"
|
||||
version = "18.0.0"
|
||||
version = "20.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daaf440c68eb2c3d88e5760fe8c7af3f9fee9181fab6c2f2c4e7cc48dcc40bb8"
|
||||
checksum = "26de808fa6461f2485dc51811aefed108850064994fb4a62b3ac21ffa62ac8df"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"parity-scale-codec",
|
||||
@@ -2110,9 +2110,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "scale-typegen"
|
||||
version = "0.10.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc3173be608895eb117cf397ab4f31f00e2ed2c7af1c6e0b8f5d51d0a0967053"
|
||||
checksum = "6aebea322734465f39e4ad8100e1f9708c6c6c325d92b8780015d30c44fae791"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2471,7 +2471,7 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
|
||||
[[package]]
|
||||
name = "subxt"
|
||||
version = "0.39.0"
|
||||
version = "0.41.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"derive-where",
|
||||
@@ -2505,7 +2505,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "subxt-codegen"
|
||||
version = "0.39.0"
|
||||
version = "0.41.0"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"heck",
|
||||
@@ -2521,7 +2521,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "subxt-core"
|
||||
version = "0.39.0"
|
||||
version = "0.41.0"
|
||||
dependencies = [
|
||||
"base58",
|
||||
"blake2",
|
||||
@@ -2549,7 +2549,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "subxt-lightclient"
|
||||
version = "0.39.0"
|
||||
version = "0.41.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"futures-timer",
|
||||
@@ -2574,7 +2574,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "subxt-macro"
|
||||
version = "0.39.0"
|
||||
version = "0.41.0"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"parity-scale-codec",
|
||||
@@ -2588,7 +2588,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "subxt-metadata"
|
||||
version = "0.39.0"
|
||||
version = "0.41.0"
|
||||
dependencies = [
|
||||
"frame-decode",
|
||||
"frame-metadata",
|
||||
@@ -2601,7 +2601,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "subxt-rpcs"
|
||||
version = "0.39.0"
|
||||
version = "0.41.0"
|
||||
dependencies = [
|
||||
"derive-where",
|
||||
"finito",
|
||||
@@ -2626,7 +2626,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "subxt-utils-fetchmetadata"
|
||||
version = "0.39.0"
|
||||
version = "0.41.0"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"parity-scale-codec",
|
||||
|
||||
@@ -140,8 +140,8 @@ pub async fn extension_signature_for_extrinsic(
|
||||
let signed_extensions: Vec<String> = api
|
||||
.metadata()
|
||||
.extrinsic()
|
||||
.transaction_extensions()
|
||||
.iter()
|
||||
.transaction_extensions_by_version(0)
|
||||
.unwrap()
|
||||
.map(|e| e.identifier().to_string())
|
||||
.collect();
|
||||
let tip = encode_then_hex(&Compact(0u128));
|
||||
|
||||
@@ -30,6 +30,7 @@ thiserror = { workspace = true, default-features = false }
|
||||
bitvec = { workspace = true, features = ["alloc"] }
|
||||
criterion = { workspace = true }
|
||||
scale-info = { workspace = true, features = ["bit-vec"] }
|
||||
subxt-utils-stripmetadata = { workspace = true }
|
||||
|
||||
[lib]
|
||||
# Without this, libtest cli opts interfere with criterion benches:
|
||||
|
||||
@@ -6,6 +6,7 @@ use alloc::string::String;
|
||||
use thiserror::Error as DeriveError;
|
||||
mod v14;
|
||||
mod v15;
|
||||
mod v16;
|
||||
|
||||
/// An error emitted if something goes wrong converting [`frame_metadata`]
|
||||
/// types into [`crate::Metadata`].
|
||||
@@ -29,13 +30,6 @@ pub enum TryFromError {
|
||||
InvalidTypePath(String),
|
||||
}
|
||||
|
||||
impl From<crate::Metadata> for frame_metadata::RuntimeMetadataPrefixed {
|
||||
fn from(value: crate::Metadata) -> Self {
|
||||
let m: frame_metadata::v15::RuntimeMetadataV15 = value.into();
|
||||
m.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<frame_metadata::RuntimeMetadataPrefixed> for crate::Metadata {
|
||||
type Error = TryFromError;
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::TryFromError;
|
||||
|
||||
use crate::utils::variant_index::VariantIndex;
|
||||
use crate::{
|
||||
utils::ordered_map::OrderedMap, ArcStr, ConstantMetadata, CustomMetadataInner,
|
||||
ExtrinsicMetadata, Metadata, OuterEnumsMetadata, PalletMetadataInner, StorageEntryMetadata,
|
||||
StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
|
||||
TransactionExtensionMetadataInner,
|
||||
};
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use alloc::{format, vec};
|
||||
use frame_metadata::v14;
|
||||
use hashbrown::HashMap;
|
||||
use scale_info::form::PortableForm;
|
||||
|
||||
impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
|
||||
type Error = TryFromError;
|
||||
fn try_from(mut m: v14::RuntimeMetadataV14) -> Result<Self, TryFromError> {
|
||||
let outer_enums = generate_outer_enums(&mut m)?;
|
||||
let missing_extrinsic_type_ids = MissingExtrinsicTypeIds::generate_from(&m)?;
|
||||
|
||||
let mut pallets = OrderedMap::new();
|
||||
let mut pallets_by_index = HashMap::new();
|
||||
for (pos, p) in m.pallets.into_iter().enumerate() {
|
||||
let name: ArcStr = p.name.into();
|
||||
|
||||
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 call_variant_index =
|
||||
VariantIndex::build(p.calls.as_ref().map(|c| c.ty.id), &m.types);
|
||||
let error_variant_index =
|
||||
VariantIndex::build(p.error.as_ref().map(|e| e.ty.id), &m.types);
|
||||
let event_variant_index =
|
||||
VariantIndex::build(p.event.as_ref().map(|e| e.ty.id), &m.types);
|
||||
|
||||
pallets_by_index.insert(p.index, pos);
|
||||
pallets.push_insert(
|
||||
name.clone(),
|
||||
PalletMetadataInner {
|
||||
name,
|
||||
index: p.index,
|
||||
storage,
|
||||
call_ty: p.calls.map(|c| c.ty.id),
|
||||
call_variant_index,
|
||||
event_ty: p.event.map(|e| e.ty.id),
|
||||
event_variant_index,
|
||||
error_ty: p.error.map(|e| e.ty.id),
|
||||
error_variant_index,
|
||||
constants: constants.collect(),
|
||||
view_functions: vec![],
|
||||
associated_types: Default::default(),
|
||||
docs: vec![],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let dispatch_error_ty = m
|
||||
.types
|
||||
.types
|
||||
.iter()
|
||||
.find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"])
|
||||
.map(|ty| ty.id);
|
||||
|
||||
Ok(Metadata {
|
||||
types: m.types,
|
||||
pallets,
|
||||
pallets_by_index,
|
||||
extrinsic: from_extrinsic_metadata(m.extrinsic, missing_extrinsic_type_ids),
|
||||
dispatch_error_ty,
|
||||
outer_enums: OuterEnumsMetadata {
|
||||
call_enum_ty: outer_enums.call_enum_ty.id,
|
||||
event_enum_ty: outer_enums.event_enum_ty.id,
|
||||
error_enum_ty: outer_enums.error_enum_ty.id,
|
||||
},
|
||||
apis: Default::default(),
|
||||
custom: CustomMetadataInner {
|
||||
map: Default::default(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn from_signed_extension_metadata(
|
||||
value: v14::SignedExtensionMetadata<PortableForm>,
|
||||
) -> TransactionExtensionMetadataInner {
|
||||
TransactionExtensionMetadataInner {
|
||||
identifier: value.identifier,
|
||||
extra_ty: value.ty.id,
|
||||
additional_ty: value.additional_signed.id,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_extrinsic_metadata(
|
||||
value: v14::ExtrinsicMetadata<PortableForm>,
|
||||
missing_ids: MissingExtrinsicTypeIds,
|
||||
) -> ExtrinsicMetadata {
|
||||
let transaction_extensions: Vec<_> = value
|
||||
.signed_extensions
|
||||
.into_iter()
|
||||
.map(from_signed_extension_metadata)
|
||||
.collect();
|
||||
|
||||
let transaction_extension_indexes = (0..transaction_extensions.len() as u32).collect();
|
||||
|
||||
ExtrinsicMetadata {
|
||||
supported_versions: vec![value.version],
|
||||
transaction_extensions,
|
||||
address_ty: missing_ids.address,
|
||||
signature_ty: missing_ids.signature,
|
||||
transaction_extensions_by_version: BTreeMap::from_iter([(
|
||||
0,
|
||||
transaction_extension_indexes,
|
||||
)]),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_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 {
|
||||
ConstantMetadata {
|
||||
name,
|
||||
ty: s.ty.id,
|
||||
value: s.value,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_outer_enums(
|
||||
metadata: &mut v14::RuntimeMetadataV14,
|
||||
) -> Result<frame_metadata::v15::OuterEnums<scale_info::form::PortableForm>, TryFromError> {
|
||||
let outer_enums = OuterEnums::find_in(&metadata.types);
|
||||
|
||||
let Some(call_enum_id) = outer_enums.call_ty else {
|
||||
return Err(TryFromError::TypeNameNotFound("RuntimeCall".into()));
|
||||
};
|
||||
let Some(event_type_id) = outer_enums.event_ty else {
|
||||
return Err(TryFromError::TypeNameNotFound("RuntimeEvent".into()));
|
||||
};
|
||||
let error_type_id = if let Some(id) = outer_enums.error_ty {
|
||||
id
|
||||
} else {
|
||||
let call_enum = &metadata.types.types[call_enum_id as usize];
|
||||
let mut error_path = call_enum.ty.path.segments.clone();
|
||||
|
||||
let Some(last) = error_path.last_mut() else {
|
||||
return Err(TryFromError::InvalidTypePath("RuntimeCall".into()));
|
||||
};
|
||||
"RuntimeError".clone_into(last);
|
||||
generate_outer_error_enum_type(metadata, error_path)
|
||||
};
|
||||
|
||||
Ok(frame_metadata::v15::OuterEnums {
|
||||
call_enum_ty: call_enum_id.into(),
|
||||
event_enum_ty: event_type_id.into(),
|
||||
error_enum_ty: error_type_id.into(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates an outer `RuntimeError` enum type and adds it to the metadata.
|
||||
///
|
||||
/// Returns the id of the generated type from the registry.
|
||||
fn generate_outer_error_enum_type(
|
||||
metadata: &mut v14::RuntimeMetadataV14,
|
||||
path_segments: Vec<String>,
|
||||
) -> u32 {
|
||||
let variants: Vec<_> = metadata
|
||||
.pallets
|
||||
.iter()
|
||||
.filter_map(|pallet| {
|
||||
let error = pallet.error.as_ref()?;
|
||||
let path = format!("{}Error", pallet.name);
|
||||
let ty = error.ty.id.into();
|
||||
|
||||
Some(scale_info::Variant {
|
||||
name: pallet.name.clone(),
|
||||
fields: vec![scale_info::Field {
|
||||
name: None,
|
||||
ty,
|
||||
type_name: Some(path),
|
||||
docs: vec![],
|
||||
}],
|
||||
index: pallet.index,
|
||||
docs: vec![],
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let enum_type = scale_info::Type {
|
||||
path: scale_info::Path {
|
||||
segments: path_segments,
|
||||
},
|
||||
type_params: vec![],
|
||||
type_def: scale_info::TypeDef::Variant(scale_info::TypeDefVariant { variants }),
|
||||
docs: vec![],
|
||||
};
|
||||
|
||||
let enum_type_id = metadata.types.types.len() as u32;
|
||||
|
||||
metadata.types.types.push(scale_info::PortableType {
|
||||
id: enum_type_id,
|
||||
ty: enum_type,
|
||||
});
|
||||
|
||||
enum_type_id
|
||||
}
|
||||
|
||||
/// The type IDs extracted from the metadata that represent the
|
||||
/// generic type parameters passed to the `UncheckedExtrinsic` from
|
||||
/// the substrate-based chain.
|
||||
#[derive(Clone, Copy)]
|
||||
struct MissingExtrinsicTypeIds {
|
||||
address: u32,
|
||||
signature: u32,
|
||||
}
|
||||
|
||||
impl MissingExtrinsicTypeIds {
|
||||
fn generate_from(
|
||||
metadata: &v14::RuntimeMetadataV14,
|
||||
) -> Result<MissingExtrinsicTypeIds, TryFromError> {
|
||||
const ADDRESS: &str = "Address";
|
||||
const SIGNATURE: &str = "Signature";
|
||||
|
||||
let extrinsic_id = metadata.extrinsic.ty.id;
|
||||
let Some(extrinsic_ty) = metadata.types.resolve(extrinsic_id) else {
|
||||
return Err(TryFromError::TypeNotFound(extrinsic_id));
|
||||
};
|
||||
|
||||
let find_param = |name: &'static str| -> Option<u32> {
|
||||
extrinsic_ty
|
||||
.type_params
|
||||
.iter()
|
||||
.find(|param| param.name.as_str() == name)
|
||||
.and_then(|param| param.ty.as_ref())
|
||||
.map(|ty| ty.id)
|
||||
};
|
||||
|
||||
let Some(address) = find_param(ADDRESS) else {
|
||||
return Err(TryFromError::TypeNameNotFound(ADDRESS.into()));
|
||||
};
|
||||
let Some(signature) = find_param(SIGNATURE) else {
|
||||
return Err(TryFromError::TypeNameNotFound(SIGNATURE.into()));
|
||||
};
|
||||
|
||||
Ok(MissingExtrinsicTypeIds { address, signature })
|
||||
}
|
||||
}
|
||||
|
||||
/// Outer enum IDs, which are required in Subxt but are not present in V14 metadata.
|
||||
pub struct OuterEnums {
|
||||
/// The RuntimeCall type ID.
|
||||
pub call_ty: Option<u32>,
|
||||
/// The RuntimeEvent type ID.
|
||||
pub event_ty: Option<u32>,
|
||||
/// The RuntimeError type ID.
|
||||
pub error_ty: Option<u32>,
|
||||
}
|
||||
|
||||
impl OuterEnums {
|
||||
pub fn find_in(types: &scale_info::PortableRegistry) -> OuterEnums {
|
||||
let find_type = |name: &str| {
|
||||
types.types.iter().find_map(|ty| {
|
||||
let ident = ty.ty.path.ident()?;
|
||||
|
||||
if ident != name {
|
||||
return None;
|
||||
}
|
||||
|
||||
let scale_info::TypeDef::Variant(_) = &ty.ty.type_def else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(ty.id)
|
||||
})
|
||||
};
|
||||
|
||||
OuterEnums {
|
||||
call_ty: find_type("RuntimeCall"),
|
||||
event_ty: find_type("RuntimeEvent"),
|
||||
error_ty: find_type("RuntimeError"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::TryFromError;
|
||||
|
||||
use crate::utils::variant_index::VariantIndex;
|
||||
use crate::{
|
||||
utils::ordered_map::OrderedMap, ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata,
|
||||
MethodParamMetadata, OuterEnumsMetadata, PalletMetadataInner, RuntimeApiMetadataInner,
|
||||
RuntimeApiMethodMetadataInner, StorageEntryMetadata, StorageEntryModifier, StorageEntryType,
|
||||
StorageHasher, StorageMetadata, TransactionExtensionMetadataInner,
|
||||
};
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use frame_metadata::v15;
|
||||
use hashbrown::HashMap;
|
||||
use scale_info::form::PortableForm;
|
||||
|
||||
impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
|
||||
type Error = TryFromError;
|
||||
fn try_from(m: v15::RuntimeMetadataV15) -> Result<Self, TryFromError> {
|
||||
let mut pallets = OrderedMap::new();
|
||||
let mut pallets_by_index = HashMap::new();
|
||||
for (pos, p) in m.pallets.into_iter().enumerate() {
|
||||
let name: ArcStr = p.name.into();
|
||||
|
||||
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 call_variant_index =
|
||||
VariantIndex::build(p.calls.as_ref().map(|c| c.ty.id), &m.types);
|
||||
let error_variant_index =
|
||||
VariantIndex::build(p.error.as_ref().map(|e| e.ty.id), &m.types);
|
||||
let event_variant_index =
|
||||
VariantIndex::build(p.event.as_ref().map(|e| e.ty.id), &m.types);
|
||||
|
||||
pallets_by_index.insert(p.index, pos);
|
||||
pallets.push_insert(
|
||||
name.clone(),
|
||||
PalletMetadataInner {
|
||||
name,
|
||||
index: p.index,
|
||||
storage,
|
||||
call_ty: p.calls.map(|c| c.ty.id),
|
||||
call_variant_index,
|
||||
event_ty: p.event.map(|e| e.ty.id),
|
||||
event_variant_index,
|
||||
error_ty: p.error.map(|e| e.ty.id),
|
||||
error_variant_index,
|
||||
constants: constants.collect(),
|
||||
view_functions: vec![],
|
||||
associated_types: Default::default(),
|
||||
docs: p.docs,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let apis = m.apis.into_iter().map(|api| {
|
||||
let name: ArcStr = api.name.clone().into();
|
||||
(name.clone(), from_runtime_api_metadata(name, api))
|
||||
});
|
||||
|
||||
let dispatch_error_ty = m
|
||||
.types
|
||||
.types
|
||||
.iter()
|
||||
.find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"])
|
||||
.map(|ty| ty.id);
|
||||
|
||||
Ok(Metadata {
|
||||
types: m.types,
|
||||
pallets,
|
||||
pallets_by_index,
|
||||
extrinsic: from_extrinsic_metadata(m.extrinsic),
|
||||
dispatch_error_ty,
|
||||
apis: apis.collect(),
|
||||
outer_enums: OuterEnumsMetadata {
|
||||
call_enum_ty: m.outer_enums.call_enum_ty.id,
|
||||
event_enum_ty: m.outer_enums.event_enum_ty.id,
|
||||
error_enum_ty: m.outer_enums.error_enum_ty.id,
|
||||
},
|
||||
custom: m.custom,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn from_signed_extension_metadata(
|
||||
value: v15::SignedExtensionMetadata<PortableForm>,
|
||||
) -> TransactionExtensionMetadataInner {
|
||||
TransactionExtensionMetadataInner {
|
||||
identifier: value.identifier,
|
||||
extra_ty: value.ty.id,
|
||||
additional_ty: value.additional_signed.id,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_extrinsic_metadata(value: v15::ExtrinsicMetadata<PortableForm>) -> ExtrinsicMetadata {
|
||||
let transaction_extensions: Vec<_> = value
|
||||
.signed_extensions
|
||||
.into_iter()
|
||||
.map(from_signed_extension_metadata)
|
||||
.collect();
|
||||
|
||||
let transaction_extension_indexes = (0..transaction_extensions.len() as u32).collect();
|
||||
|
||||
ExtrinsicMetadata {
|
||||
supported_versions: vec![value.version],
|
||||
transaction_extensions,
|
||||
address_ty: value.address_ty.id,
|
||||
signature_ty: value.signature_ty.id,
|
||||
transaction_extensions_by_version: BTreeMap::from_iter([(
|
||||
0,
|
||||
transaction_extension_indexes,
|
||||
)]),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_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 {
|
||||
ConstantMetadata {
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::TryFromError;
|
||||
|
||||
use crate::utils::variant_index::VariantIndex;
|
||||
use crate::{
|
||||
utils::ordered_map::OrderedMap, ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata,
|
||||
MethodParamMetadata, OuterEnumsMetadata, PalletMetadataInner, PalletViewFunctionMetadataInner,
|
||||
RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner, StorageEntryMetadata,
|
||||
StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
|
||||
TransactionExtensionMetadataInner,
|
||||
};
|
||||
use frame_metadata::{v15, v16};
|
||||
use hashbrown::HashMap;
|
||||
use scale_info::form::PortableForm;
|
||||
|
||||
impl TryFrom<v16::RuntimeMetadataV16> for Metadata {
|
||||
type Error = TryFromError;
|
||||
fn try_from(m: v16::RuntimeMetadataV16) -> Result<Self, TryFromError> {
|
||||
let types = m.types;
|
||||
|
||||
let mut pallets = OrderedMap::new();
|
||||
let mut pallets_by_index = HashMap::new();
|
||||
for (pos, p) in m.pallets.into_iter().enumerate() {
|
||||
let name: ArcStr = p.name.into();
|
||||
|
||||
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(from_view_function_metadata);
|
||||
|
||||
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))
|
||||
.collect();
|
||||
|
||||
pallets_by_index.insert(p.index, pos);
|
||||
pallets.push_insert(
|
||||
name.clone(),
|
||||
PalletMetadataInner {
|
||||
name,
|
||||
index: p.index,
|
||||
storage,
|
||||
call_ty: p.calls.map(|c| c.ty.id),
|
||||
call_variant_index,
|
||||
event_ty: p.event.map(|e| e.ty.id),
|
||||
event_variant_index,
|
||||
error_ty: p.error.map(|e| e.ty.id),
|
||||
error_variant_index,
|
||||
constants: constants.collect(),
|
||||
view_functions: view_functions.collect(),
|
||||
associated_types,
|
||||
docs: p.docs,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let apis = m.apis.into_iter().map(|api| {
|
||||
let name: ArcStr = api.name.clone().into();
|
||||
(name.clone(), from_runtime_api_metadata(name, api))
|
||||
});
|
||||
|
||||
let custom_map = m
|
||||
.custom
|
||||
.map
|
||||
.into_iter()
|
||||
.map(|(key, val)| {
|
||||
let custom_val = v15::CustomValueMetadata {
|
||||
ty: val.ty,
|
||||
value: val.value,
|
||||
};
|
||||
(key, custom_val)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let dispatch_error_ty = types
|
||||
.types
|
||||
.iter()
|
||||
.find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"])
|
||||
.map(|ty| ty.id);
|
||||
|
||||
Ok(Metadata {
|
||||
types,
|
||||
pallets,
|
||||
pallets_by_index,
|
||||
extrinsic: from_extrinsic_metadata(m.extrinsic),
|
||||
dispatch_error_ty,
|
||||
apis: apis.collect(),
|
||||
outer_enums: OuterEnumsMetadata {
|
||||
call_enum_ty: m.outer_enums.call_enum_ty.id,
|
||||
event_enum_ty: m.outer_enums.event_enum_ty.id,
|
||||
error_enum_ty: m.outer_enums.error_enum_ty.id,
|
||||
},
|
||||
custom: v15::CustomMetadata { map: custom_map },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn from_transaction_extension_metadata(
|
||||
value: v16::TransactionExtensionMetadata<PortableForm>,
|
||||
) -> TransactionExtensionMetadataInner {
|
||||
TransactionExtensionMetadataInner {
|
||||
identifier: value.identifier,
|
||||
extra_ty: value.ty.id,
|
||||
additional_ty: value.implicit.id,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_extrinsic_metadata(value: v16::ExtrinsicMetadata<PortableForm>) -> ExtrinsicMetadata {
|
||||
ExtrinsicMetadata {
|
||||
supported_versions: value.versions,
|
||||
transaction_extensions_by_version: value.transaction_extensions_by_version,
|
||||
transaction_extensions: value
|
||||
.transaction_extensions
|
||||
.into_iter()
|
||||
.map(from_transaction_extension_metadata)
|
||||
.collect(),
|
||||
address_ty: value.address_ty.id,
|
||||
signature_ty: value.signature_ty.id,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_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 {
|
||||
ConstantMetadata {
|
||||
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(
|
||||
s: v16::PalletViewFunctionMetadata<PortableForm>,
|
||||
) -> PalletViewFunctionMetadataInner {
|
||||
PalletViewFunctionMetadataInner {
|
||||
name: s.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,
|
||||
}
|
||||
}
|
||||
@@ -1,722 +0,0 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::TryFromError;
|
||||
use crate::Metadata;
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::string::String;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use core::fmt::Write;
|
||||
use frame_metadata::{v14, v15};
|
||||
use scale_info::TypeDef;
|
||||
|
||||
impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
|
||||
type Error = TryFromError;
|
||||
fn try_from(value: v14::RuntimeMetadataV14) -> Result<Self, Self::Error> {
|
||||
// Convert to v15 and then convert that into Metadata.
|
||||
v14_to_v15(value)?.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Metadata> for v14::RuntimeMetadataV14 {
|
||||
fn from(val: Metadata) -> Self {
|
||||
let v15 = val.into();
|
||||
v15_to_v14(v15)
|
||||
}
|
||||
}
|
||||
|
||||
fn v15_to_v14(mut metadata: v15::RuntimeMetadataV15) -> v14::RuntimeMetadataV14 {
|
||||
let types = &mut metadata.types;
|
||||
|
||||
// In subxt we care about the `Address`, `Call`, `Signature` and `Extra` types.
|
||||
let extrinsic_type = scale_info::Type {
|
||||
path: scale_info::Path {
|
||||
segments: vec![
|
||||
"primitives".to_owned(),
|
||||
"runtime".to_owned(),
|
||||
"generic".to_owned(),
|
||||
"UncheckedExtrinsic".to_owned(),
|
||||
],
|
||||
},
|
||||
type_params: vec![
|
||||
scale_info::TypeParameter::<scale_info::form::PortableForm> {
|
||||
name: "Address".to_owned(),
|
||||
ty: Some(metadata.extrinsic.address_ty),
|
||||
},
|
||||
scale_info::TypeParameter::<scale_info::form::PortableForm> {
|
||||
name: "Call".to_owned(),
|
||||
ty: Some(metadata.extrinsic.call_ty),
|
||||
},
|
||||
scale_info::TypeParameter::<scale_info::form::PortableForm> {
|
||||
name: "Signature".to_owned(),
|
||||
ty: Some(metadata.extrinsic.signature_ty),
|
||||
},
|
||||
scale_info::TypeParameter::<scale_info::form::PortableForm> {
|
||||
name: "Extra".to_owned(),
|
||||
ty: Some(metadata.extrinsic.extra_ty),
|
||||
},
|
||||
],
|
||||
type_def: scale_info::TypeDef::Composite(scale_info::TypeDefComposite { fields: vec![] }),
|
||||
docs: vec![],
|
||||
};
|
||||
let extrinsic_type_id = types.types.len() as u32;
|
||||
|
||||
types.types.push(scale_info::PortableType {
|
||||
id: extrinsic_type_id,
|
||||
ty: extrinsic_type,
|
||||
});
|
||||
|
||||
v14::RuntimeMetadataV14 {
|
||||
types: metadata.types,
|
||||
pallets: metadata
|
||||
.pallets
|
||||
.into_iter()
|
||||
.map(|pallet| frame_metadata::v14::PalletMetadata {
|
||||
name: pallet.name,
|
||||
storage: pallet
|
||||
.storage
|
||||
.map(|storage| frame_metadata::v14::PalletStorageMetadata {
|
||||
prefix: storage.prefix,
|
||||
entries: storage
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|entry| {
|
||||
let modifier = match entry.modifier {
|
||||
frame_metadata::v15::StorageEntryModifier::Optional => {
|
||||
frame_metadata::v14::StorageEntryModifier::Optional
|
||||
}
|
||||
frame_metadata::v15::StorageEntryModifier::Default => {
|
||||
frame_metadata::v14::StorageEntryModifier::Default
|
||||
}
|
||||
};
|
||||
|
||||
let ty = match entry.ty {
|
||||
frame_metadata::v15::StorageEntryType::Plain(ty) => {
|
||||
frame_metadata::v14::StorageEntryType::Plain(ty)
|
||||
},
|
||||
frame_metadata::v15::StorageEntryType::Map {
|
||||
hashers,
|
||||
key,
|
||||
value,
|
||||
} => frame_metadata::v14::StorageEntryType::Map {
|
||||
hashers: hashers.into_iter().map(|hasher| match hasher {
|
||||
frame_metadata::v15::StorageHasher::Blake2_128 => frame_metadata::v14::StorageHasher::Blake2_128,
|
||||
frame_metadata::v15::StorageHasher::Blake2_256 => frame_metadata::v14::StorageHasher::Blake2_256,
|
||||
frame_metadata::v15::StorageHasher::Blake2_128Concat => frame_metadata::v14::StorageHasher::Blake2_128Concat ,
|
||||
frame_metadata::v15::StorageHasher::Twox128 => frame_metadata::v14::StorageHasher::Twox128,
|
||||
frame_metadata::v15::StorageHasher::Twox256 => frame_metadata::v14::StorageHasher::Twox256,
|
||||
frame_metadata::v15::StorageHasher::Twox64Concat => frame_metadata::v14::StorageHasher::Twox64Concat,
|
||||
frame_metadata::v15::StorageHasher::Identity=> frame_metadata::v14::StorageHasher::Identity,
|
||||
}).collect(),
|
||||
key,
|
||||
value,
|
||||
},
|
||||
};
|
||||
|
||||
frame_metadata::v14::StorageEntryMetadata {
|
||||
name: entry.name,
|
||||
modifier,
|
||||
ty,
|
||||
default: entry.default,
|
||||
docs: entry.docs,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
calls: pallet.calls.map(|calls| frame_metadata::v14::PalletCallMetadata { ty: calls.ty } ),
|
||||
event: pallet.event.map(|event| frame_metadata::v14::PalletEventMetadata { ty: event.ty } ),
|
||||
constants: pallet.constants.into_iter().map(|constant| frame_metadata::v14::PalletConstantMetadata {
|
||||
name: constant.name,
|
||||
ty: constant.ty,
|
||||
value: constant.value,
|
||||
docs: constant.docs,
|
||||
} ).collect(),
|
||||
error: pallet.error.map(|error| frame_metadata::v14::PalletErrorMetadata { ty: error.ty } ),
|
||||
index: pallet.index,
|
||||
})
|
||||
.collect(),
|
||||
extrinsic: frame_metadata::v14::ExtrinsicMetadata {
|
||||
ty: extrinsic_type_id.into(),
|
||||
version: metadata.extrinsic.version,
|
||||
signed_extensions: metadata.extrinsic.signed_extensions.into_iter().map(|ext| {
|
||||
frame_metadata::v14::SignedExtensionMetadata {
|
||||
identifier: ext.identifier,
|
||||
ty: ext.ty,
|
||||
additional_signed: ext.additional_signed,
|
||||
}
|
||||
}).collect()
|
||||
},
|
||||
ty: metadata.ty,
|
||||
}
|
||||
}
|
||||
|
||||
fn v14_to_v15(
|
||||
mut metadata: v14::RuntimeMetadataV14,
|
||||
) -> Result<v15::RuntimeMetadataV15, TryFromError> {
|
||||
// Find the extrinsic types.
|
||||
let extrinsic_parts = ExtrinsicPartTypeIds::new(&metadata)?;
|
||||
|
||||
let outer_enums = generate_outer_enums(&mut metadata)?;
|
||||
|
||||
Ok(v15::RuntimeMetadataV15 {
|
||||
types: metadata.types,
|
||||
pallets: metadata
|
||||
.pallets
|
||||
.into_iter()
|
||||
.map(|pallet| frame_metadata::v15::PalletMetadata {
|
||||
name: pallet.name,
|
||||
storage: pallet
|
||||
.storage
|
||||
.map(|storage| frame_metadata::v15::PalletStorageMetadata {
|
||||
prefix: storage.prefix,
|
||||
entries: storage
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|entry| {
|
||||
let modifier = match entry.modifier {
|
||||
frame_metadata::v14::StorageEntryModifier::Optional => {
|
||||
frame_metadata::v15::StorageEntryModifier::Optional
|
||||
}
|
||||
frame_metadata::v14::StorageEntryModifier::Default => {
|
||||
frame_metadata::v15::StorageEntryModifier::Default
|
||||
}
|
||||
};
|
||||
|
||||
let ty = match entry.ty {
|
||||
frame_metadata::v14::StorageEntryType::Plain(ty) => {
|
||||
frame_metadata::v15::StorageEntryType::Plain(ty)
|
||||
},
|
||||
frame_metadata::v14::StorageEntryType::Map {
|
||||
hashers,
|
||||
key,
|
||||
value,
|
||||
} => frame_metadata::v15::StorageEntryType::Map {
|
||||
hashers: hashers.into_iter().map(|hasher| match hasher {
|
||||
frame_metadata::v14::StorageHasher::Blake2_128 => frame_metadata::v15::StorageHasher::Blake2_128,
|
||||
frame_metadata::v14::StorageHasher::Blake2_256 => frame_metadata::v15::StorageHasher::Blake2_256,
|
||||
frame_metadata::v14::StorageHasher::Blake2_128Concat => frame_metadata::v15::StorageHasher::Blake2_128Concat ,
|
||||
frame_metadata::v14::StorageHasher::Twox128 => frame_metadata::v15::StorageHasher::Twox128,
|
||||
frame_metadata::v14::StorageHasher::Twox256 => frame_metadata::v15::StorageHasher::Twox256,
|
||||
frame_metadata::v14::StorageHasher::Twox64Concat => frame_metadata::v15::StorageHasher::Twox64Concat,
|
||||
frame_metadata::v14::StorageHasher::Identity=> frame_metadata::v15::StorageHasher::Identity,
|
||||
}).collect(),
|
||||
key,
|
||||
value,
|
||||
},
|
||||
};
|
||||
|
||||
frame_metadata::v15::StorageEntryMetadata {
|
||||
name: entry.name,
|
||||
modifier,
|
||||
ty,
|
||||
default: entry.default,
|
||||
docs: entry.docs,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
calls: pallet.calls.map(|calls| frame_metadata::v15::PalletCallMetadata { ty: calls.ty } ),
|
||||
event: pallet.event.map(|event| frame_metadata::v15::PalletEventMetadata { ty: event.ty } ),
|
||||
constants: pallet.constants.into_iter().map(|constant| frame_metadata::v15::PalletConstantMetadata {
|
||||
name: constant.name,
|
||||
ty: constant.ty,
|
||||
value: constant.value,
|
||||
docs: constant.docs,
|
||||
} ).collect(),
|
||||
error: pallet.error.map(|error| frame_metadata::v15::PalletErrorMetadata { ty: error.ty } ),
|
||||
index: pallet.index,
|
||||
docs: Default::default(),
|
||||
})
|
||||
.collect(),
|
||||
extrinsic: frame_metadata::v15::ExtrinsicMetadata {
|
||||
version: metadata.extrinsic.version,
|
||||
signed_extensions: metadata.extrinsic.signed_extensions.into_iter().map(|ext| {
|
||||
frame_metadata::v15::SignedExtensionMetadata {
|
||||
identifier: ext.identifier,
|
||||
ty: ext.ty,
|
||||
additional_signed: ext.additional_signed,
|
||||
}
|
||||
}).collect(),
|
||||
address_ty: extrinsic_parts.address.into(),
|
||||
call_ty: extrinsic_parts.call.into(),
|
||||
signature_ty: extrinsic_parts.signature.into(),
|
||||
extra_ty: extrinsic_parts.extra.into(),
|
||||
},
|
||||
ty: metadata.ty,
|
||||
apis: Default::default(),
|
||||
outer_enums,
|
||||
custom: v15::CustomMetadata {
|
||||
map: Default::default(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// The type IDs extracted from the metadata that represent the
|
||||
/// generic type parameters passed to the `UncheckedExtrinsic` from
|
||||
/// the substrate-based chain.
|
||||
struct ExtrinsicPartTypeIds {
|
||||
address: u32,
|
||||
call: u32,
|
||||
signature: u32,
|
||||
extra: u32,
|
||||
}
|
||||
|
||||
impl ExtrinsicPartTypeIds {
|
||||
/// Extract the generic type parameters IDs from the extrinsic type.
|
||||
fn new(metadata: &v14::RuntimeMetadataV14) -> Result<Self, TryFromError> {
|
||||
const ADDRESS: &str = "Address";
|
||||
const CALL: &str = "Call";
|
||||
const SIGNATURE: &str = "Signature";
|
||||
const EXTRA: &str = "Extra";
|
||||
|
||||
let extrinsic_id = metadata.extrinsic.ty.id;
|
||||
let Some(extrinsic_ty) = metadata.types.resolve(extrinsic_id) else {
|
||||
return Err(TryFromError::TypeNotFound(extrinsic_id));
|
||||
};
|
||||
|
||||
let find_param = |name: &'static str| -> Option<u32> {
|
||||
extrinsic_ty
|
||||
.type_params
|
||||
.iter()
|
||||
.find(|param| param.name.as_str() == name)
|
||||
.and_then(|param| param.ty.as_ref())
|
||||
.map(|ty| ty.id)
|
||||
};
|
||||
|
||||
let Some(address) = find_param(ADDRESS) else {
|
||||
return Err(TryFromError::TypeNameNotFound(ADDRESS.into()));
|
||||
};
|
||||
let Some(call) = find_param(CALL) else {
|
||||
return Err(TryFromError::TypeNameNotFound(CALL.into()));
|
||||
};
|
||||
let Some(signature) = find_param(SIGNATURE) else {
|
||||
return Err(TryFromError::TypeNameNotFound(SIGNATURE.into()));
|
||||
};
|
||||
let Some(extra) = find_param(EXTRA) else {
|
||||
return Err(TryFromError::TypeNameNotFound(EXTRA.into()));
|
||||
};
|
||||
|
||||
Ok(ExtrinsicPartTypeIds {
|
||||
address,
|
||||
call,
|
||||
signature,
|
||||
extra,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_outer_enums(
|
||||
metadata: &mut v14::RuntimeMetadataV14,
|
||||
) -> Result<v15::OuterEnums<scale_info::form::PortableForm>, TryFromError> {
|
||||
let find_type = |name: &str| {
|
||||
metadata.types.types.iter().find_map(|ty| {
|
||||
let ident = ty.ty.path.ident()?;
|
||||
|
||||
if ident != name {
|
||||
return None;
|
||||
}
|
||||
|
||||
let TypeDef::Variant(_) = &ty.ty.type_def else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some((ty.id, ty.ty.path.segments.clone()))
|
||||
})
|
||||
};
|
||||
|
||||
let Some((call_enum, mut call_path)) = find_type("RuntimeCall") else {
|
||||
return Err(TryFromError::TypeNameNotFound("RuntimeCall".into()));
|
||||
};
|
||||
|
||||
let Some((event_enum, _)) = find_type("RuntimeEvent") else {
|
||||
return Err(TryFromError::TypeNameNotFound("RuntimeEvent".into()));
|
||||
};
|
||||
|
||||
let error_enum = if let Some((error_enum, _)) = find_type("RuntimeError") {
|
||||
error_enum
|
||||
} else {
|
||||
let Some(last) = call_path.last_mut() else {
|
||||
return Err(TryFromError::InvalidTypePath("RuntimeCall".into()));
|
||||
};
|
||||
"RuntimeError".clone_into(last);
|
||||
generate_outer_error_enum_type(metadata, call_path)
|
||||
};
|
||||
|
||||
Ok(v15::OuterEnums {
|
||||
call_enum_ty: call_enum.into(),
|
||||
event_enum_ty: event_enum.into(),
|
||||
error_enum_ty: error_enum.into(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates an outer `RuntimeError` enum type and adds it to the metadata.
|
||||
///
|
||||
/// Returns the id of the generated type from the registry.
|
||||
fn generate_outer_error_enum_type(
|
||||
metadata: &mut v14::RuntimeMetadataV14,
|
||||
path_segments: Vec<String>,
|
||||
) -> u32 {
|
||||
let variants: Vec<_> = metadata
|
||||
.pallets
|
||||
.iter()
|
||||
.filter_map(|pallet| {
|
||||
let error = pallet.error.as_ref()?;
|
||||
|
||||
// Note: using the `alloc::format!` macro like in `let path = format!("{}Error", pallet.name);`
|
||||
// leads to linker errors about extern function `_Unwind_Resume` not being defined.
|
||||
let mut path = String::new();
|
||||
write!(path, "{}Error", pallet.name).expect("Cannot panic, qed;");
|
||||
let ty = error.ty.id.into();
|
||||
|
||||
Some(scale_info::Variant {
|
||||
name: pallet.name.clone(),
|
||||
fields: vec![scale_info::Field {
|
||||
name: None,
|
||||
ty,
|
||||
type_name: Some(path),
|
||||
docs: vec![],
|
||||
}],
|
||||
index: pallet.index,
|
||||
docs: vec![],
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let enum_type = scale_info::Type {
|
||||
path: scale_info::Path {
|
||||
segments: path_segments,
|
||||
},
|
||||
type_params: vec![],
|
||||
type_def: scale_info::TypeDef::Variant(scale_info::TypeDefVariant { variants }),
|
||||
docs: vec![],
|
||||
};
|
||||
|
||||
let enum_type_id = metadata.types.types.len() as u32;
|
||||
|
||||
metadata.types.types.push(scale_info::PortableType {
|
||||
id: enum_type_id,
|
||||
ty: enum_type,
|
||||
});
|
||||
|
||||
enum_type_id
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codec::Decode;
|
||||
use frame_metadata::{
|
||||
v14::ExtrinsicMetadata, v15::RuntimeMetadataV15, RuntimeMetadata, RuntimeMetadataPrefixed,
|
||||
};
|
||||
use scale_info::{meta_type, IntoPortable, TypeDef, TypeInfo};
|
||||
use std::{fs, marker::PhantomData, path::Path};
|
||||
|
||||
fn load_v15_metadata() -> RuntimeMetadataV15 {
|
||||
let bytes = fs::read(Path::new("../artifacts/polkadot_metadata_full.scale"))
|
||||
.expect("Cannot read metadata blob");
|
||||
let meta: RuntimeMetadataPrefixed =
|
||||
Decode::decode(&mut &*bytes).expect("Cannot decode scale metadata");
|
||||
|
||||
match meta.1 {
|
||||
RuntimeMetadata::V15(v15) => v15,
|
||||
_ => panic!("Unsupported metadata version {:?}", meta.1),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extrinsic_id_generation() {
|
||||
let v15 = load_v15_metadata();
|
||||
let v14 = v15_to_v14(v15.clone());
|
||||
|
||||
let ext_ty = v14.types.resolve(v14.extrinsic.ty.id).unwrap();
|
||||
let addr_id = ext_ty
|
||||
.type_params
|
||||
.iter()
|
||||
.find_map(|ty| {
|
||||
if ty.name == "Address" {
|
||||
Some(ty.ty.unwrap().id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
let call_id = ext_ty
|
||||
.type_params
|
||||
.iter()
|
||||
.find_map(|ty| {
|
||||
if ty.name == "Call" {
|
||||
Some(ty.ty.unwrap().id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
let extra_id = ext_ty
|
||||
.type_params
|
||||
.iter()
|
||||
.find_map(|ty| {
|
||||
if ty.name == "Extra" {
|
||||
Some(ty.ty.unwrap().id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
let signature_id = ext_ty
|
||||
.type_params
|
||||
.iter()
|
||||
.find_map(|ty| {
|
||||
if ty.name == "Signature" {
|
||||
Some(ty.ty.unwrap().id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Position in type registry shouldn't change.
|
||||
assert_eq!(v15.extrinsic.address_ty.id, addr_id);
|
||||
assert_eq!(v15.extrinsic.call_ty.id, call_id);
|
||||
assert_eq!(v15.extrinsic.extra_ty.id, extra_id);
|
||||
assert_eq!(v15.extrinsic.signature_ty.id, signature_id);
|
||||
|
||||
let v15_addr = v15.types.resolve(v15.extrinsic.address_ty.id).unwrap();
|
||||
let v14_addr = v14.types.resolve(addr_id).unwrap();
|
||||
assert_eq!(v15_addr, v14_addr);
|
||||
|
||||
let v15_call = v15.types.resolve(v15.extrinsic.call_ty.id).unwrap();
|
||||
let v14_call = v14.types.resolve(call_id).unwrap();
|
||||
assert_eq!(v15_call, v14_call);
|
||||
|
||||
let v15_extra = v15.types.resolve(v15.extrinsic.extra_ty.id).unwrap();
|
||||
let v14_extra = v14.types.resolve(extra_id).unwrap();
|
||||
assert_eq!(v15_extra, v14_extra);
|
||||
|
||||
let v15_sign = v15.types.resolve(v15.extrinsic.signature_ty.id).unwrap();
|
||||
let v14_sign = v14.types.resolve(signature_id).unwrap();
|
||||
assert_eq!(v15_sign, v14_sign);
|
||||
|
||||
// Ensure we don't lose the information when converting back to v15.
|
||||
let converted_v15 = v14_to_v15(v14).unwrap();
|
||||
|
||||
let v15_addr = v15.types.resolve(v15.extrinsic.address_ty.id).unwrap();
|
||||
let converted_v15_addr = converted_v15
|
||||
.types
|
||||
.resolve(converted_v15.extrinsic.address_ty.id)
|
||||
.unwrap();
|
||||
assert_eq!(v15_addr, converted_v15_addr);
|
||||
|
||||
let v15_call = v15.types.resolve(v15.extrinsic.call_ty.id).unwrap();
|
||||
let converted_v15_call = converted_v15
|
||||
.types
|
||||
.resolve(converted_v15.extrinsic.call_ty.id)
|
||||
.unwrap();
|
||||
assert_eq!(v15_call, converted_v15_call);
|
||||
|
||||
let v15_extra = v15.types.resolve(v15.extrinsic.extra_ty.id).unwrap();
|
||||
let converted_v15_extra = converted_v15
|
||||
.types
|
||||
.resolve(converted_v15.extrinsic.extra_ty.id)
|
||||
.unwrap();
|
||||
assert_eq!(v15_extra, converted_v15_extra);
|
||||
|
||||
let v15_sign = v15.types.resolve(v15.extrinsic.signature_ty.id).unwrap();
|
||||
let converted_v15_sign = converted_v15
|
||||
.types
|
||||
.resolve(converted_v15.extrinsic.signature_ty.id)
|
||||
.unwrap();
|
||||
assert_eq!(v15_sign, converted_v15_sign);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_outer_enums_generation() {
|
||||
let v15 = load_v15_metadata();
|
||||
let v14 = v15_to_v14(v15.clone());
|
||||
|
||||
// Convert back to v15 and expect to have the enum types properly generated.
|
||||
let converted_v15 = v14_to_v15(v14).unwrap();
|
||||
|
||||
// RuntimeCall and RuntimeEvent were already present in the metadata v14.
|
||||
let v15_call = v15.types.resolve(v15.outer_enums.call_enum_ty.id).unwrap();
|
||||
let converted_v15_call = converted_v15
|
||||
.types
|
||||
.resolve(converted_v15.outer_enums.call_enum_ty.id)
|
||||
.unwrap();
|
||||
assert_eq!(v15_call, converted_v15_call);
|
||||
|
||||
let v15_event = v15.types.resolve(v15.outer_enums.event_enum_ty.id).unwrap();
|
||||
let converted_v15_event = converted_v15
|
||||
.types
|
||||
.resolve(converted_v15.outer_enums.event_enum_ty.id)
|
||||
.unwrap();
|
||||
assert_eq!(v15_event, converted_v15_event);
|
||||
|
||||
let v15_error = v15.types.resolve(v15.outer_enums.error_enum_ty.id).unwrap();
|
||||
let converted_v15_error = converted_v15
|
||||
.types
|
||||
.resolve(converted_v15.outer_enums.error_enum_ty.id)
|
||||
.unwrap();
|
||||
|
||||
// Ensure they match in terms of variants and fields ids.
|
||||
assert_eq!(v15_error.path, converted_v15_error.path);
|
||||
|
||||
let TypeDef::Variant(v15_variant) = &v15_error.type_def else {
|
||||
panic!("V15 error must be a variant");
|
||||
};
|
||||
|
||||
let TypeDef::Variant(converted_v15_variant) = &converted_v15_error.type_def else {
|
||||
panic!("Converted V15 error must be a variant");
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
v15_variant.variants.len(),
|
||||
converted_v15_variant.variants.len()
|
||||
);
|
||||
|
||||
for (v15_var, converted_v15_var) in v15_variant
|
||||
.variants
|
||||
.iter()
|
||||
.zip(converted_v15_variant.variants.iter())
|
||||
{
|
||||
// Variant name must match.
|
||||
assert_eq!(v15_var.name, converted_v15_var.name);
|
||||
assert_eq!(v15_var.fields.len(), converted_v15_var.fields.len());
|
||||
|
||||
// Fields must have the same type.
|
||||
for (v15_field, converted_v15_field) in
|
||||
v15_var.fields.iter().zip(converted_v15_var.fields.iter())
|
||||
{
|
||||
assert_eq!(v15_field.ty.id, converted_v15_field.ty.id);
|
||||
|
||||
let ty = v15.types.resolve(v15_field.ty.id).unwrap();
|
||||
let converted_ty = converted_v15
|
||||
.types
|
||||
.resolve(converted_v15_field.ty.id)
|
||||
.unwrap();
|
||||
assert_eq!(ty, converted_ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_extrinsic_types() {
|
||||
#[derive(TypeInfo)]
|
||||
struct Runtime;
|
||||
|
||||
let generate_metadata = |extrinsic_ty| {
|
||||
let mut registry = scale_info::Registry::new();
|
||||
|
||||
let ty = registry.register_type(&meta_type::<Runtime>());
|
||||
|
||||
let extrinsic = ExtrinsicMetadata {
|
||||
ty: extrinsic_ty,
|
||||
version: 0,
|
||||
signed_extensions: vec![],
|
||||
}
|
||||
.into_portable(&mut registry);
|
||||
|
||||
v14::RuntimeMetadataV14 {
|
||||
types: registry.into(),
|
||||
pallets: Vec::new(),
|
||||
extrinsic,
|
||||
ty,
|
||||
}
|
||||
};
|
||||
|
||||
let metadata = generate_metadata(meta_type::<()>());
|
||||
let err = v14_to_v15(metadata).unwrap_err();
|
||||
assert_eq!(err, TryFromError::TypeNameNotFound("Address".into()));
|
||||
|
||||
#[derive(TypeInfo)]
|
||||
struct ExtrinsicNoCall<Address, Signature, Extra> {
|
||||
_phantom: PhantomData<(Address, Signature, Extra)>,
|
||||
}
|
||||
let metadata = generate_metadata(meta_type::<ExtrinsicNoCall<(), (), ()>>());
|
||||
let err = v14_to_v15(metadata).unwrap_err();
|
||||
assert_eq!(err, TryFromError::TypeNameNotFound("Call".into()));
|
||||
|
||||
#[derive(TypeInfo)]
|
||||
struct ExtrinsicNoSign<Call, Address, Extra> {
|
||||
_phantom: PhantomData<(Call, Address, Extra)>,
|
||||
}
|
||||
let metadata = generate_metadata(meta_type::<ExtrinsicNoSign<(), (), ()>>());
|
||||
let err = v14_to_v15(metadata).unwrap_err();
|
||||
assert_eq!(err, TryFromError::TypeNameNotFound("Signature".into()));
|
||||
|
||||
#[derive(TypeInfo)]
|
||||
struct ExtrinsicNoExtra<Call, Address, Signature> {
|
||||
_phantom: PhantomData<(Call, Address, Signature)>,
|
||||
}
|
||||
let metadata = generate_metadata(meta_type::<ExtrinsicNoExtra<(), (), ()>>());
|
||||
let err = v14_to_v15(metadata).unwrap_err();
|
||||
assert_eq!(err, TryFromError::TypeNameNotFound("Extra".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_outer_enum_types() {
|
||||
#[derive(TypeInfo)]
|
||||
struct Runtime;
|
||||
|
||||
#[derive(TypeInfo)]
|
||||
enum RuntimeCall {}
|
||||
#[derive(TypeInfo)]
|
||||
enum RuntimeEvent {}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct ExtrinsicType<Address, Call, Signature, Extra> {
|
||||
pub signature: Option<(Address, Signature, Extra)>,
|
||||
pub function: Call,
|
||||
}
|
||||
|
||||
// Missing runtime call.
|
||||
{
|
||||
let mut registry = scale_info::Registry::new();
|
||||
let ty = registry.register_type(&meta_type::<Runtime>());
|
||||
registry.register_type(&meta_type::<RuntimeEvent>());
|
||||
|
||||
let extrinsic = ExtrinsicMetadata {
|
||||
ty: meta_type::<ExtrinsicType<(), (), (), ()>>(),
|
||||
version: 0,
|
||||
signed_extensions: vec![],
|
||||
}
|
||||
.into_portable(&mut registry);
|
||||
|
||||
let metadata = v14::RuntimeMetadataV14 {
|
||||
types: registry.into(),
|
||||
pallets: Vec::new(),
|
||||
extrinsic,
|
||||
ty,
|
||||
};
|
||||
|
||||
let err = v14_to_v15(metadata).unwrap_err();
|
||||
assert_eq!(err, TryFromError::TypeNameNotFound("RuntimeCall".into()));
|
||||
}
|
||||
|
||||
// Missing runtime event.
|
||||
{
|
||||
let mut registry = scale_info::Registry::new();
|
||||
let ty = registry.register_type(&meta_type::<Runtime>());
|
||||
registry.register_type(&meta_type::<RuntimeCall>());
|
||||
|
||||
let extrinsic = ExtrinsicMetadata {
|
||||
ty: meta_type::<ExtrinsicType<(), (), (), ()>>(),
|
||||
version: 0,
|
||||
signed_extensions: vec![],
|
||||
}
|
||||
.into_portable(&mut registry);
|
||||
|
||||
let metadata = v14::RuntimeMetadataV14 {
|
||||
types: registry.into(),
|
||||
pallets: Vec::new(),
|
||||
extrinsic,
|
||||
ty,
|
||||
};
|
||||
|
||||
let err = v14_to_v15(metadata).unwrap_err();
|
||||
assert_eq!(err, TryFromError::TypeNameNotFound("RuntimeEvent".into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,426 +0,0 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::TryFromError;
|
||||
|
||||
use crate::utils::variant_index::VariantIndex;
|
||||
use crate::{
|
||||
utils::ordered_map::OrderedMap, ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata,
|
||||
OuterEnumsMetadata, PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadata,
|
||||
RuntimeApiMethodParamMetadata, StorageEntryMetadata, StorageEntryModifier, StorageEntryType,
|
||||
StorageHasher, StorageMetadata, TransactionExtensionMetadata,
|
||||
};
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::vec;
|
||||
use frame_metadata::v15;
|
||||
use hashbrown::HashMap;
|
||||
use scale_info::form::PortableForm;
|
||||
|
||||
// Converting from V15 metadata into our Subxt repr.
|
||||
mod from_v15 {
|
||||
use super::*;
|
||||
|
||||
impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
|
||||
type Error = TryFromError;
|
||||
fn try_from(m: v15::RuntimeMetadataV15) -> Result<Self, TryFromError> {
|
||||
let mut pallets = OrderedMap::new();
|
||||
let mut pallets_by_index = HashMap::new();
|
||||
for (pos, p) in m.pallets.into_iter().enumerate() {
|
||||
let name: ArcStr = p.name.into();
|
||||
|
||||
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 call_variant_index =
|
||||
VariantIndex::build(p.calls.as_ref().map(|c| c.ty.id), &m.types);
|
||||
let error_variant_index =
|
||||
VariantIndex::build(p.error.as_ref().map(|e| e.ty.id), &m.types);
|
||||
let event_variant_index =
|
||||
VariantIndex::build(p.event.as_ref().map(|e| e.ty.id), &m.types);
|
||||
|
||||
pallets_by_index.insert(p.index, pos);
|
||||
pallets.push_insert(
|
||||
name.clone(),
|
||||
PalletMetadataInner {
|
||||
name,
|
||||
index: p.index,
|
||||
storage,
|
||||
call_ty: p.calls.map(|c| c.ty.id),
|
||||
call_variant_index,
|
||||
event_ty: p.event.map(|e| e.ty.id),
|
||||
event_variant_index,
|
||||
error_ty: p.error.map(|e| e.ty.id),
|
||||
error_variant_index,
|
||||
constants: constants.collect(),
|
||||
docs: p.docs,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let apis = m.apis.into_iter().map(|api| {
|
||||
let name: ArcStr = api.name.clone().into();
|
||||
(name.clone(), from_runtime_api_metadata(name, api))
|
||||
});
|
||||
|
||||
let dispatch_error_ty = m
|
||||
.types
|
||||
.types
|
||||
.iter()
|
||||
.find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"])
|
||||
.map(|ty| ty.id);
|
||||
|
||||
Ok(Metadata {
|
||||
types: m.types,
|
||||
pallets,
|
||||
pallets_by_index,
|
||||
extrinsic: from_extrinsic_metadata(m.extrinsic),
|
||||
runtime_ty: m.ty.id,
|
||||
dispatch_error_ty,
|
||||
apis: apis.collect(),
|
||||
outer_enums: OuterEnumsMetadata {
|
||||
call_enum_ty: m.outer_enums.call_enum_ty.id,
|
||||
event_enum_ty: m.outer_enums.event_enum_ty.id,
|
||||
error_enum_ty: m.outer_enums.error_enum_ty.id,
|
||||
},
|
||||
custom: m.custom,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn from_signed_extension_metadata(
|
||||
value: v15::SignedExtensionMetadata<PortableForm>,
|
||||
) -> TransactionExtensionMetadata {
|
||||
TransactionExtensionMetadata {
|
||||
identifier: value.identifier,
|
||||
extra_ty: value.ty.id,
|
||||
additional_ty: value.additional_signed.id,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_extrinsic_metadata(value: v15::ExtrinsicMetadata<PortableForm>) -> ExtrinsicMetadata {
|
||||
ExtrinsicMetadata {
|
||||
supported_versions: vec![value.version],
|
||||
transaction_extensions: value
|
||||
.signed_extensions
|
||||
.into_iter()
|
||||
.map(from_signed_extension_metadata)
|
||||
.collect(),
|
||||
address_ty: value.address_ty.id,
|
||||
call_ty: value.call_ty.id,
|
||||
signature_ty: value.signature_ty.id,
|
||||
extra_ty: value.extra_ty.id,
|
||||
transaction_extensions_version: 0,
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
ConstantMetadata {
|
||||
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>,
|
||||
) -> RuntimeApiMethodMetadata {
|
||||
RuntimeApiMethodMetadata {
|
||||
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>,
|
||||
) -> RuntimeApiMethodParamMetadata {
|
||||
RuntimeApiMethodParamMetadata {
|
||||
name: s.name,
|
||||
ty: s.ty.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Converting from our metadata repr to V15 metadata.
|
||||
mod into_v15 {
|
||||
use super::*;
|
||||
|
||||
impl From<Metadata> for v15::RuntimeMetadataV15 {
|
||||
fn from(m: Metadata) -> Self {
|
||||
let pallets = m.pallets.into_values().into_iter().map(|p| {
|
||||
let storage = p.storage.map(|s| v15::PalletStorageMetadata {
|
||||
prefix: s.prefix,
|
||||
entries: s
|
||||
.entries
|
||||
.into_values()
|
||||
.into_iter()
|
||||
.map(from_storage_entry_metadata)
|
||||
.collect(),
|
||||
});
|
||||
|
||||
v15::PalletMetadata {
|
||||
name: (*p.name).to_owned(),
|
||||
calls: p
|
||||
.call_ty
|
||||
.map(|id| v15::PalletCallMetadata { ty: id.into() }),
|
||||
event: p
|
||||
.event_ty
|
||||
.map(|id| v15::PalletEventMetadata { ty: id.into() }),
|
||||
error: p
|
||||
.error_ty
|
||||
.map(|id| v15::PalletErrorMetadata { ty: id.into() }),
|
||||
storage,
|
||||
constants: p
|
||||
.constants
|
||||
.into_values()
|
||||
.into_iter()
|
||||
.map(from_constant_metadata)
|
||||
.collect(),
|
||||
index: p.index,
|
||||
docs: p.docs,
|
||||
}
|
||||
});
|
||||
|
||||
v15::RuntimeMetadataV15 {
|
||||
types: m.types,
|
||||
pallets: pallets.collect(),
|
||||
ty: m.runtime_ty.into(),
|
||||
extrinsic: from_extrinsic_metadata(m.extrinsic),
|
||||
apis: m
|
||||
.apis
|
||||
.into_values()
|
||||
.into_iter()
|
||||
.map(from_runtime_api_metadata)
|
||||
.collect(),
|
||||
outer_enums: v15::OuterEnums {
|
||||
call_enum_ty: m.outer_enums.call_enum_ty.into(),
|
||||
event_enum_ty: m.outer_enums.event_enum_ty.into(),
|
||||
error_enum_ty: m.outer_enums.error_enum_ty.into(),
|
||||
},
|
||||
custom: m.custom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn from_runtime_api_metadata(
|
||||
r: RuntimeApiMetadataInner,
|
||||
) -> v15::RuntimeApiMetadata<PortableForm> {
|
||||
v15::RuntimeApiMetadata {
|
||||
name: (*r.name).to_owned(),
|
||||
methods: r
|
||||
.methods
|
||||
.into_values()
|
||||
.into_iter()
|
||||
.map(from_runtime_api_method_metadata)
|
||||
.collect(),
|
||||
docs: r.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_runtime_api_method_metadata(
|
||||
m: RuntimeApiMethodMetadata,
|
||||
) -> v15::RuntimeApiMethodMetadata<PortableForm> {
|
||||
v15::RuntimeApiMethodMetadata {
|
||||
name: (*m.name).to_owned(),
|
||||
inputs: m
|
||||
.inputs
|
||||
.into_iter()
|
||||
.map(from_runtime_api_method_param_metadata)
|
||||
.collect(),
|
||||
output: m.output_ty.into(),
|
||||
docs: m.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_runtime_api_method_param_metadata(
|
||||
p: RuntimeApiMethodParamMetadata,
|
||||
) -> v15::RuntimeApiMethodParamMetadata<PortableForm> {
|
||||
v15::RuntimeApiMethodParamMetadata {
|
||||
name: p.name,
|
||||
ty: p.ty.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_extrinsic_metadata(e: ExtrinsicMetadata) -> v15::ExtrinsicMetadata<PortableForm> {
|
||||
v15::ExtrinsicMetadata {
|
||||
// V16 and above metadata can have multiple supported extrinsic versions. We have to
|
||||
// pick just one of these if converting back to V14/V15 metadata.
|
||||
//
|
||||
// - Picking the largest may mean that older tooling won't be compatible (it may only
|
||||
// check/support older version).
|
||||
// - Picking the smallest may mean that newer tooling won't work, or newer methods won't
|
||||
// work.
|
||||
//
|
||||
// Either could make sense, but we keep the oldest to prioritize backward compat with
|
||||
// older tooling.
|
||||
version: *e
|
||||
.supported_versions
|
||||
.iter()
|
||||
.min()
|
||||
.expect("at least one extrinsic version expected"),
|
||||
signed_extensions: e
|
||||
.transaction_extensions
|
||||
.into_iter()
|
||||
.map(from_signed_extension_metadata)
|
||||
.collect(),
|
||||
address_ty: e.address_ty.into(),
|
||||
call_ty: e.call_ty.into(),
|
||||
signature_ty: e.signature_ty.into(),
|
||||
extra_ty: e.extra_ty.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_signed_extension_metadata(
|
||||
s: TransactionExtensionMetadata,
|
||||
) -> v15::SignedExtensionMetadata<PortableForm> {
|
||||
v15::SignedExtensionMetadata {
|
||||
identifier: s.identifier,
|
||||
ty: s.extra_ty.into(),
|
||||
additional_signed: s.additional_ty.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_constant_metadata(c: ConstantMetadata) -> v15::PalletConstantMetadata<PortableForm> {
|
||||
v15::PalletConstantMetadata {
|
||||
name: (*c.name).to_owned(),
|
||||
ty: c.ty.into(),
|
||||
value: c.value,
|
||||
docs: c.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_metadata(
|
||||
s: StorageEntryMetadata,
|
||||
) -> v15::StorageEntryMetadata<PortableForm> {
|
||||
v15::StorageEntryMetadata {
|
||||
docs: s.docs,
|
||||
default: s.default,
|
||||
name: (*s.name).to_owned(),
|
||||
ty: from_storage_entry_type(s.entry_type),
|
||||
modifier: from_storage_entry_modifier(s.modifier),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_modifier(s: StorageEntryModifier) -> v15::StorageEntryModifier {
|
||||
match s {
|
||||
StorageEntryModifier::Default => v15::StorageEntryModifier::Default,
|
||||
StorageEntryModifier::Optional => v15::StorageEntryModifier::Optional,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_type(s: StorageEntryType) -> v15::StorageEntryType<PortableForm> {
|
||||
match s {
|
||||
StorageEntryType::Plain(ty) => v15::StorageEntryType::Plain(ty.into()),
|
||||
StorageEntryType::Map {
|
||||
hashers,
|
||||
key_ty,
|
||||
value_ty,
|
||||
} => v15::StorageEntryType::Map {
|
||||
hashers: hashers.into_iter().map(from_storage_hasher).collect(),
|
||||
key: key_ty.into(),
|
||||
value: value_ty.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_hasher(s: StorageHasher) -> v15::StorageHasher {
|
||||
match s {
|
||||
StorageHasher::Blake2_128 => v15::StorageHasher::Blake2_128,
|
||||
StorageHasher::Blake2_256 => v15::StorageHasher::Blake2_256,
|
||||
StorageHasher::Blake2_128Concat => v15::StorageHasher::Blake2_128Concat,
|
||||
StorageHasher::Twox128 => v15::StorageHasher::Twox128,
|
||||
StorageHasher::Twox256 => v15::StorageHasher::Twox256,
|
||||
StorageHasher::Twox64Concat => v15::StorageHasher::Twox64Concat,
|
||||
StorageHasher::Identity => v15::StorageHasher::Identity,
|
||||
}
|
||||
}
|
||||
}
|
||||
+218
-160
@@ -19,10 +19,11 @@
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod from_into;
|
||||
mod from;
|
||||
mod utils;
|
||||
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::String;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
@@ -32,15 +33,19 @@ use frame_decode::extrinsics::{
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use scale_info::{form::PortableForm, PortableRegistry, Variant};
|
||||
use utils::variant_index::VariantIndex;
|
||||
use utils::{ordered_map::OrderedMap, validation::outer_enum_hashes::OuterEnumHashes};
|
||||
use utils::{
|
||||
ordered_map::OrderedMap,
|
||||
validation::{get_custom_value_hash, HASH_LEN},
|
||||
variant_index::VariantIndex,
|
||||
};
|
||||
|
||||
type ArcStr = Arc<str>;
|
||||
|
||||
use crate::utils::validation::{get_custom_value_hash, HASH_LEN};
|
||||
pub use from_into::TryFromError;
|
||||
pub use from::TryFromError;
|
||||
pub use utils::validation::MetadataHasher;
|
||||
|
||||
type CustomMetadataInner = frame_metadata::v15::CustomMetadata<PortableForm>;
|
||||
|
||||
/// Node metadata. This can be constructed by providing some compatible [`frame_metadata`]
|
||||
/// which is then decoded into this. We aim to preserve all of the existing information in
|
||||
/// the incoming metadata while optimizing the format a little for Subxt's use cases.
|
||||
@@ -54,8 +59,6 @@ pub struct Metadata {
|
||||
pallets_by_index: HashMap<u8, usize>,
|
||||
/// Metadata of the extrinsic.
|
||||
extrinsic: ExtrinsicMetadata,
|
||||
/// The type ID of the `Runtime` type.
|
||||
runtime_ty: u32,
|
||||
/// The types of the outer enums.
|
||||
outer_enums: OuterEnumsMetadata,
|
||||
/// The type Id of the `DispatchError` type, which Subxt makes use of.
|
||||
@@ -63,7 +66,7 @@ pub struct Metadata {
|
||||
/// Details about each of the runtime API traits.
|
||||
apis: OrderedMap<ArcStr, RuntimeApiMetadataInner>,
|
||||
/// Allows users to add custom types to the metadata. A map that associates a string key to a `CustomValueMetadata`.
|
||||
custom: frame_metadata::v15::CustomMetadata<PortableForm>,
|
||||
custom: CustomMetadataInner,
|
||||
}
|
||||
|
||||
// Since we've abstracted away from frame-metadatas, we impl this on our custom Metadata
|
||||
@@ -108,8 +111,8 @@ impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
|
||||
&self,
|
||||
) -> Result<ExtrinsicSignatureInfo<Self::TypeId>, ExtrinsicInfoError<'_>> {
|
||||
Ok(ExtrinsicSignatureInfo {
|
||||
address_id: self.extrinsic().address_ty(),
|
||||
signature_id: self.extrinsic().signature_ty(),
|
||||
address_id: self.extrinsic().address_ty,
|
||||
signature_id: self.extrinsic().signature_ty,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -117,28 +120,25 @@ impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
|
||||
&self,
|
||||
extension_version: Option<u8>,
|
||||
) -> Result<ExtrinsicExtensionInfo<'_, Self::TypeId>, ExtrinsicInfoError<'_>> {
|
||||
// For now, if there exists an extension version that's non-zero, we say we don't know
|
||||
// how to decode it. When multiple extension versions exist, we may have to tighten up
|
||||
// on this and require V16 metadata to decode.
|
||||
if let Some(extension_version) = extension_version {
|
||||
if extension_version != 0 {
|
||||
return Err(ExtrinsicInfoError::ExtrinsicExtensionVersionNotSupported {
|
||||
extension_version,
|
||||
});
|
||||
}
|
||||
}
|
||||
let extension_version = extension_version.unwrap_or_else(|| {
|
||||
// We have some transaction, probably a V4 one with no extension version,
|
||||
// but our metadata may support multiple versions. Use the metadata to decide
|
||||
// what version to assume we'll decode it as.
|
||||
self.extrinsic()
|
||||
.transaction_extension_version_to_use_for_decoding()
|
||||
});
|
||||
|
||||
Ok(ExtrinsicExtensionInfo {
|
||||
extension_ids: self
|
||||
.extrinsic()
|
||||
.transaction_extensions()
|
||||
.iter()
|
||||
.map(|f| ExtrinsicInfoArg {
|
||||
name: Cow::Borrowed(f.identifier()),
|
||||
id: f.extra_ty(),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
let extension_ids = self
|
||||
.extrinsic()
|
||||
.transaction_extensions_by_version(extension_version)
|
||||
.ok_or(ExtrinsicInfoError::ExtrinsicExtensionVersionNotFound { extension_version })?
|
||||
.map(|f| ExtrinsicInfoArg {
|
||||
name: Cow::Borrowed(f.identifier()),
|
||||
id: f.extra_ty(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(ExtrinsicExtensionInfo { extension_ids })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,11 +153,6 @@ impl Metadata {
|
||||
&mut self.types
|
||||
}
|
||||
|
||||
/// The type ID of the `Runtime` type.
|
||||
pub fn runtime_ty(&self) -> u32 {
|
||||
self.runtime_ty
|
||||
}
|
||||
|
||||
/// The type ID of the `DispatchError` type, if it exists.
|
||||
pub fn dispatch_error_ty(&self) -> Option<u32> {
|
||||
self.dispatch_error_ty
|
||||
@@ -234,26 +229,10 @@ impl Metadata {
|
||||
MetadataHasher::new(self)
|
||||
}
|
||||
|
||||
/// Filter out any pallets and/or runtime_apis that we don't want to keep, retaining only those that we do.
|
||||
/// Note:
|
||||
/// only filter by `pallet`s will not lead to significant metadata size reduction because the return types are kept to ensure that those can be decoded.
|
||||
///
|
||||
pub fn retain<F, G>(&mut self, pallet_filter: F, api_filter: G)
|
||||
where
|
||||
F: FnMut(&str) -> bool,
|
||||
G: FnMut(&str) -> bool,
|
||||
{
|
||||
utils::retain::retain_metadata(self, pallet_filter, api_filter);
|
||||
}
|
||||
|
||||
/// Get type hash for a type in the registry
|
||||
pub fn type_hash(&self, id: u32) -> Option<[u8; HASH_LEN]> {
|
||||
self.types.resolve(id)?;
|
||||
Some(crate::utils::validation::get_type_hash(
|
||||
&self.types,
|
||||
id,
|
||||
&OuterEnumHashes::empty(),
|
||||
))
|
||||
Some(crate::utils::validation::get_type_hash(&self.types, id))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,6 +293,30 @@ impl<'a> PalletMetadata<'a> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Return an iterator over the View Functions in this pallet, if any.
|
||||
pub fn view_functions(&self) -> impl ExactSizeIterator<Item = PalletViewFunctionMetadata<'a>> {
|
||||
self.inner
|
||||
.view_functions
|
||||
.iter()
|
||||
.map(|vf: &'a _| PalletViewFunctionMetadata {
|
||||
inner: vf,
|
||||
types: self.types,
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate (in no particular order) over the associated type names and type IDs for this pallet.
|
||||
pub fn associated_types(&self) -> impl ExactSizeIterator<Item = (&str, u32)> {
|
||||
self.inner
|
||||
.associated_types
|
||||
.iter()
|
||||
.map(|(name, ty)| (&**name, *ty))
|
||||
}
|
||||
|
||||
/// Fetch an associated type ID given the associated type name.
|
||||
pub fn associated_type_id(&self, name: &str) -> Option<u32> {
|
||||
self.inner.associated_types.get(name).copied()
|
||||
}
|
||||
|
||||
/// Return all of the call variants, if a call type exists.
|
||||
pub fn call_variants(&self) -> Option<&'a [Variant<PortableForm>]> {
|
||||
VariantIndex::get(self.inner.call_ty, self.types)
|
||||
@@ -374,7 +377,7 @@ impl<'a> PalletMetadata<'a> {
|
||||
|
||||
/// Return a hash for the entire pallet.
|
||||
pub fn hash(&self) -> [u8; HASH_LEN] {
|
||||
crate::utils::validation::get_pallet_hash(*self, &OuterEnumHashes::empty())
|
||||
crate::utils::validation::get_pallet_hash(*self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,6 +403,10 @@ struct PalletMetadataInner {
|
||||
error_variant_index: VariantIndex,
|
||||
/// Map from constant name to constant details.
|
||||
constants: OrderedMap<ArcStr, ConstantMetadata>,
|
||||
/// Details about each of the pallet view functions.
|
||||
view_functions: Vec<PalletViewFunctionMetadataInner>,
|
||||
/// Mapping from associated type to type ID describing its shape.
|
||||
associated_types: BTreeMap<String, u32>,
|
||||
/// Pallet documentation.
|
||||
docs: Vec<String>,
|
||||
}
|
||||
@@ -593,74 +600,101 @@ impl ConstantMetadata {
|
||||
/// Metadata for the extrinsic type.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExtrinsicMetadata {
|
||||
/// The type of the address that signs the extrinsic
|
||||
/// The type of the address that signs the extrinsic.
|
||||
/// Used to help decode tx signatures.
|
||||
address_ty: u32,
|
||||
/// The type of the outermost Call enum.
|
||||
call_ty: u32,
|
||||
/// The type of the extrinsic's signature.
|
||||
/// Used to help decode tx signatures.
|
||||
signature_ty: u32,
|
||||
/// The type of the outermost Extra enum.
|
||||
extra_ty: u32,
|
||||
/// Which extrinsic versions are supported by this chain.
|
||||
supported_versions: Vec<u8>,
|
||||
/// The signed extensions in the order they appear in the extrinsic.
|
||||
transaction_extensions: Vec<TransactionExtensionMetadata>,
|
||||
/// Version of the transaction extensions.
|
||||
// TODO [jsdw]: V16 metadata groups transaction extensions by version.
|
||||
// need to work out what to do once there is more than one version to deal with.
|
||||
transaction_extensions_version: u8,
|
||||
transaction_extensions: Vec<TransactionExtensionMetadataInner>,
|
||||
/// Different versions of transaction extensions can exist. Each version
|
||||
/// is a u8 which corresponds to the indexes of the transaction extensions
|
||||
/// seen in the above Vec, in order, that exist at that version.
|
||||
transaction_extensions_by_version: BTreeMap<u8, Vec<u32>>,
|
||||
}
|
||||
|
||||
impl ExtrinsicMetadata {
|
||||
/// The type of the address that signs the extrinsic
|
||||
pub fn address_ty(&self) -> u32 {
|
||||
self.address_ty
|
||||
}
|
||||
|
||||
/// The type of the outermost Call enum.
|
||||
pub fn call_ty(&self) -> u32 {
|
||||
self.call_ty
|
||||
}
|
||||
/// The type of the extrinsic's signature.
|
||||
pub fn signature_ty(&self) -> u32 {
|
||||
self.signature_ty
|
||||
}
|
||||
/// The type of the outermost Extra enum.
|
||||
pub fn extra_ty(&self) -> u32 {
|
||||
self.extra_ty
|
||||
}
|
||||
|
||||
/// Which extrinsic versions are supported.
|
||||
pub fn supported_versions(&self) -> &[u8] {
|
||||
&self.supported_versions
|
||||
}
|
||||
|
||||
/// The extra/additional information associated with the extrinsic.
|
||||
pub fn transaction_extensions(&self) -> &[TransactionExtensionMetadata] {
|
||||
&self.transaction_extensions
|
||||
pub fn transaction_extensions_by_version(
|
||||
&self,
|
||||
version: u8,
|
||||
) -> Option<impl Iterator<Item = TransactionExtensionMetadata<'_>>> {
|
||||
let extension_indexes = self.transaction_extensions_by_version.get(&version)?;
|
||||
let iter = extension_indexes.iter().map(|index| {
|
||||
let tx_metadata = self
|
||||
.transaction_extensions
|
||||
.get(*index as usize)
|
||||
.expect("transaction extension should exist if index is in transaction_extensions_by_version");
|
||||
|
||||
TransactionExtensionMetadata {
|
||||
identifier: &tx_metadata.identifier,
|
||||
extra_ty: tx_metadata.extra_ty,
|
||||
additional_ty: tx_metadata.additional_ty,
|
||||
}
|
||||
});
|
||||
|
||||
Some(iter)
|
||||
}
|
||||
|
||||
/// Which version are these transaction extensions?
|
||||
pub fn transaction_extensions_version(&self) -> u8 {
|
||||
self.transaction_extensions_version
|
||||
/// When constructing a v5 extrinsic, use this transaction extensions version.
|
||||
pub fn transaction_extension_version_to_use_for_encoding(&self) -> u8 {
|
||||
*self
|
||||
.transaction_extensions_by_version
|
||||
.keys()
|
||||
.max()
|
||||
.expect("At least one version of transaction extensions is expected")
|
||||
}
|
||||
|
||||
/// An iterator of the transaction extensions to use when encoding a transaction. Basically equivalent to
|
||||
/// `self.transaction_extensions_by_version(self.transaction_extension_version_to_use_for_encoding()).unwrap()`
|
||||
pub fn transaction_extensions_to_use_for_encoding(
|
||||
&self,
|
||||
) -> impl Iterator<Item = TransactionExtensionMetadata<'_>> {
|
||||
let encoding_version = self.transaction_extension_version_to_use_for_encoding();
|
||||
self.transaction_extensions_by_version(encoding_version)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// When presented with a v4 extrinsic that has no version, treat it as being this version.
|
||||
pub fn transaction_extension_version_to_use_for_decoding(&self) -> u8 {
|
||||
*self
|
||||
.transaction_extensions_by_version
|
||||
.keys()
|
||||
.max()
|
||||
.expect("At least one version of transaction extensions is expected")
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata for the signed extensions used by extrinsics.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TransactionExtensionMetadata {
|
||||
/// The unique signed extension identifier, which may be different from the type name.
|
||||
identifier: String,
|
||||
/// The type of the signed extension, with the data to be included in the extrinsic.
|
||||
pub struct TransactionExtensionMetadata<'a> {
|
||||
/// The unique transaction extension identifier, which may be different from the type name.
|
||||
identifier: &'a str,
|
||||
/// The type of the transaction extension, with the data to be included in the extrinsic.
|
||||
extra_ty: u32,
|
||||
/// The type of the additional signed data, with the data to be included in the signed payload
|
||||
/// The type of the additional signed data, with the data to be included in the signed payload.
|
||||
additional_ty: u32,
|
||||
}
|
||||
|
||||
impl TransactionExtensionMetadata {
|
||||
#[derive(Debug, Clone)]
|
||||
struct TransactionExtensionMetadataInner {
|
||||
identifier: String,
|
||||
extra_ty: u32,
|
||||
additional_ty: u32,
|
||||
}
|
||||
|
||||
impl<'a> TransactionExtensionMetadata<'a> {
|
||||
/// The unique signed extension identifier, which may be different from the type name.
|
||||
pub fn identifier(&self) -> &str {
|
||||
&self.identifier
|
||||
pub fn identifier(&self) -> &'a str {
|
||||
self.identifier
|
||||
}
|
||||
/// The type of the signed extension, with the data to be included in the extrinsic.
|
||||
pub fn extra_ty(&self) -> u32 {
|
||||
@@ -717,21 +751,31 @@ impl<'a> RuntimeApiMetadata<'a> {
|
||||
&self.inner.docs
|
||||
}
|
||||
/// An iterator over the trait methods.
|
||||
pub fn methods(&self) -> impl ExactSizeIterator<Item = &'a RuntimeApiMethodMetadata> {
|
||||
self.inner.methods.values().iter()
|
||||
pub fn methods(&self) -> impl ExactSizeIterator<Item = RuntimeApiMethodMetadata<'a>> {
|
||||
self.inner
|
||||
.methods
|
||||
.values()
|
||||
.iter()
|
||||
.map(|item| RuntimeApiMethodMetadata {
|
||||
trait_name: &self.inner.name,
|
||||
inner: item,
|
||||
types: self.types,
|
||||
})
|
||||
}
|
||||
/// Get a specific trait method given its name.
|
||||
pub fn method_by_name(&self, name: &str) -> Option<&'a RuntimeApiMethodMetadata> {
|
||||
self.inner.methods.get_by_key(name)
|
||||
pub fn method_by_name(&self, name: &str) -> Option<RuntimeApiMethodMetadata<'a>> {
|
||||
self.inner
|
||||
.methods
|
||||
.get_by_key(name)
|
||||
.map(|item| RuntimeApiMethodMetadata {
|
||||
trait_name: &self.inner.name,
|
||||
inner: item,
|
||||
types: self.types,
|
||||
})
|
||||
}
|
||||
/// Return a hash for the constant, or None if it was not found.
|
||||
pub fn method_hash(&self, method_name: &str) -> Option<[u8; HASH_LEN]> {
|
||||
crate::utils::validation::get_runtime_api_hash(self, method_name)
|
||||
}
|
||||
|
||||
/// Return a hash for the runtime API trait.
|
||||
pub fn hash(&self) -> [u8; HASH_LEN] {
|
||||
crate::utils::validation::get_runtime_trait_hash(*self, &OuterEnumHashes::empty())
|
||||
crate::utils::validation::get_runtime_apis_hash(*self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,46 +784,102 @@ struct RuntimeApiMetadataInner {
|
||||
/// Trait name.
|
||||
name: ArcStr,
|
||||
/// Trait methods.
|
||||
methods: OrderedMap<ArcStr, RuntimeApiMethodMetadata>,
|
||||
methods: OrderedMap<ArcStr, RuntimeApiMethodMetadataInner>,
|
||||
/// Trait documentation.
|
||||
docs: Vec<String>,
|
||||
}
|
||||
|
||||
/// Metadata for a single runtime API method.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RuntimeApiMethodMetadata {
|
||||
pub struct RuntimeApiMethodMetadata<'a> {
|
||||
trait_name: &'a str,
|
||||
inner: &'a RuntimeApiMethodMetadataInner,
|
||||
types: &'a PortableRegistry,
|
||||
}
|
||||
|
||||
impl<'a> RuntimeApiMethodMetadata<'a> {
|
||||
/// Method name.
|
||||
pub fn name(&self) -> &'a str {
|
||||
&self.inner.name
|
||||
}
|
||||
/// Method documentation.
|
||||
pub fn docs(&self) -> &[String] {
|
||||
&self.inner.docs
|
||||
}
|
||||
/// Method inputs.
|
||||
pub fn inputs(&self) -> impl ExactSizeIterator<Item = &MethodParamMetadata> {
|
||||
self.inner.inputs.iter()
|
||||
}
|
||||
/// Method return type.
|
||||
pub fn output_ty(&self) -> u32 {
|
||||
self.inner.output_ty
|
||||
}
|
||||
/// Return a hash for the method.
|
||||
pub fn hash(&self) -> [u8; HASH_LEN] {
|
||||
crate::utils::validation::get_runtime_api_hash(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct RuntimeApiMethodMetadataInner {
|
||||
/// Method name.
|
||||
name: ArcStr,
|
||||
/// Method parameters.
|
||||
inputs: Vec<RuntimeApiMethodParamMetadata>,
|
||||
inputs: Vec<MethodParamMetadata>,
|
||||
/// Method output type.
|
||||
output_ty: u32,
|
||||
/// Method documentation.
|
||||
docs: Vec<String>,
|
||||
}
|
||||
|
||||
impl RuntimeApiMethodMetadata {
|
||||
/// Metadata for the available pallet View Functions.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PalletViewFunctionMetadata<'a> {
|
||||
inner: &'a PalletViewFunctionMetadataInner,
|
||||
types: &'a PortableRegistry,
|
||||
}
|
||||
|
||||
impl PalletViewFunctionMetadata<'_> {
|
||||
/// Method name.
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
&self.inner.name
|
||||
}
|
||||
/// 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) -> [u8; 32] {
|
||||
self.inner.query_id
|
||||
}
|
||||
/// Method documentation.
|
||||
pub fn docs(&self) -> &[String] {
|
||||
&self.docs
|
||||
&self.inner.docs
|
||||
}
|
||||
/// Method inputs.
|
||||
pub fn inputs(&self) -> impl ExactSizeIterator<Item = &RuntimeApiMethodParamMetadata> {
|
||||
self.inputs.iter()
|
||||
pub fn inputs(&self) -> impl ExactSizeIterator<Item = &MethodParamMetadata> {
|
||||
self.inner.inputs.iter()
|
||||
}
|
||||
/// Method return type.
|
||||
pub fn output_ty(&self) -> u32 {
|
||||
self.output_ty
|
||||
self.inner.output_ty
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata for a single input parameter to a runtime API method.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RuntimeApiMethodParamMetadata {
|
||||
struct PalletViewFunctionMetadataInner {
|
||||
/// View function name.
|
||||
name: String,
|
||||
/// View function query ID.
|
||||
query_id: [u8; 32],
|
||||
/// Input types.
|
||||
inputs: Vec<MethodParamMetadata>,
|
||||
/// Output type.
|
||||
output_ty: u32,
|
||||
/// Documentation.
|
||||
docs: Vec<String>,
|
||||
}
|
||||
|
||||
/// Metadata for a single input parameter to a runtime API method / pallet view function.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MethodParamMetadata {
|
||||
/// Parameter name.
|
||||
pub name: String,
|
||||
/// Parameter type.
|
||||
@@ -790,7 +890,7 @@ pub struct RuntimeApiMethodParamMetadata {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CustomMetadata<'a> {
|
||||
types: &'a PortableRegistry,
|
||||
inner: &'a frame_metadata::v15::CustomMetadata<PortableForm>,
|
||||
inner: &'a CustomMetadataInner,
|
||||
}
|
||||
|
||||
impl<'a> CustomMetadata<'a> {
|
||||
@@ -854,7 +954,7 @@ impl<'a> CustomValueMetadata<'a> {
|
||||
|
||||
/// Calculates the hash for the CustomValueMetadata.
|
||||
pub fn hash(&self) -> [u8; HASH_LEN] {
|
||||
get_custom_value_hash(self, &OuterEnumHashes::empty())
|
||||
get_custom_value_hash(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -872,45 +972,3 @@ impl codec::Decode for Metadata {
|
||||
metadata.map_err(|_e| "Cannot try_into() to Metadata.".into())
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata can be encoded, too. It will encode into a format that's compatible with what
|
||||
// Subxt requires, and that it can be decoded back from. The actual specifics of the format
|
||||
// can change over time.
|
||||
impl codec::Encode for Metadata {
|
||||
fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
|
||||
let m: frame_metadata::v15::RuntimeMetadataV15 = self.clone().into();
|
||||
let m: frame_metadata::RuntimeMetadataPrefixed = m.into();
|
||||
m.encode_to(dest)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
fn load_metadata() -> Vec<u8> {
|
||||
std::fs::read("../artifacts/polkadot_metadata_full.scale").unwrap()
|
||||
}
|
||||
|
||||
// We don't expect to lose any information converting back and forth between
|
||||
// our own representation and the latest version emitted from a node that we can
|
||||
// work with.
|
||||
#[test]
|
||||
fn is_isomorphic_to_v15() {
|
||||
let bytes = load_metadata();
|
||||
|
||||
// Decode into our metadata struct:
|
||||
let metadata = Metadata::decode(&mut &*bytes).unwrap();
|
||||
|
||||
// Convert into v15 metadata:
|
||||
let v15: frame_metadata::v15::RuntimeMetadataV15 = metadata.into();
|
||||
let prefixed = frame_metadata::RuntimeMetadataPrefixed::from(v15);
|
||||
|
||||
// Re-encode that:
|
||||
let new_bytes = prefixed.encode();
|
||||
|
||||
// The bytes should be identical:
|
||||
assert_eq!(bytes, new_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,5 @@
|
||||
// see LICENSE for license details.
|
||||
|
||||
pub mod ordered_map;
|
||||
pub mod retain;
|
||||
pub mod validation;
|
||||
pub mod variant_index;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// see LICENSE for license details.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use core::mem;
|
||||
use hashbrown::HashMap;
|
||||
|
||||
/// A minimal ordered map to let one search for
|
||||
@@ -44,32 +43,6 @@ where
|
||||
self.values.is_empty()
|
||||
}
|
||||
|
||||
/// Retain specific entries.
|
||||
pub fn retain<F>(&mut self, mut f: F)
|
||||
where
|
||||
F: FnMut(&V) -> bool,
|
||||
{
|
||||
let values = mem::take(&mut self.values);
|
||||
let map = mem::take(&mut self.map);
|
||||
|
||||
// Filter the values, storing a map from old to new positions:
|
||||
let mut new_values = Vec::new();
|
||||
let mut old_pos_to_new_pos = HashMap::new();
|
||||
for (pos, value) in values.into_iter().enumerate().filter(|(_, v)| f(v)) {
|
||||
old_pos_to_new_pos.insert(pos, new_values.len());
|
||||
new_values.push(value);
|
||||
}
|
||||
|
||||
// Update the values now we've filtered them:
|
||||
self.values = new_values;
|
||||
|
||||
// Rebuild the map using the new positions:
|
||||
self.map = map
|
||||
.into_iter()
|
||||
.filter_map(|(k, v)| old_pos_to_new_pos.get(&v).map(|v2| (k, *v2)))
|
||||
.collect();
|
||||
}
|
||||
|
||||
/// Push/insert an item to the end of the map.
|
||||
pub fn push_insert(&mut self, key: K, value: V) {
|
||||
let idx = self.values.len();
|
||||
@@ -95,16 +68,6 @@ where
|
||||
pub fn values(&self) -> &[V] {
|
||||
&self.values
|
||||
}
|
||||
|
||||
/// Mutable access to the underlying values.
|
||||
pub fn values_mut(&mut self) -> &mut [V] {
|
||||
&mut self.values
|
||||
}
|
||||
|
||||
/// Return the underlying values.
|
||||
pub fn into_values(self) -> Vec<V> {
|
||||
self.values
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> FromIterator<(K, V)> for OrderedMap<K, V>
|
||||
@@ -119,21 +82,3 @@ where
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn retain() {
|
||||
let mut m = OrderedMap::from_iter([(1, 'a'), (2, 'b'), (3, 'c')]);
|
||||
|
||||
m.retain(|v| *v != 'b');
|
||||
|
||||
assert_eq!(m.get_by_key(&1), Some(&'a'));
|
||||
assert_eq!(m.get_by_key(&2), None);
|
||||
assert_eq!(m.get_by_key(&3), Some(&'c'));
|
||||
|
||||
assert_eq!(m.values(), &['a', 'c'])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,444 +0,0 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! Utility functions to generate a subset of the metadata.
|
||||
|
||||
use crate::{
|
||||
ExtrinsicMetadata, Metadata, PalletMetadataInner, RuntimeApiMetadataInner, StorageEntryType,
|
||||
};
|
||||
use alloc::collections::BTreeSet;
|
||||
use alloc::vec::Vec;
|
||||
use scale_info::{
|
||||
PortableType, TypeDef, TypeDefArray, TypeDefBitSequence, TypeDefCompact, TypeDefComposite,
|
||||
TypeDefSequence, TypeDefTuple, TypeDefVariant,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TypeSet {
|
||||
seen_ids: BTreeSet<u32>,
|
||||
pub work_set: Vec<u32>,
|
||||
}
|
||||
|
||||
impl TypeSet {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
seen_ids: BTreeSet::new(),
|
||||
// Average work set size is around 30-50 elements, depending on the metadata size
|
||||
work_set: Vec::with_capacity(32),
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(&mut self, id: u32) -> bool {
|
||||
self.seen_ids.insert(id)
|
||||
}
|
||||
|
||||
fn contains(&mut self, id: u32) -> bool {
|
||||
self.seen_ids.contains(&id)
|
||||
}
|
||||
|
||||
fn push_to_workset(&mut self, id: u32) {
|
||||
// Check if wee hit a type we've already inserted; avoid infinite loops and stop.
|
||||
if self.insert(id) {
|
||||
self.work_set.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// This function will deeply traverse the initial type and it's dependencies to collect the relevant type_ids
|
||||
fn collect_types(&mut self, metadata: &Metadata, id: u32) {
|
||||
self.push_to_workset(id);
|
||||
while let Some(typ) = self.work_set.pop() {
|
||||
let typ = resolve_typ(metadata, typ);
|
||||
match &typ.ty.type_def {
|
||||
TypeDef::Composite(TypeDefComposite { fields }) => {
|
||||
for field in fields {
|
||||
self.push_to_workset(field.ty.id);
|
||||
}
|
||||
}
|
||||
TypeDef::Variant(TypeDefVariant { variants }) => {
|
||||
for variant in variants {
|
||||
for field in &variant.fields {
|
||||
self.push_to_workset(field.ty.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
TypeDef::Array(TypeDefArray { len: _, type_param })
|
||||
| TypeDef::Sequence(TypeDefSequence { type_param })
|
||||
| TypeDef::Compact(TypeDefCompact { type_param }) => {
|
||||
self.push_to_workset(type_param.id);
|
||||
}
|
||||
TypeDef::Tuple(TypeDefTuple { fields }) => {
|
||||
for field in fields {
|
||||
self.push_to_workset(field.id);
|
||||
}
|
||||
}
|
||||
TypeDef::Primitive(_) => (),
|
||||
TypeDef::BitSequence(TypeDefBitSequence {
|
||||
bit_store_type,
|
||||
bit_order_type,
|
||||
}) => {
|
||||
for typ in [bit_order_type, bit_store_type] {
|
||||
self.push_to_workset(typ.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_extrinsic_types(&mut self, extrinsic: &ExtrinsicMetadata) {
|
||||
for ty in [
|
||||
extrinsic.address_ty,
|
||||
extrinsic.call_ty,
|
||||
extrinsic.signature_ty,
|
||||
extrinsic.extra_ty,
|
||||
] {
|
||||
self.insert(ty);
|
||||
}
|
||||
|
||||
for signed in &extrinsic.transaction_extensions {
|
||||
self.insert(signed.extra_ty);
|
||||
self.insert(signed.additional_ty);
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect all type IDs needed to represent the runtime APIs.
|
||||
fn collect_runtime_api_types(&mut self, metadata: &Metadata, api: &RuntimeApiMetadataInner) {
|
||||
for method in api.methods.values() {
|
||||
self.collect_types(metadata, method.output_ty);
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect all type IDs needed to represent the provided pallet.
|
||||
fn collect_pallet_types(&mut self, pallet: &PalletMetadataInner, metadata: &Metadata) {
|
||||
if let Some(storage) = &pallet.storage {
|
||||
for entry in storage.entries() {
|
||||
match entry.entry_type {
|
||||
StorageEntryType::Plain(ty) => {
|
||||
self.collect_types(metadata, ty);
|
||||
}
|
||||
StorageEntryType::Map {
|
||||
key_ty, value_ty, ..
|
||||
} => {
|
||||
self.collect_types(metadata, key_ty);
|
||||
self.collect_types(metadata, value_ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for constant in pallet.constants.values() {
|
||||
self.collect_types(metadata, constant.ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_typ(metadata: &Metadata, typ: u32) -> &PortableType {
|
||||
metadata
|
||||
.types
|
||||
.types
|
||||
.get(typ as usize)
|
||||
.expect("Metadata should contain enum type in registry")
|
||||
}
|
||||
|
||||
/// Generate a subset of the metadata that contains only the
|
||||
/// types needed to represent the provided pallets and runtime APIs.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Used to strip metadata of unneeded information and to reduce the
|
||||
/// binary size.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the [`scale_info::PortableRegistry`] did not retain all needed types,
|
||||
/// or the metadata does not contain the "sp_runtime::DispatchError" type.
|
||||
pub fn retain_metadata<F, G>(
|
||||
metadata: &mut Metadata,
|
||||
mut pallets_filter: F,
|
||||
mut runtime_apis_filter: G,
|
||||
) where
|
||||
F: FnMut(&str) -> bool,
|
||||
G: FnMut(&str) -> bool,
|
||||
{
|
||||
// 1. Delete pallets we don't want to keep.
|
||||
metadata
|
||||
.pallets
|
||||
.retain(|pallet| pallets_filter(&pallet.name));
|
||||
metadata.pallets_by_index = metadata
|
||||
.pallets
|
||||
.values()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(pos, p)| (p.index, pos))
|
||||
.collect();
|
||||
|
||||
// 2. Delete runtime APIs we don't want to keep.
|
||||
metadata.apis.retain(|api| runtime_apis_filter(&api.name));
|
||||
|
||||
// 3. For each outer enum type, strip it if possible, ie if it is not returned by any
|
||||
// of the things we're keeping (because if it is, we need to keep all of it so that we
|
||||
// can still decode values into it).
|
||||
let outer_enums = metadata.outer_enums();
|
||||
let mut find_type_id = keep_outer_enum(metadata, &mut pallets_filter, &mut runtime_apis_filter);
|
||||
for outer_enum_ty_id in [
|
||||
outer_enums.call_enum_ty(),
|
||||
outer_enums.error_enum_ty(),
|
||||
outer_enums.event_enum_ty(),
|
||||
] {
|
||||
if !find_type_id(outer_enum_ty_id) {
|
||||
strip_variants_in_enum_type(metadata, &mut pallets_filter, outer_enum_ty_id);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Collect all of the type IDs we still want to keep after deleting.
|
||||
let mut keep_these_type_ids: BTreeSet<u32> =
|
||||
iterate_metadata_types(metadata).map(|x| *x).collect();
|
||||
|
||||
// 5. Additionally, subxt depends on the `DispatchError` type existing; we use the same
|
||||
// logic here that is used when building our `Metadata` to ensure we keep it too.
|
||||
let dispatch_error_ty = metadata
|
||||
.types
|
||||
.types
|
||||
.iter()
|
||||
.find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"])
|
||||
.expect("Metadata must contain sp_runtime::DispatchError");
|
||||
|
||||
keep_these_type_ids.insert(dispatch_error_ty.id);
|
||||
|
||||
// 5. Strip all of the type IDs we no longer need, based on the above set.
|
||||
let map_ids = metadata
|
||||
.types
|
||||
.retain(|id| keep_these_type_ids.contains(&id));
|
||||
|
||||
// 6. Now, update the type IDs referenced in our metadata to reflect this.
|
||||
for id in iterate_metadata_types(metadata) {
|
||||
if let Some(new_id) = map_ids.get(id) {
|
||||
*id = *new_id;
|
||||
} else {
|
||||
panic!("Type id {id} was not retained. This is a bug");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn strip_variants_in_enum_type<F>(metadata: &mut Metadata, mut pallets_filter: F, id: u32)
|
||||
where
|
||||
F: FnMut(&str) -> bool,
|
||||
{
|
||||
let ty = {
|
||||
metadata
|
||||
.types
|
||||
.types
|
||||
.get_mut(id as usize)
|
||||
.expect("Metadata should contain enum type in registry")
|
||||
};
|
||||
|
||||
let TypeDef::Variant(variant) = &mut ty.ty.type_def else {
|
||||
panic!("Metadata type is expected to be a variant type");
|
||||
};
|
||||
|
||||
variant.variants.retain(|v| pallets_filter(&v.name));
|
||||
}
|
||||
|
||||
/// Returns an iterator that allows modifying each type ID seen in the metadata (not recursively).
|
||||
/// This will iterate over every type referenced in the metadata outside of `metadata.types`.
|
||||
fn iterate_metadata_types(metadata: &mut Metadata) -> impl Iterator<Item = &mut u32> {
|
||||
let mut types = alloc::vec::Vec::new();
|
||||
|
||||
// collect outer_enum top-level types
|
||||
let outer_enum = &mut metadata.outer_enums;
|
||||
types.push(&mut outer_enum.call_enum_ty);
|
||||
types.push(&mut outer_enum.event_enum_ty);
|
||||
types.push(&mut outer_enum.error_enum_ty);
|
||||
|
||||
// collect pallet top-level type ids
|
||||
for pallet in metadata.pallets.values_mut() {
|
||||
if let Some(storage) = &mut pallet.storage {
|
||||
for entry in storage.entries.values_mut() {
|
||||
match &mut entry.entry_type {
|
||||
StorageEntryType::Plain(ty) => {
|
||||
types.push(ty);
|
||||
}
|
||||
StorageEntryType::Map {
|
||||
key_ty, value_ty, ..
|
||||
} => {
|
||||
types.push(key_ty);
|
||||
types.push(value_ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(ty) = &mut pallet.call_ty {
|
||||
types.push(ty);
|
||||
}
|
||||
|
||||
if let Some(ty) = &mut pallet.event_ty {
|
||||
types.push(ty);
|
||||
}
|
||||
|
||||
if let Some(ty) = &mut pallet.error_ty {
|
||||
types.push(ty);
|
||||
}
|
||||
|
||||
for constant in pallet.constants.values_mut() {
|
||||
types.push(&mut constant.ty);
|
||||
}
|
||||
}
|
||||
|
||||
// collect extrinsic type_ids
|
||||
for ty in [
|
||||
&mut metadata.extrinsic.extra_ty,
|
||||
&mut metadata.extrinsic.address_ty,
|
||||
&mut metadata.extrinsic.signature_ty,
|
||||
&mut metadata.extrinsic.call_ty,
|
||||
] {
|
||||
types.push(ty);
|
||||
}
|
||||
|
||||
for signed in &mut metadata.extrinsic.transaction_extensions {
|
||||
types.push(&mut signed.extra_ty);
|
||||
types.push(&mut signed.additional_ty);
|
||||
}
|
||||
|
||||
types.push(&mut metadata.runtime_ty);
|
||||
|
||||
// collect runtime_api_types
|
||||
for api in metadata.apis.values_mut() {
|
||||
for method in api.methods.values_mut() {
|
||||
for input in &mut method.inputs.iter_mut() {
|
||||
types.push(&mut input.ty);
|
||||
}
|
||||
types.push(&mut method.output_ty);
|
||||
}
|
||||
}
|
||||
|
||||
types.into_iter()
|
||||
}
|
||||
|
||||
/// Look for a type ID anywhere that we can be given back, ie in constants, storage, extrinsics or runtime API return types.
|
||||
/// This will recurse deeply into those type IDs to find them.
|
||||
pub fn keep_outer_enum<F, G>(
|
||||
metadata: &Metadata,
|
||||
pallets_filter: &mut F,
|
||||
runtime_apis_filter: &mut G,
|
||||
) -> impl FnMut(u32) -> bool
|
||||
where
|
||||
F: FnMut(&str) -> bool,
|
||||
G: FnMut(&str) -> bool,
|
||||
{
|
||||
let mut type_set = TypeSet::new();
|
||||
for pallet in metadata.pallets.values() {
|
||||
if pallets_filter(&pallet.name) {
|
||||
type_set.collect_pallet_types(pallet, metadata);
|
||||
}
|
||||
}
|
||||
for api in metadata.apis.values() {
|
||||
if runtime_apis_filter(&api.name) {
|
||||
type_set.collect_runtime_api_types(metadata, api);
|
||||
}
|
||||
}
|
||||
type_set.collect_extrinsic_types(&metadata.extrinsic);
|
||||
move |type_id| type_set.contains(type_id)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Metadata;
|
||||
use codec::Decode;
|
||||
use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed};
|
||||
use std::{fs, path::Path};
|
||||
|
||||
fn load_metadata() -> Metadata {
|
||||
load_metadata_custom("../artifacts/polkadot_metadata_full.scale")
|
||||
}
|
||||
|
||||
fn load_metadata_custom(path: impl AsRef<Path>) -> Metadata {
|
||||
let bytes = fs::read(path).expect("Cannot read metadata blob");
|
||||
let meta: RuntimeMetadataPrefixed =
|
||||
Decode::decode(&mut &*bytes).expect("Cannot decode scale metadata");
|
||||
|
||||
match meta.1 {
|
||||
RuntimeMetadata::V14(v14) => v14.try_into().unwrap(),
|
||||
RuntimeMetadata::V15(v15) => v15.try_into().unwrap(),
|
||||
_ => panic!("Unsupported metadata version {:?}", meta.1),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retain_one_pallet() {
|
||||
let metadata_cache = load_metadata();
|
||||
|
||||
// Retain one pallet at a time ensuring the test does not panic.
|
||||
for pallet in metadata_cache.pallets() {
|
||||
let original_meta = metadata_cache.clone();
|
||||
let mut metadata = metadata_cache.clone();
|
||||
retain_metadata(
|
||||
&mut metadata,
|
||||
|pallet_name| pallet_name == pallet.name(),
|
||||
|_| true,
|
||||
);
|
||||
|
||||
assert_eq!(metadata.pallets.len(), 1);
|
||||
assert_eq!(
|
||||
&*metadata.pallets.get_by_index(0).unwrap().name,
|
||||
pallet.name()
|
||||
);
|
||||
|
||||
assert!(
|
||||
metadata.types.types.len() < original_meta.types.types.len(),
|
||||
"Stripped metadata must have less retained types than the non-stripped one: stripped amount {}, original amount {}",
|
||||
metadata.types.types.len(), original_meta.types.types.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retain_one_runtime_api() {
|
||||
let metadata_cache = load_metadata();
|
||||
|
||||
// Retain one runtime API at a time ensuring the test does not panic.
|
||||
for runtime_api in metadata_cache.runtime_api_traits() {
|
||||
let mut metadata = metadata_cache.clone();
|
||||
retain_metadata(
|
||||
&mut metadata,
|
||||
|_| true,
|
||||
|runtime_api_name| runtime_api_name == runtime_api.name(),
|
||||
);
|
||||
|
||||
assert_eq!(metadata.apis.len(), 1);
|
||||
assert_eq!(
|
||||
&*metadata.apis.get_by_index(0).unwrap().name,
|
||||
runtime_api.name()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_1659() {
|
||||
let full_metadata = load_metadata_custom("../artifacts/regressions/1659.scale");
|
||||
// Strip metadata to the pallets as described in the issue.
|
||||
let mut stripped_metadata = full_metadata.clone();
|
||||
retain_metadata(
|
||||
&mut stripped_metadata,
|
||||
{
|
||||
let set = "Balances,Timestamp,Contracts,ContractsEvm,System"
|
||||
.split(",")
|
||||
.collect::<BTreeSet<&str>>();
|
||||
move |s| set.contains(&s)
|
||||
},
|
||||
|_| true,
|
||||
);
|
||||
|
||||
// check that call_enum did not change as it is referenced inside runtime_api
|
||||
assert_eq!(
|
||||
stripped_metadata.type_hash(stripped_metadata.outer_enums.call_enum_ty),
|
||||
full_metadata.type_hash(full_metadata.outer_enums.call_enum_ty)
|
||||
);
|
||||
|
||||
// check that event_num did not change as it is referenced inside runtime_api
|
||||
assert_eq!(
|
||||
stripped_metadata.type_hash(stripped_metadata.outer_enums.event_enum_ty),
|
||||
full_metadata.type_hash(full_metadata.outer_enums.event_enum_ty)
|
||||
);
|
||||
}
|
||||
}
|
||||
+141
-198
@@ -6,15 +6,13 @@
|
||||
|
||||
use crate::{
|
||||
CustomMetadata, CustomValueMetadata, ExtrinsicMetadata, Metadata, PalletMetadata,
|
||||
RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata, StorageEntryType,
|
||||
PalletViewFunctionMetadata, RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata,
|
||||
StorageEntryType,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
use hashbrown::HashMap;
|
||||
use outer_enum_hashes::OuterEnumHashes;
|
||||
use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, TypeDefVariant, Variant};
|
||||
|
||||
pub mod outer_enum_hashes;
|
||||
|
||||
// The number of bytes our `hash` function produces.
|
||||
pub(crate) const HASH_LEN: usize = 32;
|
||||
pub type Hash = [u8; HASH_LEN];
|
||||
@@ -79,7 +77,6 @@ fn get_field_hash(
|
||||
registry: &PortableRegistry,
|
||||
field: &Field<PortableForm>,
|
||||
cache: &mut HashMap<u32, CachedHash>,
|
||||
outer_enum_hashes: &OuterEnumHashes,
|
||||
) -> Hash {
|
||||
let field_name_bytes = match &field.name {
|
||||
Some(name) => hash(name.as_bytes()),
|
||||
@@ -88,7 +85,7 @@ fn get_field_hash(
|
||||
|
||||
concat_and_hash2(
|
||||
&field_name_bytes,
|
||||
&get_type_hash_recurse(registry, field.ty.id, cache, outer_enum_hashes),
|
||||
&get_type_hash_recurse(registry, field.ty.id, cache),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -97,16 +94,12 @@ fn get_variant_hash(
|
||||
registry: &PortableRegistry,
|
||||
var: &Variant<PortableForm>,
|
||||
cache: &mut HashMap<u32, CachedHash>,
|
||||
outer_enum_hashes: &OuterEnumHashes,
|
||||
) -> Hash {
|
||||
let variant_name_bytes = hash(var.name.as_bytes());
|
||||
let variant_field_bytes = var.fields.iter().fold([0u8; HASH_LEN], |bytes, field| {
|
||||
// EncodeAsType and DecodeAsType don't care about variant field ordering,
|
||||
// so XOR the fields to ensure that it doesn't matter.
|
||||
xor(
|
||||
bytes,
|
||||
get_field_hash(registry, field, cache, outer_enum_hashes),
|
||||
)
|
||||
xor(bytes, get_field_hash(registry, field, cache))
|
||||
});
|
||||
|
||||
concat_and_hash2(&variant_name_bytes, &variant_field_bytes)
|
||||
@@ -117,7 +110,6 @@ fn get_type_def_variant_hash(
|
||||
variant: &TypeDefVariant<PortableForm>,
|
||||
only_these_variants: Option<&[&str]>,
|
||||
cache: &mut HashMap<u32, CachedHash>,
|
||||
outer_enum_hashes: &OuterEnumHashes,
|
||||
) -> Hash {
|
||||
let variant_id_bytes = [TypeBeingHashed::Variant as u8; HASH_LEN];
|
||||
let variant_field_bytes = variant.variants.iter().fold([0u8; HASH_LEN], |bytes, var| {
|
||||
@@ -128,10 +120,7 @@ fn get_type_def_variant_hash(
|
||||
.map(|only_these_variants| only_these_variants.contains(&var.name.as_str()))
|
||||
.unwrap_or(true);
|
||||
if should_hash {
|
||||
xor(
|
||||
bytes,
|
||||
get_variant_hash(registry, var, cache, outer_enum_hashes),
|
||||
)
|
||||
xor(bytes, get_variant_hash(registry, var, cache))
|
||||
} else {
|
||||
bytes
|
||||
}
|
||||
@@ -144,7 +133,6 @@ fn get_type_def_hash(
|
||||
registry: &PortableRegistry,
|
||||
ty_def: &TypeDef<PortableForm>,
|
||||
cache: &mut HashMap<u32, CachedHash>,
|
||||
outer_enum_hashes: &OuterEnumHashes,
|
||||
) -> Hash {
|
||||
match ty_def {
|
||||
TypeDef::Composite(composite) => {
|
||||
@@ -156,19 +144,14 @@ fn get_type_def_hash(
|
||||
.fold([0u8; HASH_LEN], |bytes, field| {
|
||||
// With EncodeAsType and DecodeAsType we no longer care which order the fields are in,
|
||||
// as long as all of the names+types are there. XOR to not care about ordering.
|
||||
xor(
|
||||
bytes,
|
||||
get_field_hash(registry, field, cache, outer_enum_hashes),
|
||||
)
|
||||
xor(bytes, get_field_hash(registry, field, cache))
|
||||
});
|
||||
concat_and_hash2(&composite_id_bytes, &composite_field_bytes)
|
||||
}
|
||||
TypeDef::Variant(variant) => {
|
||||
get_type_def_variant_hash(registry, variant, None, cache, outer_enum_hashes)
|
||||
}
|
||||
TypeDef::Variant(variant) => get_type_def_variant_hash(registry, variant, None, cache),
|
||||
TypeDef::Sequence(sequence) => concat_and_hash2(
|
||||
&[TypeBeingHashed::Sequence as u8; HASH_LEN],
|
||||
&get_type_hash_recurse(registry, sequence.type_param.id, cache, outer_enum_hashes),
|
||||
&get_type_hash_recurse(registry, sequence.type_param.id, cache),
|
||||
),
|
||||
TypeDef::Array(array) => {
|
||||
// Take length into account too; different length must lead to different hash.
|
||||
@@ -180,16 +163,13 @@ fn get_type_def_hash(
|
||||
};
|
||||
concat_and_hash2(
|
||||
&array_id_bytes,
|
||||
&get_type_hash_recurse(registry, array.type_param.id, cache, outer_enum_hashes),
|
||||
&get_type_hash_recurse(registry, array.type_param.id, cache),
|
||||
)
|
||||
}
|
||||
TypeDef::Tuple(tuple) => {
|
||||
let mut bytes = hash(&[TypeBeingHashed::Tuple as u8]);
|
||||
for field in &tuple.fields {
|
||||
bytes = concat_and_hash2(
|
||||
&bytes,
|
||||
&get_type_hash_recurse(registry, field.id, cache, outer_enum_hashes),
|
||||
);
|
||||
bytes = concat_and_hash2(&bytes, &get_type_hash_recurse(registry, field.id, cache));
|
||||
}
|
||||
bytes
|
||||
}
|
||||
@@ -199,12 +179,12 @@ fn get_type_def_hash(
|
||||
}
|
||||
TypeDef::Compact(compact) => concat_and_hash2(
|
||||
&[TypeBeingHashed::Compact as u8; HASH_LEN],
|
||||
&get_type_hash_recurse(registry, compact.type_param.id, cache, outer_enum_hashes),
|
||||
&get_type_hash_recurse(registry, compact.type_param.id, cache),
|
||||
),
|
||||
TypeDef::BitSequence(bitseq) => concat_and_hash3(
|
||||
&[TypeBeingHashed::BitSequence as u8; HASH_LEN],
|
||||
&get_type_hash_recurse(registry, bitseq.bit_order_type.id, cache, outer_enum_hashes),
|
||||
&get_type_hash_recurse(registry, bitseq.bit_store_type.id, cache, outer_enum_hashes),
|
||||
&get_type_hash_recurse(registry, bitseq.bit_order_type.id, cache),
|
||||
&get_type_hash_recurse(registry, bitseq.bit_store_type.id, cache),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -235,12 +215,8 @@ impl CachedHash {
|
||||
///
|
||||
/// The reason for this unintuitive behavior is that we sometimes want to trim the outer enum types
|
||||
/// beforehand to only include certain pallets, which affects their hash values.
|
||||
pub fn get_type_hash(
|
||||
registry: &PortableRegistry,
|
||||
id: u32,
|
||||
outer_enum_hashes: &OuterEnumHashes,
|
||||
) -> Hash {
|
||||
get_type_hash_recurse(registry, id, &mut HashMap::new(), outer_enum_hashes)
|
||||
pub fn get_type_hash(registry: &PortableRegistry, id: u32) -> Hash {
|
||||
get_type_hash_recurse(registry, id, &mut HashMap::new())
|
||||
}
|
||||
|
||||
/// Obtain the hash representation of a `scale_info::Type` identified by id.
|
||||
@@ -248,13 +224,7 @@ fn get_type_hash_recurse(
|
||||
registry: &PortableRegistry,
|
||||
id: u32,
|
||||
cache: &mut HashMap<u32, CachedHash>,
|
||||
outer_enum_hashes: &OuterEnumHashes,
|
||||
) -> Hash {
|
||||
// If the type is part of precomputed outer enum hashes, the respective hash is used instead:
|
||||
if let Some(hash) = outer_enum_hashes.resolve(id) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Guard against recursive types, with a 2 step caching approach:
|
||||
// if the cache has an entry for the id, just return a hash derived from it.
|
||||
// if the type has not been seen yet, mark it with `CachedHash::Recursive` in the cache and proceed to `get_type_def_hash()`.
|
||||
@@ -275,22 +245,17 @@ fn get_type_hash_recurse(
|
||||
let ty = registry
|
||||
.resolve(id)
|
||||
.expect("Type ID provided by the metadata is registered; qed");
|
||||
let type_hash = get_type_def_hash(registry, &ty.type_def, cache, outer_enum_hashes);
|
||||
let type_hash = get_type_def_hash(registry, &ty.type_def, cache);
|
||||
cache.insert(id, CachedHash::Hash(type_hash));
|
||||
type_hash
|
||||
}
|
||||
|
||||
/// Obtain the hash representation of a `frame_metadata::v15::ExtrinsicMetadata`.
|
||||
fn get_extrinsic_hash(
|
||||
registry: &PortableRegistry,
|
||||
extrinsic: &ExtrinsicMetadata,
|
||||
outer_enum_hashes: &OuterEnumHashes,
|
||||
) -> Hash {
|
||||
fn get_extrinsic_hash(registry: &PortableRegistry, extrinsic: &ExtrinsicMetadata) -> Hash {
|
||||
// Get the hashes of the extrinsic type.
|
||||
let address_hash = get_type_hash(registry, extrinsic.address_ty, outer_enum_hashes);
|
||||
let address_hash = get_type_hash(registry, extrinsic.address_ty);
|
||||
// The `RuntimeCall` type is intentionally omitted and hashed by the outer enums instead.
|
||||
let signature_hash = get_type_hash(registry, extrinsic.signature_ty, outer_enum_hashes);
|
||||
let extra_hash = get_type_hash(registry, extrinsic.extra_ty, outer_enum_hashes);
|
||||
let signature_hash = get_type_hash(registry, extrinsic.signature_ty);
|
||||
|
||||
// Supported versions are just u8s and we will likely never have more than 32 of these, so put them into
|
||||
// an array of u8s and panic if more than 32.
|
||||
@@ -303,10 +268,9 @@ fn get_extrinsic_hash(
|
||||
a
|
||||
};
|
||||
|
||||
let mut bytes = concat_and_hash4(
|
||||
let mut bytes = concat_and_hash3(
|
||||
&address_hash,
|
||||
&signature_hash,
|
||||
&extra_hash,
|
||||
&supported_extrinsic_versions,
|
||||
);
|
||||
|
||||
@@ -314,8 +278,8 @@ fn get_extrinsic_hash(
|
||||
bytes = concat_and_hash4(
|
||||
&bytes,
|
||||
&hash(signed_extension.identifier.as_bytes()),
|
||||
&get_type_hash(registry, signed_extension.extra_ty, outer_enum_hashes),
|
||||
&get_type_hash(registry, signed_extension.additional_ty, outer_enum_hashes),
|
||||
&get_type_hash(registry, signed_extension.extra_ty),
|
||||
&get_type_hash(registry, signed_extension.additional_ty),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -323,11 +287,7 @@ fn get_extrinsic_hash(
|
||||
}
|
||||
|
||||
/// Get the hash corresponding to a single storage entry.
|
||||
fn get_storage_entry_hash(
|
||||
registry: &PortableRegistry,
|
||||
entry: &StorageEntryMetadata,
|
||||
outer_enum_hashes: &OuterEnumHashes,
|
||||
) -> Hash {
|
||||
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.
|
||||
@@ -336,9 +296,7 @@ fn get_storage_entry_hash(
|
||||
);
|
||||
|
||||
match &entry.entry_type {
|
||||
StorageEntryType::Plain(ty) => {
|
||||
concat_and_hash2(&bytes, &get_type_hash(registry, *ty, outer_enum_hashes))
|
||||
}
|
||||
StorageEntryType::Plain(ty) => concat_and_hash2(&bytes, &get_type_hash(registry, *ty)),
|
||||
StorageEntryType::Map {
|
||||
hashers,
|
||||
key_ty,
|
||||
@@ -350,83 +308,18 @@ fn get_storage_entry_hash(
|
||||
}
|
||||
concat_and_hash3(
|
||||
&bytes,
|
||||
&get_type_hash(registry, *key_ty, outer_enum_hashes),
|
||||
&get_type_hash(registry, *value_ty, outer_enum_hashes),
|
||||
&get_type_hash(registry, *key_ty),
|
||||
&get_type_hash(registry, *value_ty),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the hash corresponding to a single runtime API method.
|
||||
fn get_runtime_method_hash(
|
||||
registry: &PortableRegistry,
|
||||
trait_name: &str,
|
||||
method_metadata: &RuntimeApiMethodMetadata,
|
||||
outer_enum_hashes: &OuterEnumHashes,
|
||||
) -> Hash {
|
||||
// The trait name is part of the runtime API call that is being
|
||||
// generated for this method. Therefore the trait name is strongly
|
||||
// connected to the method in the same way as a parameter is
|
||||
// to the method.
|
||||
let mut bytes = concat_and_hash2(
|
||||
&hash(trait_name.as_bytes()),
|
||||
&hash(method_metadata.name.as_bytes()),
|
||||
);
|
||||
|
||||
for input in &method_metadata.inputs {
|
||||
bytes = concat_and_hash3(
|
||||
&bytes,
|
||||
&hash(input.name.as_bytes()),
|
||||
&get_type_hash(registry, input.ty, outer_enum_hashes),
|
||||
);
|
||||
}
|
||||
|
||||
bytes = concat_and_hash2(
|
||||
&bytes,
|
||||
&get_type_hash(registry, method_metadata.output_ty, outer_enum_hashes),
|
||||
);
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Obtain the hash of all of a runtime API trait, including all of its methods.
|
||||
pub fn get_runtime_trait_hash(
|
||||
trait_metadata: RuntimeApiMetadata,
|
||||
outer_enum_hashes: &OuterEnumHashes,
|
||||
) -> Hash {
|
||||
let trait_name = &*trait_metadata.inner.name;
|
||||
let method_bytes = trait_metadata
|
||||
.methods()
|
||||
.fold([0u8; HASH_LEN], |bytes, method_metadata| {
|
||||
// We don't care what order the trait methods exist in, and want the hash to
|
||||
// be identical regardless. For this, we can just XOR the hashes for each method
|
||||
// together; we'll get the same output whichever order they are XOR'd together in,
|
||||
// so long as each individual method is the same.
|
||||
xor(
|
||||
bytes,
|
||||
get_runtime_method_hash(
|
||||
trait_metadata.types,
|
||||
trait_name,
|
||||
method_metadata,
|
||||
outer_enum_hashes,
|
||||
),
|
||||
)
|
||||
});
|
||||
|
||||
concat_and_hash2(&hash(trait_name.as_bytes()), &method_bytes)
|
||||
}
|
||||
|
||||
fn get_custom_metadata_hash(
|
||||
custom_metadata: &CustomMetadata,
|
||||
outer_enum_hashes: &OuterEnumHashes,
|
||||
) -> Hash {
|
||||
fn get_custom_metadata_hash(custom_metadata: &CustomMetadata) -> Hash {
|
||||
custom_metadata
|
||||
.iter()
|
||||
.fold([0u8; HASH_LEN], |bytes, custom_value| {
|
||||
xor(
|
||||
bytes,
|
||||
get_custom_value_hash(&custom_value, outer_enum_hashes),
|
||||
)
|
||||
xor(bytes, get_custom_value_hash(&custom_value))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -434,21 +327,14 @@ fn get_custom_metadata_hash(
|
||||
///
|
||||
/// If the `custom_value` has a type id that is not present in the metadata,
|
||||
/// only the name and bytes are used for hashing.
|
||||
pub fn get_custom_value_hash(
|
||||
custom_value: &CustomValueMetadata,
|
||||
outer_enum_hashes: &OuterEnumHashes,
|
||||
) -> Hash {
|
||||
pub fn get_custom_value_hash(custom_value: &CustomValueMetadata) -> Hash {
|
||||
let name_hash = hash(custom_value.name.as_bytes());
|
||||
if custom_value.types.resolve(custom_value.type_id()).is_none() {
|
||||
hash(&name_hash)
|
||||
} else {
|
||||
concat_and_hash2(
|
||||
&name_hash,
|
||||
&get_type_hash(
|
||||
custom_value.types,
|
||||
custom_value.type_id(),
|
||||
outer_enum_hashes,
|
||||
),
|
||||
&get_type_hash(custom_value.types, custom_value.type_id()),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -457,7 +343,7 @@ pub fn get_custom_value_hash(
|
||||
pub fn get_storage_hash(pallet: &PalletMetadata, entry_name: &str) -> Option<Hash> {
|
||||
let storage = pallet.storage()?;
|
||||
let entry = storage.entry_by_name(entry_name)?;
|
||||
let hash = get_storage_entry_hash(pallet.types, entry, &OuterEnumHashes::empty());
|
||||
let hash = get_storage_entry_hash(pallet.types, entry);
|
||||
Some(hash)
|
||||
}
|
||||
|
||||
@@ -466,7 +352,7 @@ pub fn get_constant_hash(pallet: &PalletMetadata, constant_name: &str) -> Option
|
||||
let constant = pallet.constant_by_name(constant_name)?;
|
||||
|
||||
// We only need to check that the type of the constant asked for matches.
|
||||
let bytes = get_type_hash(pallet.types, constant.ty, &OuterEnumHashes::empty());
|
||||
let bytes = get_type_hash(pallet.types, constant.ty);
|
||||
Some(bytes)
|
||||
}
|
||||
|
||||
@@ -475,42 +361,102 @@ pub fn get_call_hash(pallet: &PalletMetadata, call_name: &str) -> Option<Hash> {
|
||||
let call_variant = pallet.call_variant_by_name(call_name)?;
|
||||
|
||||
// hash the specific variant representing the call we are interested in.
|
||||
let hash = get_variant_hash(
|
||||
pallet.types,
|
||||
call_variant,
|
||||
&mut HashMap::new(),
|
||||
&OuterEnumHashes::empty(),
|
||||
);
|
||||
let hash = get_variant_hash(pallet.types, call_variant, &mut HashMap::new());
|
||||
Some(hash)
|
||||
}
|
||||
|
||||
/// Obtain the hash of a specific runtime API function, or an error if it's not found.
|
||||
pub fn get_runtime_api_hash(runtime_apis: &RuntimeApiMetadata, method_name: &str) -> Option<Hash> {
|
||||
let trait_name = &*runtime_apis.inner.name;
|
||||
let method_metadata = runtime_apis.method_by_name(method_name)?;
|
||||
/// Obtain the hash of a specific runtime API method, or an error if it's not found.
|
||||
pub fn get_runtime_api_hash(runtime_api: &RuntimeApiMethodMetadata) -> Hash {
|
||||
let registry = runtime_api.types;
|
||||
|
||||
Some(get_runtime_method_hash(
|
||||
runtime_apis.types,
|
||||
trait_name,
|
||||
method_metadata,
|
||||
&OuterEnumHashes::empty(),
|
||||
))
|
||||
// The trait name is part of the runtime API call that is being
|
||||
// generated for this method. Therefore the trait name is strongly
|
||||
// connected to the method in the same way as a parameter is
|
||||
// to the method.
|
||||
let mut bytes = concat_and_hash2(
|
||||
&hash(runtime_api.trait_name.as_bytes()),
|
||||
&hash(runtime_api.name().as_bytes()),
|
||||
);
|
||||
|
||||
for input in runtime_api.inputs() {
|
||||
bytes = concat_and_hash3(
|
||||
&bytes,
|
||||
&hash(input.name.as_bytes()),
|
||||
&get_type_hash(registry, input.ty),
|
||||
);
|
||||
}
|
||||
|
||||
bytes = concat_and_hash2(&bytes, &get_type_hash(registry, runtime_api.output_ty()));
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Obtain the hash of all of a runtime API trait, including all of its methods.
|
||||
pub fn get_runtime_apis_hash(trait_metadata: RuntimeApiMetadata) -> Hash {
|
||||
// Each API is already hashed considering the trait name, so we don't need
|
||||
// to consider thr trait name again here.
|
||||
trait_metadata
|
||||
.methods()
|
||||
.fold([0u8; HASH_LEN], |bytes, method_metadata| {
|
||||
// We don't care what order the trait methods exist in, and want the hash to
|
||||
// be identical regardless. For this, we can just XOR the hashes for each method
|
||||
// together; we'll get the same output whichever order they are XOR'd together in,
|
||||
// so long as each individual method is the same.
|
||||
xor(bytes, get_runtime_api_hash(&method_metadata))
|
||||
})
|
||||
}
|
||||
|
||||
/// Obtain the hash of a specific pallet view function, or an error if it's not found.
|
||||
pub fn get_pallet_view_function_hash(view_function: &PalletViewFunctionMetadata) -> Hash {
|
||||
let registry = view_function.types;
|
||||
|
||||
// The Query ID is `twox_128(pallet_name) ++ twox_128("fn_name(fnarg_types) -> return_ty")`.
|
||||
let mut bytes = view_function.query_id();
|
||||
|
||||
// This only takes type _names_ into account, so we beef this up by combining with actual
|
||||
// type hashes, in a similar approach to runtime APIs..
|
||||
for input in view_function.inputs() {
|
||||
bytes = concat_and_hash3(
|
||||
&bytes,
|
||||
&hash(input.name.as_bytes()),
|
||||
&get_type_hash(registry, input.ty),
|
||||
);
|
||||
}
|
||||
|
||||
bytes = concat_and_hash2(&bytes, &get_type_hash(registry, view_function.output_ty()));
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Obtain the hash of all of the view functions in a pallet, including all of its methods.
|
||||
fn get_pallet_view_functions_hash(pallet_metadata: &PalletMetadata) -> Hash {
|
||||
// Each API is already hashed considering the trait name, so we don't need
|
||||
// to consider thr trait name again here.
|
||||
pallet_metadata
|
||||
.view_functions()
|
||||
.fold([0u8; HASH_LEN], |bytes, method_metadata| {
|
||||
// We don't care what order the view functions are declared in, and want the hash to
|
||||
// be identical regardless. For this, we can just XOR the hashes for each method
|
||||
// together; we'll get the same output whichever order they are XOR'd together in,
|
||||
// so long as each individual method is the same.
|
||||
xor(bytes, get_pallet_view_function_hash(&method_metadata))
|
||||
})
|
||||
}
|
||||
|
||||
/// Obtain the hash representation of a `frame_metadata::v15::PalletMetadata`.
|
||||
pub fn get_pallet_hash(pallet: PalletMetadata, outer_enum_hashes: &OuterEnumHashes) -> Hash {
|
||||
pub fn get_pallet_hash(pallet: PalletMetadata) -> Hash {
|
||||
let registry = pallet.types;
|
||||
|
||||
let call_bytes = match pallet.call_ty_id() {
|
||||
Some(calls) => get_type_hash(registry, calls, outer_enum_hashes),
|
||||
Some(calls) => get_type_hash(registry, calls),
|
||||
None => [0u8; HASH_LEN],
|
||||
};
|
||||
let event_bytes = match pallet.event_ty_id() {
|
||||
Some(event) => get_type_hash(registry, event, outer_enum_hashes),
|
||||
Some(event) => get_type_hash(registry, event),
|
||||
None => [0u8; HASH_LEN],
|
||||
};
|
||||
let error_bytes = match pallet.error_ty_id() {
|
||||
Some(error) => get_type_hash(registry, error, outer_enum_hashes),
|
||||
Some(error) => get_type_hash(registry, error),
|
||||
None => [0u8; HASH_LEN],
|
||||
};
|
||||
let constant_bytes = pallet.constants().fold([0u8; HASH_LEN], |bytes, constant| {
|
||||
@@ -518,7 +464,7 @@ pub fn get_pallet_hash(pallet: PalletMetadata, outer_enum_hashes: &OuterEnumHash
|
||||
// of (constantName, constantType) to make the order we see them irrelevant.
|
||||
let constant_hash = concat_and_hash2(
|
||||
&hash(constant.name.as_bytes()),
|
||||
&get_type_hash(registry, constant.ty(), outer_enum_hashes),
|
||||
&get_type_hash(registry, constant.ty()),
|
||||
);
|
||||
xor(bytes, constant_hash)
|
||||
});
|
||||
@@ -531,23 +477,22 @@ pub fn get_pallet_hash(pallet: PalletMetadata, outer_enum_hashes: &OuterEnumHash
|
||||
.fold([0u8; HASH_LEN], |bytes, entry| {
|
||||
// We don't care what order the storage entries occur in, so XOR them together
|
||||
// to make the order irrelevant.
|
||||
xor(
|
||||
bytes,
|
||||
get_storage_entry_hash(registry, entry, outer_enum_hashes),
|
||||
)
|
||||
xor(bytes, get_storage_entry_hash(registry, entry))
|
||||
});
|
||||
concat_and_hash2(&prefix_hash, &entries_hash)
|
||||
}
|
||||
None => [0u8; HASH_LEN],
|
||||
};
|
||||
let view_functions_bytes = get_pallet_view_functions_hash(&pallet);
|
||||
|
||||
// Hash all of the above together:
|
||||
concat_and_hash5(
|
||||
concat_and_hash6(
|
||||
&call_bytes,
|
||||
&event_bytes,
|
||||
&error_bytes,
|
||||
&constant_bytes,
|
||||
&storage_bytes,
|
||||
&view_functions_bytes,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -597,14 +542,6 @@ impl<'a> MetadataHasher<'a> {
|
||||
pub fn hash(&self) -> Hash {
|
||||
let metadata = self.metadata;
|
||||
|
||||
// Get the hashes of outer enums, considering only `specific_pallets` (if any are set).
|
||||
// If any of the typed that represent outer enums are encountered later, hashes from `top_level_enum_hashes` can be substituted.
|
||||
let outer_enum_hashes = OuterEnumHashes::new(
|
||||
metadata,
|
||||
self.specific_pallets.as_deref(),
|
||||
self.specific_runtime_apis.as_deref(),
|
||||
);
|
||||
|
||||
let pallet_hash = metadata.pallets().fold([0u8; HASH_LEN], |bytes, pallet| {
|
||||
// If specific pallets are given, only include this pallet if it is in the specific pallets.
|
||||
let should_hash = self
|
||||
@@ -615,7 +552,7 @@ impl<'a> MetadataHasher<'a> {
|
||||
// We don't care what order the pallets are seen in, so XOR their
|
||||
// hashes together to be order independent.
|
||||
if should_hash {
|
||||
xor(bytes, get_pallet_hash(pallet, &outer_enum_hashes))
|
||||
xor(bytes, get_pallet_hash(pallet))
|
||||
} else {
|
||||
bytes
|
||||
}
|
||||
@@ -633,27 +570,30 @@ impl<'a> MetadataHasher<'a> {
|
||||
// We don't care what order the runtime APIs are seen in, so XOR their
|
||||
// hashes together to be order independent.
|
||||
if should_hash {
|
||||
xor(bytes, get_runtime_trait_hash(api, &outer_enum_hashes))
|
||||
xor(bytes, get_runtime_apis_hash(api))
|
||||
} else {
|
||||
bytes
|
||||
}
|
||||
});
|
||||
|
||||
let extrinsic_hash =
|
||||
get_extrinsic_hash(&metadata.types, &metadata.extrinsic, &outer_enum_hashes);
|
||||
let runtime_hash =
|
||||
get_type_hash(&metadata.types, metadata.runtime_ty(), &outer_enum_hashes);
|
||||
let outer_enums_hash = concat_and_hash3(
|
||||
&get_type_hash(&metadata.types, metadata.outer_enums.call_enum_ty),
|
||||
&get_type_hash(&metadata.types, metadata.outer_enums.event_enum_ty),
|
||||
&get_type_hash(&metadata.types, metadata.outer_enums.error_enum_ty),
|
||||
);
|
||||
|
||||
let extrinsic_hash = get_extrinsic_hash(&metadata.types, &metadata.extrinsic);
|
||||
|
||||
let custom_values_hash = self
|
||||
.include_custom_values
|
||||
.then(|| get_custom_metadata_hash(&metadata.custom(), &outer_enum_hashes))
|
||||
.then(|| get_custom_metadata_hash(&metadata.custom()))
|
||||
.unwrap_or_default();
|
||||
|
||||
concat_and_hash6(
|
||||
concat_and_hash5(
|
||||
&pallet_hash,
|
||||
&apis_hash,
|
||||
&outer_enums_hash,
|
||||
&extrinsic_hash,
|
||||
&runtime_hash,
|
||||
&outer_enum_hashes.combined_hash(),
|
||||
&custom_values_hash,
|
||||
)
|
||||
}
|
||||
@@ -886,11 +826,10 @@ mod tests {
|
||||
let registry: PortableRegistry = registry.into();
|
||||
|
||||
let mut cache = HashMap::new();
|
||||
let ignored_enums = &OuterEnumHashes::empty();
|
||||
|
||||
let a_hash = get_type_hash_recurse(®istry, a_type_id, &mut cache, ignored_enums);
|
||||
let a_hash2 = get_type_hash_recurse(®istry, a_type_id, &mut cache, ignored_enums);
|
||||
let b_hash = get_type_hash_recurse(®istry, b_type_id, &mut cache, ignored_enums);
|
||||
let a_hash = get_type_hash_recurse(®istry, a_type_id, &mut cache);
|
||||
let a_hash2 = get_type_hash_recurse(®istry, a_type_id, &mut cache);
|
||||
let b_hash = get_type_hash_recurse(®istry, b_type_id, &mut cache);
|
||||
|
||||
let CachedHash::Hash(a_cache_hash) = cache[&a_type_id] else {
|
||||
panic!()
|
||||
@@ -1123,7 +1062,7 @@ mod tests {
|
||||
PalletEventMetadata, PalletStorageMetadata, StorageEntryMetadata, StorageEntryModifier,
|
||||
};
|
||||
|
||||
fn metadata_with_pallet_events() -> Metadata {
|
||||
fn metadata_with_pallet_events() -> v15::RuntimeMetadataV15 {
|
||||
#[allow(dead_code)]
|
||||
#[derive(scale_info::TypeInfo)]
|
||||
struct FirstEvent {
|
||||
@@ -1236,20 +1175,24 @@ mod tests {
|
||||
map: Default::default(),
|
||||
},
|
||||
)
|
||||
.try_into()
|
||||
.expect("can build valid metadata")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_comparison_trimmed_metadata() {
|
||||
use subxt_utils_stripmetadata::StripMetadata;
|
||||
|
||||
// trim the metadata:
|
||||
let metadata = metadata_with_pallet_events();
|
||||
let trimmed_metadata = {
|
||||
let mut m = metadata.clone();
|
||||
m.retain(|e| e == "First", |_| true);
|
||||
m.strip_metadata(|e| e == "First", |_| true);
|
||||
m
|
||||
};
|
||||
|
||||
// Now convert it into our inner repr:
|
||||
let metadata = Metadata::try_from(metadata).unwrap();
|
||||
let trimmed_metadata = Metadata::try_from(trimmed_metadata).unwrap();
|
||||
|
||||
// test that the hashes are the same:
|
||||
let hash = MetadataHasher::new(&metadata)
|
||||
.only_these_pallets(&["First"])
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
//! Hash representations of the `frame_metadata::v15::OuterEnums`.
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use scale_info::{PortableRegistry, TypeDef};
|
||||
|
||||
use crate::{
|
||||
utils::{
|
||||
retain,
|
||||
validation::{get_type_def_variant_hash, get_type_hash},
|
||||
},
|
||||
Metadata,
|
||||
};
|
||||
|
||||
use super::{concat_and_hash3, Hash, HASH_LEN};
|
||||
|
||||
/// Hash representations of the `frame_metadata::v15::OuterEnums`.
|
||||
pub struct OuterEnumHashes {
|
||||
call_hash: (u32, Hash),
|
||||
error_hash: (u32, Hash),
|
||||
event_hash: (u32, Hash),
|
||||
}
|
||||
|
||||
impl OuterEnumHashes {
|
||||
/// Constructs new `OuterEnumHashes` from metadata. If `only_these_variants` is set, the enums are stripped down to only these variants, before their hashes are calculated.
|
||||
pub fn new(
|
||||
metadata: &Metadata,
|
||||
specific_pallets: Option<&[&str]>,
|
||||
specific_runtimes: Option<&[&str]>,
|
||||
) -> Self {
|
||||
let filter = |names: Option<&[&str]>, name: &str| match names {
|
||||
Some(names) => names.contains(&name),
|
||||
None => true,
|
||||
};
|
||||
let mut check_enum_type_id = retain::keep_outer_enum(
|
||||
metadata,
|
||||
&mut |name| filter(specific_pallets, name),
|
||||
&mut |name| filter(specific_runtimes, name),
|
||||
);
|
||||
|
||||
let variants = |filter: bool| {
|
||||
if !filter {
|
||||
specific_pallets
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
fn get_enum_hash(
|
||||
registry: &PortableRegistry,
|
||||
id: u32,
|
||||
only_these_variants: Option<&[&str]>,
|
||||
) -> Hash {
|
||||
let ty = registry
|
||||
.types
|
||||
.get(id as usize)
|
||||
.expect("Metadata should contain enum type in registry");
|
||||
|
||||
if let TypeDef::Variant(variant) = &ty.ty.type_def {
|
||||
get_type_def_variant_hash(
|
||||
registry,
|
||||
variant,
|
||||
only_these_variants,
|
||||
&mut HashMap::new(),
|
||||
// ignored, because not computed yet...
|
||||
&OuterEnumHashes::empty(),
|
||||
)
|
||||
} else {
|
||||
get_type_hash(registry, id, &OuterEnumHashes::empty())
|
||||
}
|
||||
}
|
||||
let enums = &metadata.outer_enums;
|
||||
|
||||
let call_variants = variants(check_enum_type_id(enums.call_enum_ty));
|
||||
let call_hash = get_enum_hash(metadata.types(), enums.call_enum_ty, call_variants);
|
||||
let event_variants = variants(check_enum_type_id(enums.event_enum_ty));
|
||||
let event_hash = get_enum_hash(metadata.types(), enums.event_enum_ty, event_variants);
|
||||
let error_variants = variants(check_enum_type_id(enums.error_enum_ty));
|
||||
let error_hash = get_enum_hash(metadata.types(), enums.error_enum_ty, error_variants);
|
||||
|
||||
Self {
|
||||
call_hash: (enums.call_enum_ty, call_hash),
|
||||
error_hash: (enums.error_enum_ty, error_hash),
|
||||
event_hash: (enums.event_enum_ty, event_hash),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs empty `OuterEnumHashes` with type ids that are never a real type id.
|
||||
/// Can be used as a placeholder when outer enum hashes are required but should be ignored.
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
call_hash: (u32::MAX, [0; HASH_LEN]),
|
||||
error_hash: (u32::MAX, [0; HASH_LEN]),
|
||||
event_hash: (u32::MAX, [0; HASH_LEN]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a combined hash of the top level enums.
|
||||
pub fn combined_hash(&self) -> Hash {
|
||||
concat_and_hash3(&self.call_hash.1, &self.error_hash.1, &self.event_hash.1)
|
||||
}
|
||||
|
||||
/// Checks if a type is one of the 3 top level enum types. If so, returns Some(hash).
|
||||
///
|
||||
/// This is useful, because top level enums are sometimes stripped down to only certain pallets.
|
||||
/// The hashes of these stripped down types are stored in this struct.
|
||||
pub fn resolve(&self, id: u32) -> Option<[u8; HASH_LEN]> {
|
||||
match id {
|
||||
e if e == self.error_hash.0 => Some(self.error_hash.1),
|
||||
e if e == self.event_hash.0 => Some(self.event_hash.1),
|
||||
e if e == self.call_hash.0 => Some(self.call_hash.1),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,7 @@ use crate::{
|
||||
utils::{node_runtime, TestNodeProcess},
|
||||
};
|
||||
use codec::Encode;
|
||||
use futures::{stream, Stream, StreamExt};
|
||||
use std::task::Poll;
|
||||
use futures::{Stream, StreamExt};
|
||||
use subxt::{
|
||||
blocks::Block,
|
||||
client::OnlineClient,
|
||||
@@ -98,15 +97,31 @@ async fn archive_unstable_call() {
|
||||
async fn archive_unstable_finalized_height() {
|
||||
let ctx = test_context().await;
|
||||
let rpc = ctx.chainhead_rpc_methods().await;
|
||||
let mut blocks = fetch_finalized_blocks(&ctx, 3).await;
|
||||
|
||||
while let Some(block) = blocks.next().await {
|
||||
let subxt_block_height = block.number() as usize;
|
||||
// This test is quite ugly. Originally, we asked for finalized blocks from subxt and
|
||||
// asserted that the archive height we then get back matches, but that is subject to
|
||||
// races between subxt's stream and reality (and failed surprisingly often). To try
|
||||
// to avoid this, we weaken the test to just check that the height increments over time.
|
||||
let mut last_block_height = None;
|
||||
loop {
|
||||
// Fetch archive block height.
|
||||
let archive_block_height = rpc.archive_unstable_finalized_height().await.unwrap();
|
||||
|
||||
// Note: may be prone to race if call is super slow for some reason, since a new
|
||||
// block may have been finalized since subxt reported it.
|
||||
assert_eq!(subxt_block_height, archive_block_height);
|
||||
// On a dev node we expect blocks to be finalized 1 by 1, so panic
|
||||
// if the height we fetch has grown by more than 1.
|
||||
if let Some(last) = last_block_height {
|
||||
if archive_block_height != last && archive_block_height != last + 1 {
|
||||
panic!("Archive block height should increase 1 at a time, but jumped from {last} to {archive_block_height}");
|
||||
}
|
||||
}
|
||||
|
||||
last_block_height = Some(archive_block_height);
|
||||
if archive_block_height > 5 {
|
||||
break;
|
||||
}
|
||||
|
||||
// Wait a little before looping
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,41 @@
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::{node_runtime, subxt_test, test_context, TestContext};
|
||||
use frame_metadata::v15::{
|
||||
CustomMetadata, ExtrinsicMetadata, OuterEnums, PalletCallMetadata, PalletMetadata,
|
||||
PalletStorageMetadata, RuntimeMetadataV15, StorageEntryMetadata, StorageEntryModifier,
|
||||
StorageEntryType,
|
||||
use codec::Decode;
|
||||
use frame_metadata::{
|
||||
v15::{
|
||||
CustomMetadata, ExtrinsicMetadata, OuterEnums, PalletCallMetadata, PalletMetadata,
|
||||
PalletStorageMetadata, RuntimeMetadataV15, StorageEntryMetadata, StorageEntryModifier,
|
||||
StorageEntryType,
|
||||
},
|
||||
RuntimeMetadata, RuntimeMetadataPrefixed,
|
||||
};
|
||||
use scale_info::{
|
||||
build::{Fields, Variants},
|
||||
meta_type, Path, Type, TypeInfo,
|
||||
};
|
||||
use subxt::{Metadata, OfflineClient, SubstrateConfig};
|
||||
use subxt::{Metadata, OfflineClient, OnlineClient, SubstrateConfig};
|
||||
|
||||
async fn fetch_v15_metadata(client: &OnlineClient<SubstrateConfig>) -> RuntimeMetadataV15 {
|
||||
let payload = node_runtime::apis().metadata().metadata_at_version(15);
|
||||
let runtime_metadata_bytes = client
|
||||
.runtime_api()
|
||||
.at_latest()
|
||||
.await
|
||||
.unwrap()
|
||||
.call(payload)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.0;
|
||||
let runtime_metadata = RuntimeMetadataPrefixed::decode(&mut &*runtime_metadata_bytes)
|
||||
.unwrap()
|
||||
.1;
|
||||
let RuntimeMetadata::V15(v15_metadata) = runtime_metadata else {
|
||||
panic!("Metadata is not v15")
|
||||
};
|
||||
v15_metadata
|
||||
}
|
||||
|
||||
async fn metadata_to_api(metadata: Metadata, ctx: &TestContext) -> OfflineClient<SubstrateConfig> {
|
||||
OfflineClient::new(
|
||||
@@ -27,15 +52,6 @@ fn v15_to_metadata(v15: RuntimeMetadataV15) -> Metadata {
|
||||
subxt_md.into()
|
||||
}
|
||||
|
||||
fn modified_metadata<F>(metadata: Metadata, f: F) -> Metadata
|
||||
where
|
||||
F: FnOnce(&mut RuntimeMetadataV15),
|
||||
{
|
||||
let mut metadata = RuntimeMetadataV15::from((*metadata).clone());
|
||||
f(&mut metadata);
|
||||
v15_to_metadata(metadata)
|
||||
}
|
||||
|
||||
fn default_pallet() -> PalletMetadata {
|
||||
PalletMetadata {
|
||||
name: "Test",
|
||||
@@ -97,72 +113,72 @@ fn pallets_to_metadata(pallets: Vec<PalletMetadata>) -> Metadata {
|
||||
))
|
||||
}
|
||||
|
||||
#[subxt_test]
|
||||
async fn metadata_converting_works_ok() {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
assert!(
|
||||
node_runtime::is_codegen_valid_for(&api.metadata()),
|
||||
"Should be valid initially"
|
||||
);
|
||||
|
||||
let metadata = RuntimeMetadataV15::from((*api.metadata()).clone());
|
||||
let metadata = v15_to_metadata(metadata);
|
||||
|
||||
assert!(
|
||||
node_runtime::is_codegen_valid_for(&metadata),
|
||||
"Should still be valid after conversion back and forth"
|
||||
);
|
||||
}
|
||||
|
||||
#[subxt_test]
|
||||
async fn full_metadata_check() {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
let mut v15_metadata = fetch_v15_metadata(&api).await;
|
||||
|
||||
// Runtime metadata is identical to the metadata used during API generation.
|
||||
assert!(node_runtime::is_codegen_valid_for(&api.metadata()));
|
||||
// Runtime metadata is identical to the metadata we just downloaded
|
||||
let metadata_before = v15_to_metadata(v15_metadata.clone());
|
||||
assert!(node_runtime::is_codegen_valid_for(&metadata_before));
|
||||
|
||||
// Modify the metadata.
|
||||
let metadata = modified_metadata(api.metadata(), |md| {
|
||||
md.pallets[0].name = "NewPallet".to_string();
|
||||
});
|
||||
v15_metadata.pallets[0].name = "NewPallet".to_string();
|
||||
|
||||
// It should now be invalid:
|
||||
assert!(!node_runtime::is_codegen_valid_for(&metadata));
|
||||
let metadata_after = v15_to_metadata(v15_metadata);
|
||||
assert!(!node_runtime::is_codegen_valid_for(&metadata_after));
|
||||
}
|
||||
|
||||
#[subxt_test]
|
||||
async fn constant_values_are_not_validated() {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
let mut v15_metadata = fetch_v15_metadata(&api).await;
|
||||
|
||||
// Build an api from our v15 metadata to confirm that it's good, just like
|
||||
// the metadata downloaded by the API itself.
|
||||
let api_from_original_metadata = {
|
||||
let metadata_before = v15_to_metadata(v15_metadata.clone());
|
||||
metadata_to_api(metadata_before, &ctx).await
|
||||
};
|
||||
|
||||
let deposit_addr = node_runtime::constants().balances().existential_deposit();
|
||||
|
||||
// Retrieve existential deposit to validate it and confirm that it's OK.
|
||||
assert!(api.constants().at(&deposit_addr).is_ok());
|
||||
assert!(api_from_original_metadata
|
||||
.constants()
|
||||
.at(&deposit_addr)
|
||||
.is_ok());
|
||||
|
||||
// Modify the metadata.
|
||||
let metadata = modified_metadata(api.metadata(), |md| {
|
||||
let existential = md
|
||||
.pallets
|
||||
.iter_mut()
|
||||
.find(|pallet| pallet.name == "Balances")
|
||||
.expect("Metadata must contain Balances pallet")
|
||||
.constants
|
||||
.iter_mut()
|
||||
.find(|constant| constant.name == "ExistentialDeposit")
|
||||
.expect("ExistentialDeposit constant must be present");
|
||||
let existential = v15_metadata
|
||||
.pallets
|
||||
.iter_mut()
|
||||
.find(|pallet| pallet.name == "Balances")
|
||||
.expect("Metadata must contain Balances pallet")
|
||||
.constants
|
||||
.iter_mut()
|
||||
.find(|constant| constant.name == "ExistentialDeposit")
|
||||
.expect("ExistentialDeposit constant must be present");
|
||||
|
||||
// Modifying a constant value should not lead to an error:
|
||||
existential.value = vec![0u8; 32];
|
||||
});
|
||||
// Modifying a constant value should not lead to an error:
|
||||
existential.value = vec![0u8; 32];
|
||||
|
||||
let api = metadata_to_api(metadata, &ctx).await;
|
||||
// Build our API again, this time form the metadata we've tweaked.
|
||||
let api_from_modified_metadata = {
|
||||
let metadata_before = v15_to_metadata(v15_metadata);
|
||||
metadata_to_api(metadata_before, &ctx).await
|
||||
};
|
||||
|
||||
assert!(node_runtime::is_codegen_valid_for(&api.metadata()));
|
||||
assert!(api.constants().at(&deposit_addr).is_ok());
|
||||
assert!(node_runtime::is_codegen_valid_for(
|
||||
&api_from_modified_metadata.metadata()
|
||||
));
|
||||
assert!(api_from_modified_metadata
|
||||
.constants()
|
||||
.at(&deposit_addr)
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[subxt_test]
|
||||
|
||||
@@ -16,4 +16,5 @@ frame-metadata = { workspace = true }
|
||||
codec = { package = "parity-scale-codec", workspace = true, features = ["derive", "bit-vec"] }
|
||||
subxt = { workspace = true, features = ["native", "jsonrpsee", "runtime-wasm-path"] }
|
||||
subxt-metadata = { workspace = true }
|
||||
subxt-utils-stripmetadata = { workspace = true }
|
||||
generate-custom-metadata = { path = "../generate-custom-metadata" }
|
||||
|
||||
@@ -17,11 +17,32 @@ mod storage;
|
||||
mod utils;
|
||||
|
||||
use crate::utils::MetadataTestRunner;
|
||||
use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed};
|
||||
use subxt_utils_stripmetadata::StripMetadata;
|
||||
|
||||
// Each of these tests leads to some rust code being compiled and
|
||||
// executed to test that compilation is successful (or errors in the
|
||||
// way that we'd expect).
|
||||
|
||||
fn strip_metadata<Pallets, Apis>(
|
||||
metadata: &mut RuntimeMetadataPrefixed,
|
||||
pallets: Pallets,
|
||||
apis: Apis,
|
||||
) where
|
||||
Pallets: Fn(&str) -> bool,
|
||||
Apis: Fn(&str) -> bool,
|
||||
{
|
||||
match &mut metadata.1 {
|
||||
RuntimeMetadata::V14(m) => m.strip_metadata(pallets, apis),
|
||||
RuntimeMetadata::V15(m) => m.strip_metadata(pallets, apis),
|
||||
RuntimeMetadata::V16(m) => m.strip_metadata(pallets, apis),
|
||||
m => panic!(
|
||||
"Metadata should be V14, V15 or V16, but is V{}",
|
||||
m.version()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui_tests() {
|
||||
let mut m = MetadataTestRunner::default();
|
||||
@@ -62,7 +83,7 @@ fn ui_tests() {
|
||||
// Test retaining only specific pallets and ensure that works.
|
||||
for pallet in ["Babe", "Claims", "Grandpa", "Balances"] {
|
||||
let mut metadata = MetadataTestRunner::load_metadata();
|
||||
metadata.retain(|p| p == pallet, |_| true);
|
||||
strip_metadata(&mut metadata, |p| p == pallet, |_| true);
|
||||
|
||||
t.pass(
|
||||
m.new_test_case()
|
||||
@@ -74,7 +95,7 @@ fn ui_tests() {
|
||||
// Test retaining only specific runtime APIs to ensure that works.
|
||||
for runtime_api in ["Core", "Metadata"] {
|
||||
let mut metadata = MetadataTestRunner::load_metadata();
|
||||
metadata.retain(|_| true, |r| r == runtime_api);
|
||||
strip_metadata(&mut metadata, |_| true, |r| r == runtime_api);
|
||||
|
||||
t.pass(
|
||||
m.new_test_case()
|
||||
@@ -87,7 +108,8 @@ fn ui_tests() {
|
||||
// client state is full:
|
||||
{
|
||||
let mut metadata = MetadataTestRunner::load_metadata();
|
||||
metadata.retain(
|
||||
strip_metadata(
|
||||
&mut metadata,
|
||||
|p| ["Babe", "Claims"].contains(&p),
|
||||
|r| ["Core", "Metadata"].contains(&r),
|
||||
);
|
||||
@@ -104,12 +126,17 @@ fn ui_tests() {
|
||||
// _not_ compare valid against client with differently stripped metadata.
|
||||
{
|
||||
let mut codegen_metadata = MetadataTestRunner::load_metadata();
|
||||
codegen_metadata.retain(
|
||||
strip_metadata(
|
||||
&mut codegen_metadata,
|
||||
|p| ["Babe", "Claims"].contains(&p),
|
||||
|r| ["Core", "Metadata"].contains(&r),
|
||||
);
|
||||
let mut validation_metadata = MetadataTestRunner::load_metadata();
|
||||
validation_metadata.retain(|p| p != "Claims", |r| r != "Metadata");
|
||||
strip_metadata(
|
||||
&mut validation_metadata,
|
||||
|p| p != "Claims",
|
||||
|r| r != "Metadata",
|
||||
);
|
||||
|
||||
t.pass(
|
||||
m.new_test_case()
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
// see LICENSE for license details.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use frame_metadata::RuntimeMetadataPrefixed;
|
||||
use std::io::Read;
|
||||
use subxt_metadata::Metadata;
|
||||
|
||||
static TEST_DIR_PREFIX: &str = "subxt_generated_ui_tests_";
|
||||
static METADATA_FILE: &str = "../../artifacts/polkadot_metadata_full.scale";
|
||||
@@ -17,7 +17,7 @@ pub struct MetadataTestRunner {
|
||||
impl MetadataTestRunner {
|
||||
/// Loads metadata that we can use in our tests. Panics if
|
||||
/// there is some issue decoding the metadata.
|
||||
pub fn load_metadata() -> Metadata {
|
||||
pub fn load_metadata() -> RuntimeMetadataPrefixed {
|
||||
let mut file =
|
||||
std::fs::File::open(METADATA_FILE).expect("Cannot open metadata.scale artifact");
|
||||
|
||||
@@ -25,7 +25,7 @@ impl MetadataTestRunner {
|
||||
file.read_to_end(&mut bytes)
|
||||
.expect("Failed to read metadata.scale file");
|
||||
|
||||
Metadata::decode(&mut &*bytes).expect("Cannot decode metadata bytes")
|
||||
RuntimeMetadataPrefixed::decode(&mut &*bytes).expect("Cannot decode metadata bytes")
|
||||
}
|
||||
|
||||
/// Create a new test case.
|
||||
@@ -54,7 +54,7 @@ impl Drop for MetadataTestRunner {
|
||||
pub struct MetadataTestRunnerCaseBuilder {
|
||||
index: usize,
|
||||
name: String,
|
||||
validation_metadata: Option<Metadata>,
|
||||
validation_metadata: Option<RuntimeMetadataPrefixed>,
|
||||
should_be_valid: bool,
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ impl MetadataTestRunnerCaseBuilder {
|
||||
|
||||
/// Set metadata to be validated against the generated code.
|
||||
/// By default, we'll validate the same metadata used to generate the code.
|
||||
pub fn validation_metadata(mut self, md: impl Into<Metadata>) -> Self {
|
||||
pub fn validation_metadata(mut self, md: impl Into<RuntimeMetadataPrefixed>) -> Self {
|
||||
self.validation_metadata = Some(md.into());
|
||||
self
|
||||
}
|
||||
@@ -100,15 +100,12 @@ impl MetadataTestRunnerCaseBuilder {
|
||||
///
|
||||
/// The generated code will be tidied up when the `MetadataTestRunner` that
|
||||
/// this was handed out from is dropped.
|
||||
pub fn build<M>(self, macro_metadata: M) -> String
|
||||
where
|
||||
M: TryInto<Metadata>,
|
||||
M::Error: std::fmt::Debug,
|
||||
{
|
||||
let macro_metadata = macro_metadata.try_into().expect("can into Metadata");
|
||||
let validation_metadata = self
|
||||
.validation_metadata
|
||||
.unwrap_or_else(|| macro_metadata.clone());
|
||||
pub fn build(self, macro_metadata: frame_metadata::RuntimeMetadataPrefixed) -> String {
|
||||
let validation_metadata = self.validation_metadata.unwrap_or_else(|| {
|
||||
// RuntimeMetadataPrefixed doesn't implement Clone for some reason (we should prob fix that).
|
||||
// until then, this hack clones it by encoding and then decoding it again from bytes..
|
||||
clone_via_encode(¯o_metadata)
|
||||
});
|
||||
|
||||
let index = self.index;
|
||||
let mut tmp_dir = std::env::temp_dir();
|
||||
@@ -178,3 +175,8 @@ impl MetadataTestRunnerCaseBuilder {
|
||||
tmp_rust_path
|
||||
}
|
||||
}
|
||||
|
||||
fn clone_via_encode<T: codec::Encode + codec::Decode>(item: &T) -> T {
|
||||
let bytes = item.encode();
|
||||
T::decode(&mut &*bytes).unwrap()
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ license.workspace = true
|
||||
repository.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
description = "subxt utils fetch metadata"
|
||||
description = "subxt utility to fetch metadata"
|
||||
|
||||
[features]
|
||||
url = ["dep:jsonrpsee", "dep:tokio", "dep:url", "frame-metadata"]
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "subxt-utils-stripmetadata"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
publish = true
|
||||
autotests = false
|
||||
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
description = "subxt utility to strip metadata"
|
||||
|
||||
[dependencies]
|
||||
frame-metadata = { workspace = true, features = ["std"] }
|
||||
scale-info = { workspace = true, features = ["std"] }
|
||||
either = { workspace = true }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["url"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.playground]
|
||||
default-features = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,920 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! This utility crate provides a [`StripMetadata`] trait which exposes a [`StripMetadata::strip_metadata`] method
|
||||
//! able to remove pallets and runtime APIs from the metadata in question.
|
||||
|
||||
use either::Either;
|
||||
use frame_metadata::{v14, v15, v16};
|
||||
use scale_info::PortableRegistry;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
/// This trait is implemented for metadata versions to enable us to strip pallets and runtime APIs from them.
|
||||
///
|
||||
/// To implement the [`StripMetadata::strip_metadata`] method for a new metadata version, you'll probably:
|
||||
/// - Remove any pallets and runtime APIs from the metadata based on the filter functions.
|
||||
/// - Call `self.iter_type_ids_mut().collect()` to gather all of the type IDs to keep.
|
||||
/// - This will require implementing [`IterateTypeIds`], which is the thing that iterates over all of the
|
||||
/// type IDs still present in the metadata such that we know what we need to keep.
|
||||
/// - Call `self.types.retain(..)` to filter any types not matching the type IDs above out of the registry.
|
||||
/// - Iterate over the type IDs again, mapping those found in the metadata to the new IDs that calling
|
||||
/// `self.types.retain(..)` handed back.
|
||||
pub trait StripMetadata {
|
||||
/// Strip out any pallets and runtime APIs for which the provided filter functions return false.
|
||||
fn strip_metadata<PalletFilter, RuntimeApiFilter>(
|
||||
&mut self,
|
||||
keep_pallet: PalletFilter,
|
||||
keep_runtime_api: RuntimeApiFilter,
|
||||
) where
|
||||
PalletFilter: Fn(&str) -> bool,
|
||||
RuntimeApiFilter: Fn(&str) -> bool;
|
||||
}
|
||||
|
||||
impl StripMetadata for v14::RuntimeMetadataV14 {
|
||||
fn strip_metadata<PalletFilter, RuntimeApiFilter>(
|
||||
&mut self,
|
||||
keep_pallet: PalletFilter,
|
||||
_keep_runtime_api: RuntimeApiFilter,
|
||||
) where
|
||||
PalletFilter: Fn(&str) -> bool,
|
||||
RuntimeApiFilter: Fn(&str) -> bool,
|
||||
{
|
||||
// Throw away pallets we don't care about:
|
||||
self.pallets.retain(|pallet| keep_pallet(&pallet.name));
|
||||
|
||||
// Now, only retain types we care about in the registry:
|
||||
retain_types(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl StripMetadata for v15::RuntimeMetadataV15 {
|
||||
fn strip_metadata<PalletFilter, RuntimeApiFilter>(
|
||||
&mut self,
|
||||
keep_pallet: PalletFilter,
|
||||
keep_runtime_api: RuntimeApiFilter,
|
||||
) where
|
||||
PalletFilter: Fn(&str) -> bool,
|
||||
RuntimeApiFilter: Fn(&str) -> bool,
|
||||
{
|
||||
// Throw away pallets and runtime APIs we don't care about:
|
||||
self.pallets.retain(|pallet| keep_pallet(&pallet.name));
|
||||
self.apis.retain(|api| keep_runtime_api(&api.name));
|
||||
|
||||
// Now, only retain types we care about in the registry:
|
||||
retain_types(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl StripMetadata for v16::RuntimeMetadataV16 {
|
||||
fn strip_metadata<PalletFilter, RuntimeApiFilter>(
|
||||
&mut self,
|
||||
keep_pallet: PalletFilter,
|
||||
keep_runtime_api: RuntimeApiFilter,
|
||||
) where
|
||||
PalletFilter: Fn(&str) -> bool,
|
||||
RuntimeApiFilter: Fn(&str) -> bool,
|
||||
{
|
||||
// Throw away pallets and runtime APIs we don't care about:
|
||||
self.pallets.retain(|pallet| keep_pallet(&pallet.name));
|
||||
self.apis.retain(|api| keep_runtime_api(&api.name));
|
||||
|
||||
// Now, only retain types we care about in the registry:
|
||||
retain_types(self);
|
||||
}
|
||||
}
|
||||
|
||||
fn retain_types<M: GetTypes + IterateTypeIds>(m: &mut M) {
|
||||
// We want to preserve this type even if it's not used anywhere:
|
||||
let dispatch_err_type_id = find_dispatch_error_type(m.get_types_mut());
|
||||
|
||||
// Iterate over the type IDs and retain any that we still need:
|
||||
let keep_these_ids: BTreeSet<u32> = m
|
||||
.iter_type_ids_mut()
|
||||
.map(|id| *id)
|
||||
.chain(Some(dispatch_err_type_id))
|
||||
.collect();
|
||||
|
||||
let new_ids = m.get_types_mut().retain(|id| keep_these_ids.contains(&id));
|
||||
|
||||
// Map IDs found in the metadata to new ones as needed after the retaining:
|
||||
for id in m.iter_type_ids_mut() {
|
||||
if let Some(new_id) = new_ids.get(id) {
|
||||
*id = *new_id;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait is implemented for metadatas, and its purpose is to hand back iterators over
|
||||
/// all of the type IDs (doesn't need to recurse into them) that are used in the metadata,
|
||||
/// so that we know which ones we need to keep around in the type registry (and thus which
|
||||
/// ones we can remove).
|
||||
trait IterateTypeIds {
|
||||
/// This should iterate over all type IDs found in the metadata.
|
||||
fn iter_type_ids_mut(&mut self) -> impl Iterator<Item = &mut u32>;
|
||||
}
|
||||
|
||||
impl IterateTypeIds for v14::RuntimeMetadataV14 {
|
||||
fn iter_type_ids_mut(&mut self) -> impl Iterator<Item = &mut u32> {
|
||||
// Gather pallet types:
|
||||
let pallet_types = self.pallets.iter_mut().flat_map(|pallet| {
|
||||
let pallet_call_types = pallet
|
||||
.calls
|
||||
.as_mut()
|
||||
.into_iter()
|
||||
.map(|calls| &mut calls.ty.id);
|
||||
|
||||
let pallet_storage_types = pallet
|
||||
.storage
|
||||
.as_mut()
|
||||
.into_iter()
|
||||
.flat_map(|s| &mut s.entries)
|
||||
.flat_map(|storage_entry| match &mut storage_entry.ty {
|
||||
v14::StorageEntryType::Plain(ty) => Either::Left(core::iter::once(&mut ty.id)),
|
||||
v14::StorageEntryType::Map { key, value, .. } => {
|
||||
Either::Right([&mut key.id, &mut value.id].into_iter())
|
||||
}
|
||||
});
|
||||
|
||||
let pallet_constant_types = pallet
|
||||
.constants
|
||||
.iter_mut()
|
||||
.map(|constant| &mut constant.ty.id);
|
||||
|
||||
let pallet_event_type = pallet
|
||||
.event
|
||||
.as_mut()
|
||||
.into_iter()
|
||||
.map(|events| &mut events.ty.id);
|
||||
|
||||
let pallet_error_type = pallet
|
||||
.error
|
||||
.as_mut()
|
||||
.into_iter()
|
||||
.map(|error| &mut error.ty.id);
|
||||
|
||||
pallet_call_types
|
||||
.chain(pallet_storage_types)
|
||||
.chain(pallet_constant_types)
|
||||
.chain(pallet_event_type)
|
||||
.chain(pallet_error_type)
|
||||
});
|
||||
|
||||
// Transaction Extension types:
|
||||
let transaction_extension_types = self
|
||||
.extrinsic
|
||||
.signed_extensions
|
||||
.iter_mut()
|
||||
.flat_map(|ext| [&mut ext.ty.id, &mut ext.additional_signed.id].into_iter());
|
||||
|
||||
// The extrinsic type:
|
||||
let extrinsic_type_id = &mut self.extrinsic.ty.id;
|
||||
|
||||
// Return all IDs gathered:
|
||||
pallet_types
|
||||
.chain(Some(extrinsic_type_id))
|
||||
.chain(transaction_extension_types)
|
||||
}
|
||||
}
|
||||
|
||||
impl IterateTypeIds for v15::RuntimeMetadataV15 {
|
||||
fn iter_type_ids_mut(&mut self) -> impl Iterator<Item = &mut u32> {
|
||||
// Gather pallet types:
|
||||
let pallet_types = self.pallets.iter_mut().flat_map(|pallet| {
|
||||
let pallet_call_types = pallet
|
||||
.calls
|
||||
.as_mut()
|
||||
.into_iter()
|
||||
.map(|calls| &mut calls.ty.id);
|
||||
|
||||
let pallet_storage_types = pallet
|
||||
.storage
|
||||
.as_mut()
|
||||
.into_iter()
|
||||
.flat_map(|s| &mut s.entries)
|
||||
.flat_map(|storage_entry| match &mut storage_entry.ty {
|
||||
v14::StorageEntryType::Plain(ty) => Either::Left(core::iter::once(&mut ty.id)),
|
||||
v14::StorageEntryType::Map { key, value, .. } => {
|
||||
Either::Right([&mut key.id, &mut value.id].into_iter())
|
||||
}
|
||||
});
|
||||
|
||||
let pallet_constant_types = pallet
|
||||
.constants
|
||||
.iter_mut()
|
||||
.map(|constant| &mut constant.ty.id);
|
||||
|
||||
let pallet_event_type = pallet
|
||||
.event
|
||||
.as_mut()
|
||||
.into_iter()
|
||||
.map(|events| &mut events.ty.id);
|
||||
|
||||
let pallet_error_type = pallet
|
||||
.error
|
||||
.as_mut()
|
||||
.into_iter()
|
||||
.map(|error| &mut error.ty.id);
|
||||
|
||||
pallet_call_types
|
||||
.chain(pallet_storage_types)
|
||||
.chain(pallet_constant_types)
|
||||
.chain(pallet_event_type)
|
||||
.chain(pallet_error_type)
|
||||
});
|
||||
|
||||
// Runtime APIs:
|
||||
let runtime_api_types = self
|
||||
.apis
|
||||
.iter_mut()
|
||||
.flat_map(|api| &mut api.methods)
|
||||
.flat_map(|method| {
|
||||
let method_inputs = method.inputs.iter_mut().map(|input| &mut input.ty.id);
|
||||
let method_output = &mut method.output.id;
|
||||
method_inputs.chain(core::iter::once(method_output))
|
||||
});
|
||||
|
||||
// The extrinsic type IDs:
|
||||
let extrinsic_type_ids = [
|
||||
&mut self.extrinsic.call_ty.id,
|
||||
&mut self.extrinsic.address_ty.id,
|
||||
&mut self.extrinsic.extra_ty.id,
|
||||
&mut self.extrinsic.signature_ty.id,
|
||||
];
|
||||
|
||||
// Outer enum type IDs:
|
||||
let outer_enum_type_ids = [
|
||||
&mut self.outer_enums.call_enum_ty.id,
|
||||
&mut self.outer_enums.event_enum_ty.id,
|
||||
&mut self.outer_enums.error_enum_ty.id,
|
||||
];
|
||||
|
||||
// Transaction Extension types:
|
||||
let transaction_extension_types = self
|
||||
.extrinsic
|
||||
.signed_extensions
|
||||
.iter_mut()
|
||||
.flat_map(|ext| [&mut ext.ty.id, &mut ext.additional_signed.id].into_iter());
|
||||
|
||||
// Custom types:
|
||||
let custom_type_ids = self.custom.map.values_mut().map(|value| &mut value.ty.id);
|
||||
|
||||
// Return all IDs gathered:
|
||||
pallet_types
|
||||
.chain(runtime_api_types)
|
||||
.chain(extrinsic_type_ids)
|
||||
.chain(outer_enum_type_ids)
|
||||
.chain(transaction_extension_types)
|
||||
.chain(custom_type_ids)
|
||||
}
|
||||
}
|
||||
|
||||
impl IterateTypeIds for v16::RuntimeMetadataV16 {
|
||||
fn iter_type_ids_mut(&mut self) -> impl Iterator<Item = &mut u32> {
|
||||
// Gather pallet types:
|
||||
let pallet_types = self.pallets.iter_mut().flat_map(|pallet| {
|
||||
let pallet_call_types = pallet
|
||||
.calls
|
||||
.as_mut()
|
||||
.into_iter()
|
||||
.map(|calls| &mut calls.ty.id);
|
||||
|
||||
let pallet_storage_types = pallet
|
||||
.storage
|
||||
.as_mut()
|
||||
.into_iter()
|
||||
.flat_map(|s| &mut s.entries)
|
||||
.flat_map(|storage_entry| match &mut storage_entry.ty {
|
||||
v16::StorageEntryType::Plain(ty) => Either::Left(core::iter::once(&mut ty.id)),
|
||||
v16::StorageEntryType::Map { key, value, .. } => {
|
||||
Either::Right([&mut key.id, &mut value.id].into_iter())
|
||||
}
|
||||
});
|
||||
|
||||
let pallet_constant_types = pallet
|
||||
.constants
|
||||
.iter_mut()
|
||||
.map(|constant| &mut constant.ty.id);
|
||||
|
||||
let pallet_event_type = pallet
|
||||
.event
|
||||
.as_mut()
|
||||
.into_iter()
|
||||
.map(|events| &mut events.ty.id);
|
||||
|
||||
let pallet_error_type = pallet
|
||||
.error
|
||||
.as_mut()
|
||||
.into_iter()
|
||||
.map(|error| &mut error.ty.id);
|
||||
|
||||
let pallet_view_fns = pallet.view_functions.iter_mut().flat_map(|vf| {
|
||||
let inputs = vf.inputs.iter_mut().map(|input| &mut input.ty.id);
|
||||
let output = &mut vf.output.id;
|
||||
|
||||
inputs.chain(core::iter::once(output))
|
||||
});
|
||||
|
||||
let pallet_associated_types = pallet
|
||||
.associated_types
|
||||
.iter_mut()
|
||||
.map(|associated_type| &mut associated_type.ty.id);
|
||||
|
||||
pallet_call_types
|
||||
.chain(pallet_storage_types)
|
||||
.chain(pallet_constant_types)
|
||||
.chain(pallet_event_type)
|
||||
.chain(pallet_error_type)
|
||||
.chain(pallet_view_fns)
|
||||
.chain(pallet_associated_types)
|
||||
});
|
||||
|
||||
// Runtime APIs:
|
||||
let runtime_api_types = self
|
||||
.apis
|
||||
.iter_mut()
|
||||
.flat_map(|api| &mut api.methods)
|
||||
.flat_map(|method| {
|
||||
let method_inputs = method.inputs.iter_mut().map(|input| &mut input.ty.id);
|
||||
let method_output = &mut method.output.id;
|
||||
method_inputs.chain(core::iter::once(method_output))
|
||||
});
|
||||
|
||||
// The extrinsic type IDs:
|
||||
let extrinsic_type_ids = [
|
||||
&mut self.extrinsic.address_ty.id,
|
||||
&mut self.extrinsic.signature_ty.id,
|
||||
];
|
||||
|
||||
// Outer enum type IDs:
|
||||
let outer_enum_type_ids = [
|
||||
&mut self.outer_enums.call_enum_ty.id,
|
||||
&mut self.outer_enums.event_enum_ty.id,
|
||||
&mut self.outer_enums.error_enum_ty.id,
|
||||
];
|
||||
|
||||
// Transaction Extension types:
|
||||
let transaction_extension_types = self
|
||||
.extrinsic
|
||||
.transaction_extensions
|
||||
.iter_mut()
|
||||
.flat_map(|ext| [&mut ext.ty.id, &mut ext.implicit.id].into_iter());
|
||||
|
||||
// Custom types:
|
||||
let custom_type_ids = self.custom.map.values_mut().map(|value| &mut value.ty.id);
|
||||
|
||||
// Return all IDs gathered:
|
||||
pallet_types
|
||||
.chain(runtime_api_types)
|
||||
.chain(extrinsic_type_ids)
|
||||
.chain(outer_enum_type_ids)
|
||||
.chain(transaction_extension_types)
|
||||
.chain(custom_type_ids)
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait defines how to get a type registry from the metadata
|
||||
trait GetTypes {
|
||||
fn get_types_mut(&mut self) -> &mut PortableRegistry;
|
||||
}
|
||||
|
||||
impl GetTypes for v14::RuntimeMetadataV14 {
|
||||
fn get_types_mut(&mut self) -> &mut PortableRegistry {
|
||||
&mut self.types
|
||||
}
|
||||
}
|
||||
|
||||
impl GetTypes for v15::RuntimeMetadataV15 {
|
||||
fn get_types_mut(&mut self) -> &mut PortableRegistry {
|
||||
&mut self.types
|
||||
}
|
||||
}
|
||||
|
||||
impl GetTypes for v16::RuntimeMetadataV16 {
|
||||
fn get_types_mut(&mut self) -> &mut PortableRegistry {
|
||||
&mut self.types
|
||||
}
|
||||
}
|
||||
|
||||
/// Subxt needs this type so we always ensure to preserve it
|
||||
/// even if it's not explicitly mentioned anywhere:
|
||||
fn find_dispatch_error_type(types: &mut PortableRegistry) -> u32 {
|
||||
types
|
||||
.types
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_idx, ty)| ty.ty.path.segments == ["sp_runtime", "DispatchError"])
|
||||
.expect("Metadata must contain sp_runtime::DispatchError")
|
||||
.0 as u32
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::*;
|
||||
use scale_info::meta_type;
|
||||
|
||||
/// Create dummy types that we can check the presense of with is_in_types.
|
||||
macro_rules! make_types {
|
||||
($($name:ident)+) => {
|
||||
$(
|
||||
struct $name {}
|
||||
impl scale_info::TypeInfo for $name {
|
||||
type Identity = $name;
|
||||
|
||||
fn type_info() -> scale_info::Type {
|
||||
scale_info::Type {
|
||||
path: scale_info::Path {
|
||||
segments: vec!["dummy_type", stringify!($name)],
|
||||
},
|
||||
type_params: vec![],
|
||||
type_def: scale_info::TypeDef::Composite(scale_info::TypeDefComposite { fields: vec![] }),
|
||||
docs: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl $name {
|
||||
#[allow(dead_code)]
|
||||
pub fn is_in_types(types: &scale_info::PortableRegistry) -> bool {
|
||||
types.types.iter().any(|ty| ty.ty.path.segments == vec!["dummy_type", stringify!($name)])
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts that a set of the dummy types exist in a registry.
|
||||
macro_rules! assert_is_in_types {
|
||||
($($name:ident)+ => $types:expr) => {{
|
||||
$(
|
||||
if !$name::is_in_types(&$types) {
|
||||
panic!("{} was not found in {}", stringify!($name), stringify!($types));
|
||||
}
|
||||
)+
|
||||
}}
|
||||
}
|
||||
|
||||
/// Asserts that a set of the dummy types do not exist in a registry.
|
||||
macro_rules! assert_not_in_types {
|
||||
($($name:ident)+ => $types:expr) => {{
|
||||
$(
|
||||
if $name::is_in_types(&$types) {
|
||||
panic!("{} was found in {}", stringify!($name), stringify!($types));
|
||||
}
|
||||
)+
|
||||
}}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
enum DummyDispatchError {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
}
|
||||
|
||||
impl scale_info::TypeInfo for DummyDispatchError {
|
||||
type Identity = DummyDispatchError;
|
||||
|
||||
fn type_info() -> scale_info::Type {
|
||||
scale_info::Type {
|
||||
path: scale_info::Path {
|
||||
segments: vec!["sp_runtime", "DispatchError"],
|
||||
},
|
||||
type_params: vec![],
|
||||
type_def: scale_info::TypeDef::Variant(scale_info::TypeDefVariant {
|
||||
variants: vec![],
|
||||
}),
|
||||
docs: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v14_stripping_works() {
|
||||
make_types!(A B C D E);
|
||||
|
||||
let pallets = vec![
|
||||
v14::PalletMetadata {
|
||||
name: "First",
|
||||
index: 0,
|
||||
calls: None,
|
||||
storage: Some(v14::PalletStorageMetadata {
|
||||
prefix: "___",
|
||||
entries: vec![v14::StorageEntryMetadata {
|
||||
name: "Hello",
|
||||
modifier: v14::StorageEntryModifier::Optional,
|
||||
ty: frame_metadata::v14::StorageEntryType::Plain(meta_type::<A>()),
|
||||
default: vec![],
|
||||
docs: vec![],
|
||||
}],
|
||||
}),
|
||||
event: Some(v14::PalletEventMetadata {
|
||||
ty: meta_type::<B>(),
|
||||
}),
|
||||
constants: vec![],
|
||||
error: None,
|
||||
},
|
||||
v14::PalletMetadata {
|
||||
name: "Second",
|
||||
index: 1,
|
||||
calls: Some(v15::PalletCallMetadata {
|
||||
ty: meta_type::<C>(),
|
||||
}),
|
||||
storage: None,
|
||||
event: None,
|
||||
constants: vec![v14::PalletConstantMetadata {
|
||||
name: "SomeConstant",
|
||||
ty: meta_type::<D>(),
|
||||
value: vec![],
|
||||
docs: vec![],
|
||||
}],
|
||||
error: None,
|
||||
},
|
||||
];
|
||||
|
||||
let extrinsic = v14::ExtrinsicMetadata {
|
||||
version: 0,
|
||||
signed_extensions: vec![],
|
||||
ty: meta_type::<E>(),
|
||||
};
|
||||
|
||||
let metadata =
|
||||
v14::RuntimeMetadataV14::new(pallets, extrinsic, meta_type::<DummyDispatchError>());
|
||||
|
||||
assert_eq!(metadata.types.types.len(), 6);
|
||||
assert_is_in_types!(A B C D E => metadata.types);
|
||||
|
||||
let only_first_pallet = {
|
||||
let mut md = metadata.clone();
|
||||
md.strip_metadata(|name| name == "First", |_| true);
|
||||
md
|
||||
};
|
||||
|
||||
assert_eq!(only_first_pallet.types.types.len(), 4);
|
||||
assert_is_in_types!(A B E => only_first_pallet.types);
|
||||
assert_not_in_types!(C D => only_first_pallet.types);
|
||||
assert_eq!(only_first_pallet.pallets.len(), 1);
|
||||
assert_eq!(&only_first_pallet.pallets[0].name, "First");
|
||||
|
||||
let only_second_pallet = {
|
||||
let mut md = metadata.clone();
|
||||
md.strip_metadata(|name| name == "Second", |_| true);
|
||||
md
|
||||
};
|
||||
|
||||
assert_eq!(only_second_pallet.types.types.len(), 4);
|
||||
assert_is_in_types!(C D E => only_second_pallet.types);
|
||||
assert_not_in_types!(A B => only_second_pallet.types);
|
||||
assert_eq!(only_second_pallet.pallets.len(), 1);
|
||||
assert_eq!(&only_second_pallet.pallets[0].name, "Second");
|
||||
|
||||
let no_pallets = {
|
||||
let mut md = metadata.clone();
|
||||
md.strip_metadata(|_| false, |_| true);
|
||||
md
|
||||
};
|
||||
|
||||
assert_eq!(no_pallets.types.types.len(), 2);
|
||||
assert_is_in_types!(E => no_pallets.types);
|
||||
assert_not_in_types!(A B C D => no_pallets.types);
|
||||
assert_eq!(no_pallets.pallets.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v15_stripping_works() {
|
||||
make_types!(A B C D E F G H I J K L M N O P);
|
||||
|
||||
let pallets = vec![
|
||||
v15::PalletMetadata {
|
||||
name: "First",
|
||||
index: 0,
|
||||
calls: None,
|
||||
storage: Some(v15::PalletStorageMetadata {
|
||||
prefix: "___",
|
||||
entries: vec![v15::StorageEntryMetadata {
|
||||
name: "Hello",
|
||||
modifier: v15::StorageEntryModifier::Optional,
|
||||
ty: frame_metadata::v15::StorageEntryType::Plain(meta_type::<A>()),
|
||||
default: vec![],
|
||||
docs: vec![],
|
||||
}],
|
||||
}),
|
||||
event: Some(v15::PalletEventMetadata {
|
||||
ty: meta_type::<B>(),
|
||||
}),
|
||||
constants: vec![],
|
||||
error: None,
|
||||
docs: vec![],
|
||||
},
|
||||
v15::PalletMetadata {
|
||||
name: "Second",
|
||||
index: 1,
|
||||
calls: Some(v15::PalletCallMetadata {
|
||||
ty: meta_type::<C>(),
|
||||
}),
|
||||
storage: None,
|
||||
event: None,
|
||||
constants: vec![v15::PalletConstantMetadata {
|
||||
name: "SomeConstant",
|
||||
ty: meta_type::<D>(),
|
||||
value: vec![],
|
||||
docs: vec![],
|
||||
}],
|
||||
error: None,
|
||||
docs: vec![],
|
||||
},
|
||||
];
|
||||
|
||||
let extrinsic = v15::ExtrinsicMetadata {
|
||||
version: 0,
|
||||
signed_extensions: vec![],
|
||||
call_ty: meta_type::<E>(),
|
||||
address_ty: meta_type::<F>(),
|
||||
signature_ty: meta_type::<G>(),
|
||||
extra_ty: meta_type::<H>(),
|
||||
};
|
||||
|
||||
let runtime_apis = vec![
|
||||
v15::RuntimeApiMetadata {
|
||||
name: "SomeApi",
|
||||
docs: vec![],
|
||||
methods: vec![v15::RuntimeApiMethodMetadata {
|
||||
name: "some_method",
|
||||
inputs: vec![v15::RuntimeApiMethodParamMetadata {
|
||||
name: "input1",
|
||||
ty: meta_type::<I>(),
|
||||
}],
|
||||
output: meta_type::<J>(),
|
||||
docs: vec![],
|
||||
}],
|
||||
},
|
||||
v15::RuntimeApiMetadata {
|
||||
name: "AnotherApi",
|
||||
docs: vec![],
|
||||
methods: vec![v15::RuntimeApiMethodMetadata {
|
||||
name: "another_method",
|
||||
inputs: vec![v15::RuntimeApiMethodParamMetadata {
|
||||
name: "input1",
|
||||
ty: meta_type::<K>(),
|
||||
}],
|
||||
output: meta_type::<L>(),
|
||||
docs: vec![],
|
||||
}],
|
||||
},
|
||||
];
|
||||
|
||||
let outer_enums = v15::OuterEnums {
|
||||
call_enum_ty: meta_type::<M>(),
|
||||
error_enum_ty: meta_type::<N>(),
|
||||
event_enum_ty: meta_type::<O>(),
|
||||
};
|
||||
|
||||
let custom_values = v15::CustomMetadata {
|
||||
map: BTreeMap::from_iter(vec![(
|
||||
"Item",
|
||||
v15::CustomValueMetadata {
|
||||
ty: meta_type::<P>(),
|
||||
value: vec![],
|
||||
},
|
||||
)]),
|
||||
};
|
||||
|
||||
let metadata = v15::RuntimeMetadataV15::new(
|
||||
pallets,
|
||||
extrinsic,
|
||||
meta_type::<DummyDispatchError>(),
|
||||
runtime_apis,
|
||||
outer_enums,
|
||||
custom_values,
|
||||
);
|
||||
|
||||
assert_is_in_types!(A B C D E F G H I J K L M N O P => metadata.types);
|
||||
|
||||
let only_first_pallet = {
|
||||
let mut md = metadata.clone();
|
||||
md.strip_metadata(|name| name == "First", |_| true);
|
||||
md
|
||||
};
|
||||
|
||||
assert_is_in_types!(A B E F G H I J K L M N O P => only_first_pallet.types);
|
||||
assert_not_in_types!(C D => only_first_pallet.types);
|
||||
assert_eq!(only_first_pallet.pallets.len(), 1);
|
||||
assert_eq!(&only_first_pallet.pallets[0].name, "First");
|
||||
|
||||
let only_second_pallet = {
|
||||
let mut md = metadata.clone();
|
||||
md.strip_metadata(|name| name == "Second", |_| true);
|
||||
md
|
||||
};
|
||||
|
||||
assert_is_in_types!(C D E F G H I J K L M N O P => only_second_pallet.types);
|
||||
assert_not_in_types!(A B => only_second_pallet.types);
|
||||
assert_eq!(only_second_pallet.pallets.len(), 1);
|
||||
assert_eq!(&only_second_pallet.pallets[0].name, "Second");
|
||||
|
||||
let no_pallets = {
|
||||
let mut md = metadata.clone();
|
||||
md.strip_metadata(|_| false, |_| true);
|
||||
md
|
||||
};
|
||||
|
||||
assert_is_in_types!(E F G H I J K L M N O P => no_pallets.types);
|
||||
assert_not_in_types!(A B C D => no_pallets.types);
|
||||
assert_eq!(no_pallets.pallets.len(), 0);
|
||||
|
||||
let only_second_runtime_api = {
|
||||
let mut md = metadata.clone();
|
||||
md.strip_metadata(|_| true, |api| api == "AnotherApi");
|
||||
md
|
||||
};
|
||||
|
||||
assert_is_in_types!(A B C D E F G H K L M N O P => only_second_runtime_api.types);
|
||||
assert_not_in_types!(I J => only_second_runtime_api.types);
|
||||
assert_eq!(only_second_runtime_api.pallets.len(), 2);
|
||||
assert_eq!(only_second_runtime_api.apis.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v16_stripping_works() {
|
||||
make_types!(A B C D E F G H I J K L M N O P);
|
||||
|
||||
let pallets = vec![
|
||||
v16::PalletMetadata {
|
||||
name: "First",
|
||||
index: 0,
|
||||
calls: None,
|
||||
storage: Some(v16::PalletStorageMetadata {
|
||||
prefix: "___",
|
||||
entries: vec![v16::StorageEntryMetadata {
|
||||
name: "Hello",
|
||||
modifier: v16::StorageEntryModifier::Optional,
|
||||
ty: frame_metadata::v16::StorageEntryType::Plain(meta_type::<A>()),
|
||||
default: vec![],
|
||||
docs: vec![],
|
||||
deprecation_info: v16::DeprecationStatus::NotDeprecated,
|
||||
}],
|
||||
}),
|
||||
event: Some(v16::PalletEventMetadata {
|
||||
ty: meta_type::<B>(),
|
||||
deprecation_info: v16::DeprecationInfo::NotDeprecated,
|
||||
}),
|
||||
constants: vec![],
|
||||
associated_types: vec![],
|
||||
view_functions: vec![],
|
||||
error: None,
|
||||
docs: vec![],
|
||||
deprecation_info: v16::DeprecationStatus::NotDeprecated,
|
||||
},
|
||||
v16::PalletMetadata {
|
||||
name: "Second",
|
||||
index: 1,
|
||||
calls: Some(v16::PalletCallMetadata {
|
||||
ty: meta_type::<C>(),
|
||||
deprecation_info: v16::DeprecationInfo::NotDeprecated,
|
||||
}),
|
||||
storage: None,
|
||||
event: None,
|
||||
constants: vec![v16::PalletConstantMetadata {
|
||||
name: "SomeConstant",
|
||||
ty: meta_type::<D>(),
|
||||
value: vec![],
|
||||
docs: vec![],
|
||||
deprecation_info: v16::DeprecationStatus::NotDeprecated,
|
||||
}],
|
||||
associated_types: vec![v16::PalletAssociatedTypeMetadata {
|
||||
name: "Hasher",
|
||||
ty: meta_type::<E>(),
|
||||
docs: vec![],
|
||||
}],
|
||||
view_functions: vec![v16::PalletViewFunctionMetadata {
|
||||
name: "some_view_function",
|
||||
id: [0; 32],
|
||||
inputs: vec![v16::PalletViewFunctionParamMetadata {
|
||||
name: "input1",
|
||||
ty: meta_type::<F>(),
|
||||
}],
|
||||
output: meta_type::<G>(),
|
||||
docs: vec![],
|
||||
deprecation_info: v16::DeprecationStatus::NotDeprecated,
|
||||
}],
|
||||
error: None,
|
||||
docs: vec![],
|
||||
deprecation_info: v16::DeprecationStatus::NotDeprecated,
|
||||
},
|
||||
];
|
||||
|
||||
let extrinsic = v16::ExtrinsicMetadata {
|
||||
versions: vec![0],
|
||||
transaction_extensions_by_version: BTreeMap::new(),
|
||||
transaction_extensions: vec![],
|
||||
address_ty: meta_type::<H>(),
|
||||
signature_ty: meta_type::<I>(),
|
||||
};
|
||||
|
||||
let runtime_apis = vec![
|
||||
v16::RuntimeApiMetadata {
|
||||
name: "SomeApi",
|
||||
version: 2,
|
||||
docs: vec![],
|
||||
deprecation_info: v16::DeprecationStatus::NotDeprecated,
|
||||
methods: vec![v16::RuntimeApiMethodMetadata {
|
||||
name: "some_method",
|
||||
inputs: vec![v16::RuntimeApiMethodParamMetadata {
|
||||
name: "input1",
|
||||
ty: meta_type::<J>(),
|
||||
}],
|
||||
output: meta_type::<K>(),
|
||||
docs: vec![],
|
||||
deprecation_info: v16::DeprecationStatus::NotDeprecated,
|
||||
}],
|
||||
},
|
||||
v16::RuntimeApiMetadata {
|
||||
name: "AnotherApi",
|
||||
version: 1,
|
||||
docs: vec![],
|
||||
deprecation_info: v16::DeprecationStatus::NotDeprecated,
|
||||
methods: vec![v16::RuntimeApiMethodMetadata {
|
||||
name: "another_method",
|
||||
inputs: vec![v16::RuntimeApiMethodParamMetadata {
|
||||
name: "input1",
|
||||
ty: meta_type::<L>(),
|
||||
}],
|
||||
output: meta_type::<M>(),
|
||||
docs: vec![],
|
||||
deprecation_info: v16::DeprecationStatus::NotDeprecated,
|
||||
}],
|
||||
},
|
||||
];
|
||||
|
||||
let outer_enums = v16::OuterEnums {
|
||||
call_enum_ty: meta_type::<N>(),
|
||||
error_enum_ty: meta_type::<O>(),
|
||||
event_enum_ty: meta_type::<P>(),
|
||||
};
|
||||
|
||||
let custom_values = v16::CustomMetadata {
|
||||
map: BTreeMap::from_iter(vec![(
|
||||
"Item",
|
||||
v16::CustomValueMetadata {
|
||||
ty: meta_type::<DummyDispatchError>(),
|
||||
value: vec![],
|
||||
},
|
||||
)]),
|
||||
};
|
||||
|
||||
let metadata = v16::RuntimeMetadataV16::new(
|
||||
pallets,
|
||||
extrinsic,
|
||||
runtime_apis,
|
||||
outer_enums,
|
||||
custom_values,
|
||||
);
|
||||
|
||||
assert_is_in_types!(A B C D E F G H I J K L M N O P => metadata.types);
|
||||
|
||||
let only_first_pallet = {
|
||||
let mut md = metadata.clone();
|
||||
md.strip_metadata(|name| name == "First", |_| true);
|
||||
md
|
||||
};
|
||||
|
||||
assert_is_in_types!(A B H I J K L M N O P => only_first_pallet.types);
|
||||
assert_not_in_types!(C D E F G => only_first_pallet.types);
|
||||
assert_eq!(only_first_pallet.pallets.len(), 1);
|
||||
assert_eq!(&only_first_pallet.pallets[0].name, "First");
|
||||
|
||||
let only_second_pallet = {
|
||||
let mut md = metadata.clone();
|
||||
md.strip_metadata(|name| name == "Second", |_| true);
|
||||
md
|
||||
};
|
||||
|
||||
assert_is_in_types!(C D E F G H I J K L M N O P => only_second_pallet.types);
|
||||
assert_not_in_types!(A B => only_second_pallet.types);
|
||||
assert_eq!(only_second_pallet.pallets.len(), 1);
|
||||
assert_eq!(&only_second_pallet.pallets[0].name, "Second");
|
||||
|
||||
let no_pallets = {
|
||||
let mut md = metadata.clone();
|
||||
md.strip_metadata(|_| false, |_| true);
|
||||
md
|
||||
};
|
||||
|
||||
assert_is_in_types!(H I J K L M N O P => no_pallets.types);
|
||||
assert_not_in_types!(A B C D E F G => no_pallets.types);
|
||||
assert_eq!(no_pallets.pallets.len(), 0);
|
||||
|
||||
let only_second_runtime_api = {
|
||||
let mut md = metadata.clone();
|
||||
md.strip_metadata(|_| true, |api| api == "AnotherApi");
|
||||
md
|
||||
};
|
||||
|
||||
assert_is_in_types!(A B C D E F G H I L M N O P => only_second_runtime_api.types);
|
||||
assert_not_in_types!(J K => only_second_runtime_api.types);
|
||||
assert_eq!(only_second_runtime_api.pallets.len(), 2);
|
||||
assert_eq!(only_second_runtime_api.apis.len(), 1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user