mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Metadata: Retain a subset of metadata pallets (#879)
* Update cargo.lock to use scale-info v2.4.0 Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Retain only a subset of the metadata Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Generate top level Event Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Only retain DispatchError Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Export just the retain method Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * cli: Retain pallets Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Do not include extrinsic metadata Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * retain: Fix clippy Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * test-runtime: Generate per pallet metadata and rs file Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * ui-tests: Check per metadata generated files Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Revert "test-runtime: Generate per pallet metadata and rs file" This reverts commit 725a2e5f8339a795892dbbd2df19e49330ae3a9b. Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * ui-tests: Adjust path to metadata file Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * ui-tests: Change drop order to keep `PalletMetadata` around Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Update metadata/src/retain.rs Co-authored-by: James Wilson <james@jsdw.me> * Address feedback Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * retain: Keep extrinsic type Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * cli: Introduce `MetadataSource` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * cli: Use `MetadataSource` helper Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * cli: Use `FileOrUrl` flatten command argument Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * retain: Do not include generic type params in retained metadata Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Adjust subxt to scale-info v2.5.0 Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Update scaleinfo to v2.5.0 Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Remove deprecated fn Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * testing: Fix clippy Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * benches: Use inner fields of scale info Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * address nits, and strip RuntimeCall type instead of trying to filter out use of it for better overall wins/clarity * fix UI test * move utils out of commands folder and fix clippy etc * address nits --------- Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> Co-authored-by: James Wilson <james@jsdw.me>
This commit is contained in:
Generated
+1
@@ -3895,6 +3895,7 @@ dependencies = [
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"subxt",
|
||||
"subxt-metadata",
|
||||
"trybuild",
|
||||
]
|
||||
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::utils::FileOrUrl;
|
||||
use clap::Parser as ClapParser;
|
||||
use color_eyre::eyre;
|
||||
use jsonrpsee::client_transport::ws::Uri;
|
||||
use std::{fs, io::Read, path::PathBuf};
|
||||
use subxt_codegen::{DerivesRegistry, TypeSubstitutes, TypeSubstitutionError};
|
||||
|
||||
/// Generate runtime API client code from metadata.
|
||||
@@ -15,12 +14,8 @@ use subxt_codegen::{DerivesRegistry, TypeSubstitutes, TypeSubstitutionError};
|
||||
/// `subxt codegen | rustfmt --edition=2018 --emit=stdout`
|
||||
#[derive(Debug, ClapParser)]
|
||||
pub struct Opts {
|
||||
/// The url of the substrate node to query for metadata for codegen.
|
||||
#[clap(name = "url", long, value_parser)]
|
||||
url: Option<Uri>,
|
||||
/// The path to the encoded metadata file.
|
||||
#[clap(short, long, value_parser)]
|
||||
file: Option<PathBuf>,
|
||||
#[command(flatten)]
|
||||
file_or_url: FileOrUrl,
|
||||
/// Additional derives
|
||||
#[clap(long = "derive")]
|
||||
derives: Vec<String>,
|
||||
@@ -65,23 +60,7 @@ fn substitute_type_parser(src: &str) -> Result<(String, String), String> {
|
||||
}
|
||||
|
||||
pub async fn run(opts: Opts) -> color_eyre::Result<()> {
|
||||
let bytes = if let Some(file) = opts.file.as_ref() {
|
||||
if opts.url.is_some() {
|
||||
eyre::bail!("specify one of `--url` or `--file` but not both")
|
||||
};
|
||||
|
||||
let mut file = fs::File::open(file)?;
|
||||
let mut bytes = Vec::new();
|
||||
file.read_to_end(&mut bytes)?;
|
||||
bytes
|
||||
} else {
|
||||
let url = opts.url.unwrap_or_else(|| {
|
||||
"http://localhost:9933"
|
||||
.parse::<Uri>()
|
||||
.expect("default url is valid")
|
||||
});
|
||||
subxt_codegen::utils::fetch_metadata_bytes(&url).await?
|
||||
};
|
||||
let bytes = opts.file_or_url.fetch().await?;
|
||||
|
||||
codegen(
|
||||
&bytes,
|
||||
|
||||
@@ -2,47 +2,61 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::utils::FileOrUrl;
|
||||
use clap::Parser as ClapParser;
|
||||
use color_eyre::eyre;
|
||||
use frame_metadata::RuntimeMetadataPrefixed;
|
||||
use jsonrpsee::client_transport::ws::Uri;
|
||||
use scale::Decode;
|
||||
use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed};
|
||||
use scale::{Decode, Encode};
|
||||
use std::io::{self, Write};
|
||||
use subxt_codegen::utils::fetch_metadata_hex;
|
||||
use subxt_metadata::retain_metadata_pallets;
|
||||
|
||||
/// Download metadata from a substrate node, for use with `subxt` codegen.
|
||||
#[derive(Debug, ClapParser)]
|
||||
pub struct Opts {
|
||||
/// The url of the substrate node to query for metadata.
|
||||
#[clap(
|
||||
name = "url",
|
||||
long,
|
||||
value_parser,
|
||||
default_value = "http://localhost:9933"
|
||||
)]
|
||||
url: Uri,
|
||||
#[command(flatten)]
|
||||
file_or_url: FileOrUrl,
|
||||
/// The format of the metadata to display: `json`, `hex` or `bytes`.
|
||||
#[clap(long, short, default_value = "bytes")]
|
||||
format: String,
|
||||
/// Generate a subset of the metadata that contains only the
|
||||
/// types needed to represent the provided pallets.
|
||||
#[clap(long, use_value_delimiter = true, value_parser)]
|
||||
pallets: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
pub async fn run(opts: Opts) -> color_eyre::Result<()> {
|
||||
let hex_data = fetch_metadata_hex(&opts.url).await?;
|
||||
let bytes = opts.file_or_url.fetch().await?;
|
||||
let mut metadata = <RuntimeMetadataPrefixed as Decode>::decode(&mut &bytes[..])?;
|
||||
|
||||
if let Some(pallets) = opts.pallets {
|
||||
let metadata_v14 = match &mut metadata.1 {
|
||||
RuntimeMetadata::V14(metadata_v14) => metadata_v14,
|
||||
_ => {
|
||||
return Err(eyre::eyre!(
|
||||
"Unsupported metadata version {:?}, expected V14.",
|
||||
metadata.1
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
retain_metadata_pallets(metadata_v14, |pallet_name| {
|
||||
pallets.iter().any(|p| &**p == pallet_name)
|
||||
});
|
||||
}
|
||||
|
||||
match opts.format.as_str() {
|
||||
"json" => {
|
||||
let bytes = hex::decode(hex_data.trim_start_matches("0x"))?;
|
||||
let metadata = <RuntimeMetadataPrefixed as Decode>::decode(&mut &bytes[..])?;
|
||||
let json = serde_json::to_string_pretty(&metadata)?;
|
||||
println!("{json}");
|
||||
Ok(())
|
||||
}
|
||||
"hex" => {
|
||||
let hex_data = format!("0x{:?}", hex::encode(metadata.encode()));
|
||||
println!("{hex_data}");
|
||||
Ok(())
|
||||
}
|
||||
"bytes" => {
|
||||
let bytes = hex::decode(hex_data.trim_start_matches("0x"))?;
|
||||
let bytes = metadata.encode();
|
||||
Ok(io::stdout().write_all(&bytes)?)
|
||||
}
|
||||
_ => Err(eyre::eyre!(
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#![deny(unused_crate_dependencies)]
|
||||
|
||||
mod commands;
|
||||
mod utils;
|
||||
use clap::Parser as ClapParser;
|
||||
|
||||
/// Subxt utilities for interacting with Substrate based nodes.
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use clap::Args;
|
||||
use color_eyre::eyre;
|
||||
use std::{fs, io::Read, path::PathBuf};
|
||||
use subxt_codegen::utils::Uri;
|
||||
|
||||
/// The source of the metadata.
|
||||
#[derive(Debug, Args)]
|
||||
pub struct FileOrUrl {
|
||||
/// The url of the substrate node to query for metadata for codegen.
|
||||
#[clap(long, value_parser)]
|
||||
url: Option<Uri>,
|
||||
/// The path to the encoded metadata file.
|
||||
#[clap(long, value_parser)]
|
||||
file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl FileOrUrl {
|
||||
/// Fetch the metadata bytes.
|
||||
pub async fn fetch(&self) -> color_eyre::Result<Vec<u8>> {
|
||||
match (&self.file, &self.url) {
|
||||
// Can't provide both --file and --url
|
||||
(Some(_), Some(_)) => {
|
||||
eyre::bail!("specify one of `--url` or `--file` but not both")
|
||||
}
|
||||
// Load from --file path
|
||||
(Some(path), None) => {
|
||||
let mut file = fs::File::open(path)?;
|
||||
let mut bytes = Vec::new();
|
||||
file.read_to_end(&mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
// Fetch from --url
|
||||
(None, Some(uri)) => Ok(subxt_codegen::utils::fetch_metadata_bytes(uri).await?),
|
||||
// Default if neither is provided; fetch from local url
|
||||
(None, None) => {
|
||||
let uri = Uri::from_static("http://localhost:9933");
|
||||
Ok(subxt_codegen::utils::fetch_metadata_bytes(&uri).await?)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
-28
@@ -235,12 +235,13 @@ impl RuntimeGenerator {
|
||||
) -> Result<TokenStream2, CodegenError> {
|
||||
let item_mod_attrs = item_mod.attrs.clone();
|
||||
let item_mod_ir = ir::ItemMod::try_from(item_mod)?;
|
||||
let default_derives = derives.default_derives();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&self.metadata.types,
|
||||
"runtime_types",
|
||||
type_substitutes,
|
||||
derives,
|
||||
derives.clone(),
|
||||
crate_path.clone(),
|
||||
should_gen_docs,
|
||||
);
|
||||
@@ -258,28 +259,6 @@ impl RuntimeGenerator {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Get the path to the `Runtime` struct. We assume that the same path contains
|
||||
// RuntimeCall and RuntimeEvent.
|
||||
let runtime_type_id = self.metadata.ty.id;
|
||||
let runtime_path_segments = self
|
||||
.metadata
|
||||
.types
|
||||
.resolve(runtime_type_id)
|
||||
.ok_or(CodegenError::TypeNotFound(runtime_type_id))?
|
||||
.path
|
||||
.namespace()
|
||||
.iter()
|
||||
.map(|part| syn::PathSegment::from(format_ident!("{}", part)));
|
||||
let runtime_path_suffix = syn::Path {
|
||||
leading_colon: None,
|
||||
segments: syn::punctuated::Punctuated::from_iter(runtime_path_segments),
|
||||
};
|
||||
let runtime_path = if runtime_path_suffix.segments.is_empty() {
|
||||
quote!(#types_mod_ident)
|
||||
} else {
|
||||
quote!(#types_mod_ident::#runtime_path_suffix)
|
||||
};
|
||||
|
||||
// Pallet names and their length are used to create PALLETS array.
|
||||
// The array is used to identify the pallets composing the metadata for
|
||||
// validation of just those pallets.
|
||||
@@ -344,6 +323,26 @@ impl RuntimeGenerator {
|
||||
})
|
||||
.collect::<Result<Vec<_>, CodegenError>>()?;
|
||||
|
||||
let outer_event_variants = self.metadata.pallets.iter().filter_map(|p| {
|
||||
let variant_name = format_ident!("{}", p.name);
|
||||
let mod_name = format_ident!("{}", p.name.to_string().to_snake_case());
|
||||
let index = proc_macro2::Literal::u8_unsuffixed(p.index);
|
||||
|
||||
p.event.as_ref().map(|_| {
|
||||
quote! {
|
||||
#[codec(index = #index)]
|
||||
#variant_name(#mod_name::Event),
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let outer_event = quote! {
|
||||
#default_derives
|
||||
pub enum Event {
|
||||
#( #outer_event_variants )*
|
||||
}
|
||||
};
|
||||
|
||||
let root_event_if_arms = self.metadata.pallets.iter().filter_map(|p| {
|
||||
let variant_name_str = &p.name;
|
||||
let variant_name = format_ident!("{}", variant_name_str);
|
||||
@@ -402,14 +401,10 @@ impl RuntimeGenerator {
|
||||
// Identify the pallets composing the static metadata by name.
|
||||
pub static PALLETS: [&str; #pallet_names_len] = [ #(#pallet_names,)* ];
|
||||
|
||||
/// The statically generated runtime call type.
|
||||
pub type Call = #runtime_path::RuntimeCall;
|
||||
|
||||
/// The error type returned when there is a runtime issue.
|
||||
pub type DispatchError = #types_mod_ident::sp_runtime::DispatchError;
|
||||
|
||||
// Make the runtime event type easily accessible, and impl RootEvent to help decode into it.
|
||||
pub type Event = #runtime_path::RuntimeEvent;
|
||||
#outer_event
|
||||
|
||||
impl #crate_path::events::RootEvent for Event {
|
||||
fn root_event(pallet_bytes: &[u8], pallet_name: &str, pallet_ty: u32, metadata: &#crate_path::Metadata) -> Result<Self, #crate_path::Error> {
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
mod retain;
|
||||
|
||||
use frame_metadata::{
|
||||
ExtrinsicMetadata, RuntimeMetadataV14, StorageEntryMetadata, StorageEntryType,
|
||||
};
|
||||
pub use retain::retain_metadata_pallets;
|
||||
use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, Variant};
|
||||
use std::collections::HashSet;
|
||||
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
// Copyright 2019-2023 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 frame_metadata::{ExtrinsicMetadata, PalletMetadata, RuntimeMetadataV14, StorageEntryType};
|
||||
use scale_info::{form::PortableForm, interner::UntrackedSymbol, TypeDef};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
collections::{BTreeMap, HashSet},
|
||||
};
|
||||
|
||||
/// Collect all type IDs needed to represent the provided pallet.
|
||||
fn collect_pallet_types(pallet: &PalletMetadata<PortableForm>, type_ids: &mut HashSet<u32>) {
|
||||
if let Some(storage) = &pallet.storage {
|
||||
for entry in &storage.entries {
|
||||
match entry.ty {
|
||||
StorageEntryType::Plain(ty) => {
|
||||
type_ids.insert(ty.id);
|
||||
}
|
||||
StorageEntryType::Map { key, value, .. } => {
|
||||
type_ids.insert(key.id);
|
||||
type_ids.insert(value.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(calls) = &pallet.calls {
|
||||
type_ids.insert(calls.ty.id);
|
||||
}
|
||||
|
||||
if let Some(event) = &pallet.event {
|
||||
type_ids.insert(event.ty.id);
|
||||
}
|
||||
|
||||
for constant in &pallet.constants {
|
||||
type_ids.insert(constant.ty.id);
|
||||
}
|
||||
|
||||
if let Some(error) = &pallet.error {
|
||||
type_ids.insert(error.ty.id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update all type IDs of the provided pallet using the new type IDs from the portable registry.
|
||||
fn update_pallet_types(pallet: &mut PalletMetadata<PortableForm>, map_ids: &BTreeMap<u32, u32>) {
|
||||
if let Some(storage) = &mut pallet.storage {
|
||||
for entry in &mut storage.entries {
|
||||
match &mut entry.ty {
|
||||
StorageEntryType::Plain(ty) => {
|
||||
update_type(ty, map_ids);
|
||||
}
|
||||
StorageEntryType::Map { key, value, .. } => {
|
||||
update_type(key, map_ids);
|
||||
update_type(value, map_ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(calls) = &mut pallet.calls {
|
||||
update_type(&mut calls.ty, map_ids);
|
||||
}
|
||||
|
||||
if let Some(event) = &mut pallet.event {
|
||||
update_type(&mut event.ty, map_ids);
|
||||
}
|
||||
|
||||
for constant in &mut pallet.constants {
|
||||
update_type(&mut constant.ty, map_ids);
|
||||
}
|
||||
|
||||
if let Some(error) = &mut pallet.error {
|
||||
update_type(&mut error.ty, map_ids);
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect all type IDs needed to represent the extrinsic metadata.
|
||||
fn collect_extrinsic_types(
|
||||
extrinsic: &ExtrinsicMetadata<PortableForm>,
|
||||
type_ids: &mut HashSet<u32>,
|
||||
) {
|
||||
type_ids.insert(extrinsic.ty.id);
|
||||
|
||||
for signed in &extrinsic.signed_extensions {
|
||||
type_ids.insert(signed.ty.id);
|
||||
type_ids.insert(signed.additional_signed.id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update all type IDs of the provided extrinsic metadata using the new type IDs from the portable registry.
|
||||
fn update_extrinsic_types(
|
||||
extrinsic: &mut ExtrinsicMetadata<PortableForm>,
|
||||
map_ids: &BTreeMap<u32, u32>,
|
||||
) {
|
||||
update_type(&mut extrinsic.ty, map_ids);
|
||||
|
||||
for signed in &mut extrinsic.signed_extensions {
|
||||
update_type(&mut signed.ty, map_ids);
|
||||
update_type(&mut signed.additional_signed, map_ids);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the given type using the new type ID from the portable registry.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the [`scale_info::PortableRegistry`] did not retain all needed types.
|
||||
fn update_type(ty: &mut UntrackedSymbol<TypeId>, map_ids: &BTreeMap<u32, u32>) {
|
||||
let old_id = ty.id;
|
||||
let new_id = map_ids
|
||||
.get(&old_id)
|
||||
.copied()
|
||||
.unwrap_or_else(|| panic!("PortableRegistry did not retain type id {old_id}. This is a bug. Please open an issue."));
|
||||
*ty = new_id.into();
|
||||
}
|
||||
|
||||
/// Strip any pallets out of the RuntimeCall type that aren't the ones we want to keep.
|
||||
/// The RuntimeCall type is referenced in a bunch of places, so doing this prevents us from
|
||||
/// holding on to stuff in pallets we've asked not to keep.
|
||||
fn retain_pallets_in_runtime_call_type<F>(metadata: &mut RuntimeMetadataV14, mut filter: F)
|
||||
where
|
||||
F: FnMut(&str) -> bool,
|
||||
{
|
||||
let extrinsic_ty = metadata
|
||||
.types
|
||||
.types
|
||||
.get_mut(metadata.extrinsic.ty.id as usize)
|
||||
.expect("Metadata should contain extrinsic type in registry");
|
||||
|
||||
let Some(call_ty) = extrinsic_ty.ty.type_params
|
||||
.iter_mut()
|
||||
.find(|ty| ty.name == "Call")
|
||||
.and_then(|ty| ty.ty) else { return };
|
||||
|
||||
let call_ty = metadata
|
||||
.types
|
||||
.types
|
||||
.get_mut(call_ty.id as usize)
|
||||
.expect("Metadata should contain Call type information");
|
||||
|
||||
let TypeDef::Variant(variant) = &mut call_ty.ty.type_def else {
|
||||
panic!("Metadata Call type is expected to be a variant type");
|
||||
};
|
||||
|
||||
// Remove all variants from the call type that aren't the pallet(s) we want to keep.
|
||||
variant.variants.retain(|v| filter(&v.name));
|
||||
}
|
||||
|
||||
/// Generate a subset of the metadata that contains only the
|
||||
/// types needed to represent the provided pallets.
|
||||
///
|
||||
/// # 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_pallets<F>(metadata: &mut RuntimeMetadataV14, mut filter: F)
|
||||
where
|
||||
F: FnMut(&str) -> bool,
|
||||
{
|
||||
let mut type_ids = HashSet::new();
|
||||
|
||||
// There is a special RuntimeCall type which points to all pallets and call types by default.
|
||||
// This brings in a significant chunk of types. We trim this down to only include variants
|
||||
// for the pallets we're retaining, to avoid this.
|
||||
retain_pallets_in_runtime_call_type(metadata, &mut filter);
|
||||
|
||||
// Filter our pallet list to only those pallets we want to keep. Keep hold of all
|
||||
//type IDs in the pallets we're keeping.
|
||||
metadata.pallets.retain(|pallet| {
|
||||
if filter(&pallet.name) {
|
||||
collect_pallet_types(pallet, &mut type_ids);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
// Keep the extrinsic stuff referenced in our metadata.
|
||||
collect_extrinsic_types(&metadata.extrinsic, &mut type_ids);
|
||||
|
||||
// Keep the "runtime" type ID, since it's referenced in our metadata.
|
||||
type_ids.insert(metadata.ty.id);
|
||||
|
||||
// Additionally, subxt depends on the `DispatchError` type existing; we use the same
|
||||
// logic here that is used when building our `Metadata`.
|
||||
let dispatch_error_ty = metadata
|
||||
.types
|
||||
.types
|
||||
.iter()
|
||||
.find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"])
|
||||
.expect("Metadata must contain sp_runtime::DispatchError");
|
||||
type_ids.insert(dispatch_error_ty.id);
|
||||
|
||||
// Now, keep the type IDs we've asked for. This recursively keeps any types referenced from these.
|
||||
// This will return a map from old to new type ID, because IDs may change.
|
||||
let map_ids = metadata.types.retain(|id| type_ids.contains(&id));
|
||||
|
||||
// And finally, we can go and update all of our type IDs in the metadata as a result of this:
|
||||
for pallets in &mut metadata.pallets {
|
||||
update_pallet_types(pallets, &map_ids);
|
||||
}
|
||||
update_extrinsic_types(&mut metadata.extrinsic, &map_ids);
|
||||
update_type(&mut metadata.ty, &map_ids);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codec::Decode;
|
||||
use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed, RuntimeMetadataV14};
|
||||
use std::{fs, path::Path};
|
||||
|
||||
fn load_metadata() -> RuntimeMetadataV14 {
|
||||
let bytes = fs::read(Path::new("../artifacts/polkadot_metadata.scale"))
|
||||
.expect("Cannot read metadata blob");
|
||||
let meta: RuntimeMetadataPrefixed =
|
||||
Decode::decode(&mut &*bytes).expect("Cannot decode scale metadata");
|
||||
|
||||
match meta.1 {
|
||||
RuntimeMetadata::V14(v14) => v14,
|
||||
_ => 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 mut metadata = metadata_cache.clone();
|
||||
retain_metadata_pallets(&mut metadata, |pallet_name| pallet_name == pallet.name);
|
||||
|
||||
assert_eq!(metadata.pallets.len(), 1);
|
||||
assert_eq!(metadata.pallets.get(0).unwrap().name, pallet.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -403,7 +403,7 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
|
||||
v.name.clone(),
|
||||
CallMetadata {
|
||||
call_index: v.index,
|
||||
fields: v.fields.to_vec(),
|
||||
fields: v.fields.clone(),
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
@@ -87,7 +87,7 @@ impl<T> EncodeAsType for WrapperKeepOpaque<T> {
|
||||
};
|
||||
|
||||
// Do a basic check that the target shape lines up.
|
||||
let scale_info::TypeDef::Composite(_) = ty.type_def else {
|
||||
let scale_info::TypeDef::Composite(_) = &ty.type_def else {
|
||||
return Err(Error::new(ErrorKind::WrongShape {
|
||||
actual: Kind::Struct,
|
||||
expected: type_id,
|
||||
|
||||
@@ -14,3 +14,4 @@ scale-info = { version = "2.5.0", features = ["bit-vec"] }
|
||||
frame-metadata = "15.0.0"
|
||||
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full", "bit-vec"] }
|
||||
subxt = { path = "../../subxt" }
|
||||
subxt-metadata = { path = "../../metadata" }
|
||||
|
||||
@@ -15,7 +15,7 @@ mod dispatch_errors;
|
||||
mod storage;
|
||||
mod utils;
|
||||
|
||||
use crate::utils::MetadataTestRunner;
|
||||
use crate::utils::{MetadataTestRunner, PalletMetadataTestRunner};
|
||||
|
||||
// Each of these tests leads to some rust code being compiled and
|
||||
// executed to test that compilation is successful (or errors in the
|
||||
@@ -23,6 +23,7 @@ use crate::utils::MetadataTestRunner;
|
||||
#[test]
|
||||
fn ui_tests() {
|
||||
let mut m = MetadataTestRunner::default();
|
||||
let mut p = PalletMetadataTestRunner::new();
|
||||
let t = trybuild::TestCases::new();
|
||||
|
||||
t.pass("src/correct/*.rs");
|
||||
@@ -46,6 +47,11 @@ fn ui_tests() {
|
||||
"array_dispatch_error",
|
||||
dispatch_errors::metadata_array_dispatch_error(),
|
||||
));
|
||||
|
||||
// Ensure the generate per pallet metadata compiles.
|
||||
while let Some(path) = p.path_to_next_ui_test() {
|
||||
t.pass(path);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
pub mod dispatch_error;
|
||||
mod metadata_test_runner;
|
||||
mod pallet_metadata_test_runner;
|
||||
|
||||
use frame_metadata::{
|
||||
v14::RuntimeMetadataV14, ExtrinsicMetadata, PalletMetadata, PalletStorageMetadata,
|
||||
@@ -12,6 +13,7 @@ use frame_metadata::{
|
||||
use scale_info::{meta_type, IntoPortable, TypeInfo};
|
||||
|
||||
pub use metadata_test_runner::MetadataTestRunner;
|
||||
pub use pallet_metadata_test_runner::PalletMetadataTestRunner;
|
||||
|
||||
/// Given some pallet metadata, generate a [`RuntimeMetadataPrefixed`] struct.
|
||||
/// We default to a useless extrinsic type, and register a fake `DispatchError`
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use frame_metadata::{RuntimeMetadataPrefixed, RuntimeMetadataV14};
|
||||
use std::io::Read;
|
||||
use subxt_metadata::retain_metadata_pallets;
|
||||
|
||||
static TEST_DIR_PREFIX: &str = "subxt_generated_pallets_ui_tests_";
|
||||
static METADATA_FILE: &str = "../../artifacts/polkadot_metadata.scale";
|
||||
|
||||
pub struct PalletMetadataTestRunner {
|
||||
metadata: RuntimeMetadataV14,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl PalletMetadataTestRunner {
|
||||
pub fn new() -> PalletMetadataTestRunner {
|
||||
let mut file =
|
||||
std::fs::File::open(METADATA_FILE).expect("Cannot open metadata.scale artifact");
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
file.read_to_end(&mut bytes)
|
||||
.expect("Failed to read metadata.scale file");
|
||||
|
||||
let meta: RuntimeMetadataPrefixed =
|
||||
Decode::decode(&mut &*bytes).expect("Cannot decode metadata bytes");
|
||||
|
||||
let metadata = match meta.1 {
|
||||
frame_metadata::RuntimeMetadata::V14(v14) => v14,
|
||||
_ => panic!("Unsupported metadata version. Tests support only v14"),
|
||||
};
|
||||
|
||||
PalletMetadataTestRunner { metadata, index: 0 }
|
||||
}
|
||||
|
||||
pub fn path_to_next_ui_test(&mut self) -> Option<String> {
|
||||
let Some(pallet) = self.metadata.pallets.get(self.index) else {
|
||||
return None
|
||||
};
|
||||
let test_name = &pallet.name;
|
||||
|
||||
// Increment test index to avoid overlaps.
|
||||
let index = self.index;
|
||||
self.index += 1;
|
||||
|
||||
// Build custom metadata containing only this pallet.
|
||||
let mut metadata = self.metadata.clone();
|
||||
retain_metadata_pallets(&mut metadata, |pallet_filter| pallet_filter == pallet.name);
|
||||
|
||||
let mut tmp_dir = std::env::temp_dir();
|
||||
tmp_dir.push(format!("{TEST_DIR_PREFIX}{index}"));
|
||||
|
||||
let tmp_metadata_path = {
|
||||
let mut t = tmp_dir.clone();
|
||||
t.push("metadata.scale");
|
||||
t.to_string_lossy().into_owned()
|
||||
};
|
||||
let tmp_rust_path = {
|
||||
let mut t = tmp_dir.clone();
|
||||
t.push(format!("{test_name}.rs"));
|
||||
t.to_string_lossy().into_owned()
|
||||
};
|
||||
|
||||
let metadata_prefixed: RuntimeMetadataPrefixed = metadata.into();
|
||||
let encoded_metadata = metadata_prefixed.encode();
|
||||
let rust_file = format!(
|
||||
r#"
|
||||
use subxt;
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "{tmp_metadata_path}")]
|
||||
pub mod polkadot {{}}
|
||||
|
||||
fn main() {{}}
|
||||
"#
|
||||
);
|
||||
|
||||
std::fs::create_dir_all(&tmp_dir).expect("could not create tmp ui test dir");
|
||||
// Write metadata to tmp folder:
|
||||
std::fs::write(&tmp_metadata_path, encoded_metadata).unwrap();
|
||||
// Write test file to tmp folder (it'll be moved by trybuild):
|
||||
std::fs::write(&tmp_rust_path, rust_file).unwrap();
|
||||
|
||||
Some(tmp_rust_path)
|
||||
}
|
||||
}
|
||||
|
||||
// `trybuild` runs all tests once it's dropped. So, we defer all cleanup until we
|
||||
// are dropped too, to make sure that cleanup happens after tests are ran.
|
||||
impl Drop for PalletMetadataTestRunner {
|
||||
fn drop(&mut self) {
|
||||
for i in 0..self.index {
|
||||
let mut tmp_dir = std::env::temp_dir();
|
||||
tmp_dir.push(format!("{TEST_DIR_PREFIX}{i}"));
|
||||
std::fs::remove_dir_all(tmp_dir).expect("cannot cleanup temp files");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user