diff --git a/Cargo.lock b/Cargo.lock index 85b2ff8b6c5..eb1f8054744 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 45d3dfe93c9..89db11429e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index e980a8b3ea2..6b91fea4254 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -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 } diff --git a/cli/src/commands/metadata.rs b/cli/src/commands/metadata.rs index 3185442e990..a9342eda437 100644 --- a/cli/src/commands/metadata.rs +++ b/cli/src/commands/metadata.rs @@ -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, - }; + let mut metadata = RuntimeMetadataPrefixed::decode(&mut &bytes[..])?; + // 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 bool> = match opts.pallets.as_ref() { + let keep_pallets_fn: Box 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 bool> = match opts.runtime_apis.as_ref() { + let keep_runtime_apis_fn: Box 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, -} diff --git a/codegen/src/api/mod.rs b/codegen/src/api/mod.rs index 929d93df43c..aa7f345fd13 100644 --- a/codegen/src/api/mod.rs +++ b/codegen/src/api/mod.rs @@ -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! { diff --git a/codegen/src/api/runtime_apis.rs b/codegen/src/api/runtime_apis.rs index 2377577be1b..57dd10cef9c 100644 --- a/codegen/src/api/runtime_apis.rs +++ b/codegen/src/api/runtime_apis.rs @@ -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 { @@ -147,7 +141,7 @@ fn generate_runtime_api( Ok((struct_input, method)) }) - .collect::>()?; + .collect::, CodegenError>>()?; let trait_name = format_ident!("{}", trait_name_str); diff --git a/core/src/config/transaction_extensions.rs b/core/src/config/transaction_extensions.rs index 529ed941099..9c73fbe633e 100644 --- a/core/src/config/transaction_extensions.rs +++ b/core/src/config/transaction_extensions.rs @@ -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 diff --git a/core/src/runtime_api/mod.rs b/core/src/runtime_api/mod.rs index ad65b52b297..9bdab6f50af 100644 --- a/core/src/runtime_api/mod.rs +++ b/core/src/runtime_api/mod.rs @@ -61,10 +61,11 @@ pub fn validate(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()); } diff --git a/core/src/tx/mod.rs b/core/src/tx/mod.rs index 664b28dcede..ec0b23925a1 100644 --- a/core/src/tx/mod.rs +++ b/core/src/tx/mod.rs @@ -205,12 +205,11 @@ pub fn create_v5_general( // 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)?; diff --git a/examples/wasm-example/Cargo.lock b/examples/wasm-example/Cargo.lock index 010917d83d4..27797ca0c76 100644 --- a/examples/wasm-example/Cargo.lock +++ b/examples/wasm-example/Cargo.lock @@ -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", diff --git a/examples/wasm-example/src/services.rs b/examples/wasm-example/src/services.rs index 270a95f8606..ad9b82298dc 100644 --- a/examples/wasm-example/src/services.rs +++ b/examples/wasm-example/src/services.rs @@ -140,8 +140,8 @@ pub async fn extension_signature_for_extrinsic( let signed_extensions: Vec = 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)); diff --git a/metadata/Cargo.toml b/metadata/Cargo.toml index 1c8bcbfb864..425dc6698dc 100644 --- a/metadata/Cargo.toml +++ b/metadata/Cargo.toml @@ -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: diff --git a/metadata/src/from_into/mod.rs b/metadata/src/from/mod.rs similarity index 94% rename from metadata/src/from_into/mod.rs rename to metadata/src/from/mod.rs index 9f8f93a7bf5..65dce2d0577 100644 --- a/metadata/src/from_into/mod.rs +++ b/metadata/src/from/mod.rs @@ -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 for frame_metadata::RuntimeMetadataPrefixed { - fn from(value: crate::Metadata) -> Self { - let m: frame_metadata::v15::RuntimeMetadataV15 = value.into(); - m.into() - } -} - impl TryFrom for crate::Metadata { type Error = TryFromError; diff --git a/metadata/src/from/v14.rs b/metadata/src/from/v14.rs new file mode 100644 index 00000000000..02c713829a7 --- /dev/null +++ b/metadata/src/from/v14.rs @@ -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 for Metadata { + type Error = TryFromError; + fn try_from(mut m: v14::RuntimeMetadataV14) -> Result { + 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, +) -> TransactionExtensionMetadataInner { + TransactionExtensionMetadataInner { + identifier: value.identifier, + extra_ty: value.ty.id, + additional_ty: value.additional_signed.id, + } +} + +fn from_extrinsic_metadata( + value: v14::ExtrinsicMetadata, + 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) -> 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, +) -> 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, +) -> ConstantMetadata { + ConstantMetadata { + name, + ty: s.ty.id, + value: s.value, + docs: s.docs, + } +} + +fn generate_outer_enums( + metadata: &mut v14::RuntimeMetadataV14, +) -> Result, 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, +) -> 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 { + 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 { + 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, + /// The RuntimeEvent type ID. + pub event_ty: Option, + /// The RuntimeError type ID. + pub error_ty: Option, +} + +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"), + } + } +} diff --git a/metadata/src/from/v15.rs b/metadata/src/from/v15.rs new file mode 100644 index 00000000000..ebfb6231678 --- /dev/null +++ b/metadata/src/from/v15.rs @@ -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 for Metadata { + type Error = TryFromError; + fn try_from(m: v15::RuntimeMetadataV15) -> Result { + 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, +) -> TransactionExtensionMetadataInner { + TransactionExtensionMetadataInner { + identifier: value.identifier, + extra_ty: value.ty.id, + additional_ty: value.additional_signed.id, + } +} + +fn from_extrinsic_metadata(value: v15::ExtrinsicMetadata) -> 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) -> 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, +) -> 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, +) -> ConstantMetadata { + ConstantMetadata { + name, + ty: s.ty.id, + value: s.value, + docs: s.docs, + } +} + +fn from_runtime_api_metadata( + name: ArcStr, + s: v15::RuntimeApiMetadata, +) -> 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, +) -> 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, +) -> MethodParamMetadata { + MethodParamMetadata { + name: s.name, + ty: s.ty.id, + } +} diff --git a/metadata/src/from/v16.rs b/metadata/src/from/v16.rs new file mode 100644 index 00000000000..419136d90f8 --- /dev/null +++ b/metadata/src/from/v16.rs @@ -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 for Metadata { + type Error = TryFromError; + fn try_from(m: v16::RuntimeMetadataV16) -> Result { + 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, +) -> TransactionExtensionMetadataInner { + TransactionExtensionMetadataInner { + identifier: value.identifier, + extra_ty: value.ty.id, + additional_ty: value.implicit.id, + } +} + +fn from_extrinsic_metadata(value: v16::ExtrinsicMetadata) -> 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) -> 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, +) -> 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, +) -> ConstantMetadata { + ConstantMetadata { + name, + ty: s.ty.id, + value: s.value, + docs: s.docs, + } +} + +fn from_runtime_api_metadata( + name: ArcStr, + s: v16::RuntimeApiMetadata, +) -> 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, +) -> 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, +) -> 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, + } +} diff --git a/metadata/src/from_into/v14.rs b/metadata/src/from_into/v14.rs deleted file mode 100644 index 5171e801179..00000000000 --- a/metadata/src/from_into/v14.rs +++ /dev/null @@ -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 for Metadata { - type Error = TryFromError; - fn try_from(value: v14::RuntimeMetadataV14) -> Result { - // Convert to v15 and then convert that into Metadata. - v14_to_v15(value)?.try_into() - } -} - -impl From 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:: { - name: "Address".to_owned(), - ty: Some(metadata.extrinsic.address_ty), - }, - scale_info::TypeParameter:: { - name: "Call".to_owned(), - ty: Some(metadata.extrinsic.call_ty), - }, - scale_info::TypeParameter:: { - name: "Signature".to_owned(), - ty: Some(metadata.extrinsic.signature_ty), - }, - scale_info::TypeParameter:: { - 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 { - // 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 { - 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 { - 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, 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, -) -> 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::()); - - 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 { - _phantom: PhantomData<(Address, Signature, Extra)>, - } - let metadata = generate_metadata(meta_type::>()); - let err = v14_to_v15(metadata).unwrap_err(); - assert_eq!(err, TryFromError::TypeNameNotFound("Call".into())); - - #[derive(TypeInfo)] - struct ExtrinsicNoSign { - _phantom: PhantomData<(Call, Address, Extra)>, - } - let metadata = generate_metadata(meta_type::>()); - let err = v14_to_v15(metadata).unwrap_err(); - assert_eq!(err, TryFromError::TypeNameNotFound("Signature".into())); - - #[derive(TypeInfo)] - struct ExtrinsicNoExtra { - _phantom: PhantomData<(Call, Address, Signature)>, - } - let metadata = generate_metadata(meta_type::>()); - 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 { - 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::()); - registry.register_type(&meta_type::()); - - let extrinsic = ExtrinsicMetadata { - ty: meta_type::>(), - 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::()); - registry.register_type(&meta_type::()); - - let extrinsic = ExtrinsicMetadata { - ty: meta_type::>(), - 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())); - } - } -} diff --git a/metadata/src/from_into/v15.rs b/metadata/src/from_into/v15.rs deleted file mode 100644 index bfec3c9bea2..00000000000 --- a/metadata/src/from_into/v15.rs +++ /dev/null @@ -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 for Metadata { - type Error = TryFromError; - fn try_from(m: v15::RuntimeMetadataV15) -> Result { - 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, - ) -> TransactionExtensionMetadata { - TransactionExtensionMetadata { - identifier: value.identifier, - extra_ty: value.ty.id, - additional_ty: value.additional_signed.id, - } - } - - fn from_extrinsic_metadata(value: v15::ExtrinsicMetadata) -> 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) -> 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, - ) -> 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, - ) -> ConstantMetadata { - ConstantMetadata { - name, - ty: s.ty.id, - value: s.value, - docs: s.docs, - } - } - - fn from_runtime_api_metadata( - name: ArcStr, - s: v15::RuntimeApiMetadata, - ) -> 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, - ) -> 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, - ) -> RuntimeApiMethodParamMetadata { - RuntimeApiMethodParamMetadata { - name: s.name, - ty: s.ty.id, - } - } -} - -// Converting from our metadata repr to V15 metadata. -mod into_v15 { - use super::*; - - impl From 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 { - 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 { - 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 { - v15::RuntimeApiMethodParamMetadata { - name: p.name, - ty: p.ty.into(), - } - } - - fn from_extrinsic_metadata(e: ExtrinsicMetadata) -> v15::ExtrinsicMetadata { - 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 { - v15::SignedExtensionMetadata { - identifier: s.identifier, - ty: s.extra_ty.into(), - additional_signed: s.additional_ty.into(), - } - } - - fn from_constant_metadata(c: ConstantMetadata) -> v15::PalletConstantMetadata { - 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 { - 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 { - 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, - } - } -} diff --git a/metadata/src/lib.rs b/metadata/src/lib.rs index 346d503788a..9d8427319ab 100644 --- a/metadata/src/lib.rs +++ b/metadata/src/lib.rs @@ -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; -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; + /// 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, /// 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, /// Allows users to add custom types to the metadata. A map that associates a string key to a `CustomValueMetadata`. - custom: frame_metadata::v15::CustomMetadata, + 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, 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, ) -> Result, 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() + }); + + 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: self - .extrinsic() - .transaction_extensions() - .iter() - .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 { 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(&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> { + 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 { + 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 { + 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]> { 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, + /// Details about each of the pallet view functions. + view_functions: Vec, + /// Mapping from associated type to type ID describing its shape. + associated_types: BTreeMap, /// Pallet documentation. docs: Vec, } @@ -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, /// The signed extensions in the order they appear in the extrinsic. - transaction_extensions: Vec, - /// 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, + /// 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>, } 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>> { + 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> { + 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. +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. + additional_ty: u32, +} + +#[derive(Debug, Clone)] +struct TransactionExtensionMetadataInner { identifier: String, - /// The type of the signed 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 additional_ty: u32, } -impl TransactionExtensionMetadata { +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 { - self.inner.methods.values().iter() + pub fn methods(&self) -> impl ExactSizeIterator> { + 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) - } - /// 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) + pub fn method_by_name(&self, name: &str) -> Option> { + 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 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, + methods: OrderedMap, /// Trait documentation. docs: Vec, } /// 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 { + 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, + inputs: Vec, /// Method output type. output_ty: u32, /// Method documentation. docs: Vec, } -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 { - self.inputs.iter() + pub fn inputs(&self) -> impl ExactSizeIterator { + 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, + /// Output type. + output_ty: u32, + /// Documentation. + docs: Vec, +} + +/// 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, + 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(&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 { - 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); - } -} diff --git a/metadata/src/utils/mod.rs b/metadata/src/utils/mod.rs index d4939130c30..1aa04a2c241 100644 --- a/metadata/src/utils/mod.rs +++ b/metadata/src/utils/mod.rs @@ -3,6 +3,5 @@ // see LICENSE for license details. pub mod ordered_map; -pub mod retain; pub mod validation; pub mod variant_index; diff --git a/metadata/src/utils/ordered_map.rs b/metadata/src/utils/ordered_map.rs index 2a1d4a8383b..2a7eb826021 100644 --- a/metadata/src/utils/ordered_map.rs +++ b/metadata/src/utils/ordered_map.rs @@ -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(&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 { - self.values - } } impl FromIterator<(K, V)> for OrderedMap @@ -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']) - } -} diff --git a/metadata/src/utils/retain.rs b/metadata/src/utils/retain.rs deleted file mode 100644 index d476a54acb0..00000000000 --- a/metadata/src/utils/retain.rs +++ /dev/null @@ -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, - pub work_set: Vec, -} - -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( - 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 = - 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(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 { - 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( - 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) -> 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::>(); - 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) - ); - } -} diff --git a/metadata/src/utils/validation.rs b/metadata/src/utils/validation.rs index ea58fff7d3e..2237571f17f 100644 --- a/metadata/src/utils/validation.rs +++ b/metadata/src/utils/validation.rs @@ -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, cache: &mut HashMap, - 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, cache: &mut HashMap, - 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, only_these_variants: Option<&[&str]>, cache: &mut HashMap, - 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, cache: &mut HashMap, - 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, - 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 { 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 { 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 { - let trait_name = &*runtime_apis.inner.name; - let method_metadata = runtime_apis.method_by_name(method_name)?; - - Some(get_runtime_method_hash( - runtime_apis.types, - trait_name, - method_metadata, - &OuterEnumHashes::empty(), - )) +/// 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; + + // 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"]) diff --git a/metadata/src/utils/validation/outer_enum_hashes.rs b/metadata/src/utils/validation/outer_enum_hashes.rs deleted file mode 100644 index 9144c6b6a6d..00000000000 --- a/metadata/src/utils/validation/outer_enum_hashes.rs +++ /dev/null @@ -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, - } - } -} diff --git a/testing/integration-tests/src/full_client/client/archive_rpcs.rs b/testing/integration-tests/src/full_client/client/archive_rpcs.rs index 4b7c3c43c90..847424098b0 100644 --- a/testing/integration-tests/src/full_client/client/archive_rpcs.rs +++ b/testing/integration-tests/src/full_client/client/archive_rpcs.rs @@ -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; } } diff --git a/testing/integration-tests/src/full_client/metadata_validation.rs b/testing/integration-tests/src/full_client/metadata_validation.rs index edb0dc39039..fcc6dab9b5a 100644 --- a/testing/integration-tests/src/full_client/metadata_validation.rs +++ b/testing/integration-tests/src/full_client/metadata_validation.rs @@ -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) -> 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 { OfflineClient::new( @@ -27,15 +52,6 @@ fn v15_to_metadata(v15: RuntimeMetadataV15) -> Metadata { subxt_md.into() } -fn modified_metadata(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) -> 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"); - - // Modifying a constant value should not lead to an error: - existential.value = vec![0u8; 32]; - }); - - let api = metadata_to_api(metadata, &ctx).await; + 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]; + + // 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] diff --git a/testing/ui-tests/Cargo.toml b/testing/ui-tests/Cargo.toml index 6582f45ed14..2bf67474305 100644 --- a/testing/ui-tests/Cargo.toml +++ b/testing/ui-tests/Cargo.toml @@ -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" } diff --git a/testing/ui-tests/src/lib.rs b/testing/ui-tests/src/lib.rs index 2909dfb312e..04276ff062d 100644 --- a/testing/ui-tests/src/lib.rs +++ b/testing/ui-tests/src/lib.rs @@ -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( + 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() diff --git a/testing/ui-tests/src/utils/metadata_test_runner.rs b/testing/ui-tests/src/utils/metadata_test_runner.rs index 13d9d78f2ed..f8a08148a34 100644 --- a/testing/ui-tests/src/utils/metadata_test_runner.rs +++ b/testing/ui-tests/src/utils/metadata_test_runner.rs @@ -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, + validation_metadata: Option, 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) -> Self { + pub fn validation_metadata(mut self, md: impl Into) -> 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(self, macro_metadata: M) -> String - where - M: TryInto, - 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(item: &T) -> T { + let bytes = item.encode(); + T::decode(&mut &*bytes).unwrap() +} diff --git a/utils/fetch-metadata/Cargo.toml b/utils/fetch-metadata/Cargo.toml index c79e61e95bd..1fcf7613438 100644 --- a/utils/fetch-metadata/Cargo.toml +++ b/utils/fetch-metadata/Cargo.toml @@ -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"] diff --git a/utils/strip-metadata/Cargo.toml b/utils/strip-metadata/Cargo.toml new file mode 100644 index 00000000000..2cd85616a27 --- /dev/null +++ b/utils/strip-metadata/Cargo.toml @@ -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 \ No newline at end of file diff --git a/utils/strip-metadata/src/lib.rs b/utils/strip-metadata/src/lib.rs new file mode 100644 index 00000000000..1b9f3a6dd44 --- /dev/null +++ b/utils/strip-metadata/src/lib.rs @@ -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( + &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( + &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( + &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( + &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: &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 = 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; +} + +impl IterateTypeIds for v14::RuntimeMetadataV14 { + fn iter_type_ids_mut(&mut self) -> impl Iterator { + // 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 { + // 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 { + // 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::()), + default: vec![], + docs: vec![], + }], + }), + event: Some(v14::PalletEventMetadata { + ty: meta_type::(), + }), + constants: vec![], + error: None, + }, + v14::PalletMetadata { + name: "Second", + index: 1, + calls: Some(v15::PalletCallMetadata { + ty: meta_type::(), + }), + storage: None, + event: None, + constants: vec![v14::PalletConstantMetadata { + name: "SomeConstant", + ty: meta_type::(), + value: vec![], + docs: vec![], + }], + error: None, + }, + ]; + + let extrinsic = v14::ExtrinsicMetadata { + version: 0, + signed_extensions: vec![], + ty: meta_type::(), + }; + + let metadata = + v14::RuntimeMetadataV14::new(pallets, extrinsic, meta_type::()); + + 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::()), + default: vec![], + docs: vec![], + }], + }), + event: Some(v15::PalletEventMetadata { + ty: meta_type::(), + }), + constants: vec![], + error: None, + docs: vec![], + }, + v15::PalletMetadata { + name: "Second", + index: 1, + calls: Some(v15::PalletCallMetadata { + ty: meta_type::(), + }), + storage: None, + event: None, + constants: vec![v15::PalletConstantMetadata { + name: "SomeConstant", + ty: meta_type::(), + value: vec![], + docs: vec![], + }], + error: None, + docs: vec![], + }, + ]; + + let extrinsic = v15::ExtrinsicMetadata { + version: 0, + signed_extensions: vec![], + call_ty: meta_type::(), + address_ty: meta_type::(), + signature_ty: meta_type::(), + extra_ty: meta_type::(), + }; + + 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::(), + }], + output: meta_type::(), + docs: vec![], + }], + }, + v15::RuntimeApiMetadata { + name: "AnotherApi", + docs: vec![], + methods: vec![v15::RuntimeApiMethodMetadata { + name: "another_method", + inputs: vec![v15::RuntimeApiMethodParamMetadata { + name: "input1", + ty: meta_type::(), + }], + output: meta_type::(), + docs: vec![], + }], + }, + ]; + + let outer_enums = v15::OuterEnums { + call_enum_ty: meta_type::(), + error_enum_ty: meta_type::(), + event_enum_ty: meta_type::(), + }; + + let custom_values = v15::CustomMetadata { + map: BTreeMap::from_iter(vec![( + "Item", + v15::CustomValueMetadata { + ty: meta_type::

(), + }; + + let custom_values = v16::CustomMetadata { + map: BTreeMap::from_iter(vec![( + "Item", + v16::CustomValueMetadata { + ty: meta_type::(), + 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); + } +}

(), + value: vec![], + }, + )]), + }; + + let metadata = v15::RuntimeMetadataV15::new( + pallets, + extrinsic, + meta_type::(), + 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::()), + default: vec![], + docs: vec![], + deprecation_info: v16::DeprecationStatus::NotDeprecated, + }], + }), + event: Some(v16::PalletEventMetadata { + ty: meta_type::(), + 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::(), + deprecation_info: v16::DeprecationInfo::NotDeprecated, + }), + storage: None, + event: None, + constants: vec![v16::PalletConstantMetadata { + name: "SomeConstant", + ty: meta_type::(), + value: vec![], + docs: vec![], + deprecation_info: v16::DeprecationStatus::NotDeprecated, + }], + associated_types: vec![v16::PalletAssociatedTypeMetadata { + name: "Hasher", + ty: meta_type::(), + docs: vec![], + }], + view_functions: vec![v16::PalletViewFunctionMetadata { + name: "some_view_function", + id: [0; 32], + inputs: vec![v16::PalletViewFunctionParamMetadata { + name: "input1", + ty: meta_type::(), + }], + output: meta_type::(), + 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::(), + signature_ty: meta_type::(), + }; + + 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::(), + }], + output: meta_type::(), + 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::(), + }], + output: meta_type::(), + docs: vec![], + deprecation_info: v16::DeprecationStatus::NotDeprecated, + }], + }, + ]; + + let outer_enums = v16::OuterEnums { + call_enum_ty: meta_type::(), + error_enum_ty: meta_type::(), + event_enum_ty: meta_type::