diff --git a/Cargo.lock b/Cargo.lock index ca57f533be..8ead4ce860 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3895,6 +3895,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "subxt", + "subxt-metadata", "trybuild", ] diff --git a/cli/src/commands/codegen.rs b/cli/src/commands/codegen.rs index cd61dc8bb0..a835d50bd8 100644 --- a/cli/src/commands/codegen.rs +++ b/cli/src/commands/codegen.rs @@ -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, - /// The path to the encoded metadata file. - #[clap(short, long, value_parser)] - file: Option, + #[command(flatten)] + file_or_url: FileOrUrl, /// Additional derives #[clap(long = "derive")] derives: Vec, @@ -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::() - .expect("default url is valid") - }); - subxt_codegen::utils::fetch_metadata_bytes(&url).await? - }; + let bytes = opts.file_or_url.fetch().await?; codegen( &bytes, diff --git a/cli/src/commands/metadata.rs b/cli/src/commands/metadata.rs index 150220ff8c..f8d3aec83a 100644 --- a/cli/src/commands/metadata.rs +++ b/cli/src/commands/metadata.rs @@ -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>, } 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 = ::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 = ::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!( diff --git a/cli/src/main.rs b/cli/src/main.rs index 54b2247fea..4ad786f7b8 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -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. diff --git a/cli/src/utils.rs b/cli/src/utils.rs new file mode 100644 index 0000000000..c5af8c9cd8 --- /dev/null +++ b/cli/src/utils.rs @@ -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, + /// The path to the encoded metadata file. + #[clap(long, value_parser)] + file: Option, +} + +impl FileOrUrl { + /// Fetch the metadata bytes. + pub async fn fetch(&self) -> color_eyre::Result> { + 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?) + } + } + } +} diff --git a/codegen/src/api/mod.rs b/codegen/src/api/mod.rs index 9f7ca93ecc..308f2a03ce 100644 --- a/codegen/src/api/mod.rs +++ b/codegen/src/api/mod.rs @@ -235,12 +235,13 @@ impl RuntimeGenerator { ) -> Result { 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::>(); - // 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::, 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 { diff --git a/metadata/src/lib.rs b/metadata/src/lib.rs index 44268258d8..d31ea9a92b 100644 --- a/metadata/src/lib.rs +++ b/metadata/src/lib.rs @@ -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; diff --git a/metadata/src/retain.rs b/metadata/src/retain.rs new file mode 100644 index 0000000000..67cd06a05b --- /dev/null +++ b/metadata/src/retain.rs @@ -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, type_ids: &mut HashSet) { + 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, map_ids: &BTreeMap) { + 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, + type_ids: &mut HashSet, +) { + 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, + map_ids: &BTreeMap, +) { + 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, map_ids: &BTreeMap) { + 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(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(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); + } + } +} diff --git a/subxt/src/metadata/metadata_type.rs b/subxt/src/metadata/metadata_type.rs index 723ae51984..d9668bff3f 100644 --- a/subxt/src/metadata/metadata_type.rs +++ b/subxt/src/metadata/metadata_type.rs @@ -403,7 +403,7 @@ impl TryFrom for Metadata { v.name.clone(), CallMetadata { call_index: v.index, - fields: v.fields.to_vec(), + fields: v.fields.clone(), }, ) }) diff --git a/subxt/src/utils/wrapper_opaque.rs b/subxt/src/utils/wrapper_opaque.rs index 14a6f7e8da..f5da348682 100644 --- a/subxt/src/utils/wrapper_opaque.rs +++ b/subxt/src/utils/wrapper_opaque.rs @@ -87,7 +87,7 @@ impl EncodeAsType for WrapperKeepOpaque { }; // 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, diff --git a/testing/ui-tests/Cargo.toml b/testing/ui-tests/Cargo.toml index 96f8012539..21ca7e3cff 100644 --- a/testing/ui-tests/Cargo.toml +++ b/testing/ui-tests/Cargo.toml @@ -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" } diff --git a/testing/ui-tests/src/lib.rs b/testing/ui-tests/src/lib.rs index d23ecc0389..702577204b 100644 --- a/testing/ui-tests/src/lib.rs +++ b/testing/ui-tests/src/lib.rs @@ -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] diff --git a/testing/ui-tests/src/utils/mod.rs b/testing/ui-tests/src/utils/mod.rs index 85113f4beb..d2aef3c31f 100644 --- a/testing/ui-tests/src/utils/mod.rs +++ b/testing/ui-tests/src/utils/mod.rs @@ -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` diff --git a/testing/ui-tests/src/utils/pallet_metadata_test_runner.rs b/testing/ui-tests/src/utils/pallet_metadata_test_runner.rs new file mode 100644 index 0000000000..a27450c9f5 --- /dev/null +++ b/testing/ui-tests/src/utils/pallet_metadata_test_runner.rs @@ -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 { + 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"); + } + } +}