diff --git a/Cargo.lock b/Cargo.lock index 56902d35cd..3ea56157b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1842,6 +1842,17 @@ dependencies = [ "serde", ] +[[package]] +name = "frame-metadata" +version = "16.0.0" +source = "git+https://github.com/paritytech/frame-metadata.git?branch=lexnv/metadata-v16-associated-types#ede6a4dda012a101e46eea7a44a410a13b46867f" +dependencies = [ + "cfg-if", + "parity-scale-codec", + "scale-info", + "serde", +] + [[package]] name = "fs-err" version = "2.11.0" @@ -1983,7 +1994,7 @@ dependencies = [ name = "generate-custom-metadata" version = "0.37.0" dependencies = [ - "frame-metadata 16.0.0", + "frame-metadata 16.0.0 (git+https://github.com/paritytech/frame-metadata.git?branch=lexnv/metadata-v16-associated-types)", "parity-scale-codec", "scale-info", ] @@ -2525,7 +2536,7 @@ version = "0.37.0" dependencies = [ "assert_matches", "cfg_aliases", - "frame-metadata 16.0.0", + "frame-metadata 16.0.0 (git+https://github.com/paritytech/frame-metadata.git?branch=lexnv/metadata-v16-associated-types)", "futures", "hex", "parity-scale-codec", @@ -5103,7 +5114,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a616fa51350b35326682a472ee8e6ba742fdacb18babac38ecd46b3e05ead869" dependencies = [ - "frame-metadata 16.0.0", + "frame-metadata 16.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec", "scale-info", ] @@ -5605,7 +5616,7 @@ dependencies = [ "derive-where", "either", "finito", - "frame-metadata 16.0.0", + "frame-metadata 16.0.0 (git+https://github.com/paritytech/frame-metadata.git?branch=lexnv/metadata-v16-associated-types)", "futures", "getrandom", "hex", @@ -5648,7 +5659,7 @@ version = "0.37.0" dependencies = [ "clap 4.5.17", "color-eyre", - "frame-metadata 16.0.0", + "frame-metadata 16.0.0 (git+https://github.com/paritytech/frame-metadata.git?branch=lexnv/metadata-v16-associated-types)", "heck 0.5.0", "hex", "indoc", @@ -5675,7 +5686,7 @@ dependencies = [ name = "subxt-codegen" version = "0.37.0" dependencies = [ - "frame-metadata 16.0.0", + "frame-metadata 16.0.0 (git+https://github.com/paritytech/frame-metadata.git?branch=lexnv/metadata-v16-associated-types)", "getrandom", "heck 0.5.0", "hex", @@ -5700,7 +5711,7 @@ dependencies = [ "bitvec", "blake2", "derive-where", - "frame-metadata 16.0.0", + "frame-metadata 16.0.0 (git+https://github.com/paritytech/frame-metadata.git?branch=lexnv/metadata-v16-associated-types)", "hashbrown 0.14.5", "hex", "impl-serde", @@ -5774,7 +5785,7 @@ dependencies = [ "assert_matches", "bitvec", "criterion", - "frame-metadata 16.0.0", + "frame-metadata 16.0.0 (git+https://github.com/paritytech/frame-metadata.git?branch=lexnv/metadata-v16-associated-types)", "hashbrown 0.14.5", "parity-scale-codec", "scale-info", @@ -6306,7 +6317,7 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" name = "ui-tests" version = "0.37.0" dependencies = [ - "frame-metadata 16.0.0", + "frame-metadata 16.0.0 (git+https://github.com/paritytech/frame-metadata.git?branch=lexnv/metadata-v16-associated-types)", "generate-custom-metadata", "hex", "parity-scale-codec", diff --git a/Cargo.toml b/Cargo.toml index 9803a00183..25b864a9a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ darling = "0.20.10" derive-where = "1.2.7" either = { version = "1.13.0", default-features = false } finito = { version = "0.1.0", default-features = false } -frame-metadata = { version = "16.0.0", default-features = false } +frame-metadata = { git = "https://github.com/paritytech/frame-metadata.git", branch = "lexnv/metadata-v16-associated-types" ,features = ["current", "decode", "unstable"] } futures = { version = "0.3.30", default-features = false, features = ["std"] } getrandom = { version = "0.2", default-features = false } hashbrown = "0.14.5" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index d6392da894..c4bc640fec 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -34,7 +34,7 @@ serde = { workspace = true, features = ["derive"] } color-eyre = { workspace = true } serde_json = { workspace = true } hex = { workspace = true } -frame-metadata = { workspace = true } +frame-metadata = { workspace = true, features = ["current", "decode", "unstable"] } codec = { package = "parity-scale-codec", workspace = true } scale-info = { workspace = true } scale-value = { workspace = true } diff --git a/cli/src/commands/metadata.rs b/cli/src/commands/metadata.rs index 292ca1c7a5..0263416f9c 100644 --- a/cli/src/commands/metadata.rs +++ b/cli/src/commands/metadata.rs @@ -41,6 +41,7 @@ pub struct Opts { } pub async fn run(opts: Opts, output: &mut impl Write) -> color_eyre::Result<()> { + println!("opts {:?}", opts); 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[..])?; diff --git a/cli/src/utils.rs b/cli/src/utils.rs index 624d5256ae..e4cce6b314 100644 --- a/cli/src/utils.rs +++ b/cli/src/utils.rs @@ -122,7 +122,9 @@ impl FileOrUrl { // Default if neither is provided; fetch from local url (None, None, version) => { let url = Url::parse("ws://localhost:9944").expect("Valid URL; qed"); - Ok(fetch_metadata_from_url(url, version.unwrap_or_default()).await?) + let version = version.unwrap_or_default(); + // println!("Fetching metadata from {url} with version {version:?}"); + Ok(fetch_metadata_from_url(url, version).await?) } } } diff --git a/codegen/src/api/associated_types.rs b/codegen/src/api/associated_types.rs new file mode 100644 index 0000000000..8e0a086ce3 --- /dev/null +++ b/codegen/src/api/associated_types.rs @@ -0,0 +1,113 @@ +// 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 std::any::Any; + +use super::CodegenError; +use heck::{ToSnakeCase as _, ToUpperCamelCase as _}; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use scale_typegen::typegen::ir::ToTokensWithSettings; +use scale_typegen::{typegen::ir::type_ir::CompositeIRKind, TypeGenerator}; +use subxt_metadata::{AssociatedTypeMetadata, PalletMetadata}; + +/// The name of the system pallet. +const PALLET_SYSTEM: &str = "System"; +/// The name of the system pallet block type. +const PALLET_SYSTEM_BLOCK: &str = "Block"; + +fn handle_block_type( + type_gen: &TypeGenerator, + pallet: &PalletMetadata, + ty: &AssociatedTypeMetadata, + crate_path: &syn::Path, +) -> Option { + // Only handle the system pallet block type. + if pallet.name() != PALLET_SYSTEM || ty.name() != PALLET_SYSTEM_BLOCK { + return None; + } + + // println!("System pallet, block type: {:?}", ty); + + let resolved_ty = type_gen.resolve_type(ty.type_id()).ok()?; + // First generic param is the header of the chain. + let header = resolved_ty.type_params.get(0)?; + + // Second generic param is the unchecked extrinsics. + let extrinsics = resolved_ty.type_params.get(1)?; + let extrinsics_ty = type_gen.resolve_type(extrinsics.ty?.id).ok()?; + // Which contains the Address Type as first generic parameter. + let account_id = extrinsics_ty.type_params.get(0)?; + let resolved_account_id = type_gen.resolve_type_path(account_id.ty?.id).ok()?; + let resolved_account_id = resolved_account_id.to_token_stream(type_gen.settings()); + + let ty_path = type_gen.resolve_type_path(ty.type_id()).ok()?; + let ty = ty_path.to_token_stream(type_gen.settings()); + + Some(quote! { + pub type Address = #resolved_account_id; + // TODO: add the header type here. + // pub type Header = <#crate_path::system::Block as #crate_path::Block>::Header; + }) +} + +/// Generate associated types. +pub fn generate_associated_types( + type_gen: &TypeGenerator, + pallet: &PalletMetadata, + crate_path: &syn::Path, +) -> Result { + let associated_types = pallet.associated_types(); + + let collected = associated_types.iter().map(|ty| { + let name = format_ident!("{}", ty.name()); + let docs = type_gen.docs_from_scale_info(&ty.docs()); + + let Ok(ty_path) = type_gen.resolve_type_path(ty.type_id()) else { + // We don't have the information in the type generator to handle this type. + return quote! {}; + }; + + let maybe_block_ty = handle_block_type(type_gen, pallet, ty, crate_path); + let name_str = ty.name(); + let ty = ty_path.to_token_stream(type_gen.settings()); + + let mut maybe_impl = None; + if name_str == "Hashing" { + // Extract hasher name + let ty_path_str = ty.to_string(); + if ty_path_str.contains("BlakeTwo256") { + maybe_impl = Some(quote! { + impl #crate_path::config::Hasher for #ty { + type Output = #crate_path::utils::H256; + + fn hash(s: &[u8]) -> Self::Output { + let mut bytes = Vec::new(); + #crate_path::storage::utils::hash_bytes(s, #crate_path::storage::utils::StorageHasher::Blake2_256, &mut bytes); + let arr: [u8; 32] = bytes.try_into().expect("Invalid hashing output provided"); + arr.into() + } + } + }); + } + } + + + + quote! { + #docs + pub type #name = #ty; + + // Types extracted from the generic parameters of the system pallet block type. + #maybe_block_ty + + // Implementation for the hasher type. + #maybe_impl + } + }); + + Ok(quote! { + #( #collected )* + }) +} diff --git a/codegen/src/api/custom_values.rs b/codegen/src/api/custom_values.rs index dd577d6e74..315a008f4c 100644 --- a/codegen/src/api/custom_values.rs +++ b/codegen/src/api/custom_values.rs @@ -3,13 +3,14 @@ // see LICENSE for license details. use heck::ToSnakeCase as _; +use scale_info::{TypeDef, TypeDefComposite}; use scale_typegen::typegen::ir::ToTokensWithSettings; use scale_typegen::TypeGenerator; -use std::collections::HashSet; +use std::{any::Any, collections::HashSet}; use subxt_metadata::{CustomValueMetadata, Metadata}; use proc_macro2::TokenStream as TokenStream2; -use quote::quote; +use quote::{format_ident, quote}; /// Generate the custom values mod, if there are any custom values in the metadata. Else returns None. pub fn generate_custom_values( @@ -19,6 +20,117 @@ pub fn generate_custom_values( ) -> TokenStream2 { let mut fn_names_taken = HashSet::new(); let custom = metadata.custom(); + let custom_types = custom.iter().map(|custom| { + let name_str = custom.name(); + + let name = format_ident!("{}", name_str); + + let Ok(ty_path) = type_gen.resolve_type_path(custom.type_id()) else { + return quote! {}; + }; + let ty = ty_path.to_token_stream(type_gen.settings()); + + let mut maybe_impl = None; + let mut extra = None; + if name_str == "Hashing" { + // Extract hasher name + let ty_path_str = ty.to_string(); + if ty_path_str.contains("BlakeTwo256") { + maybe_impl = Some(quote! { + impl #crate_path::config::Hasher for #ty { + type Output = #crate_path::utils::H256; + + fn hash(s: &[u8]) -> Self::Output { + let mut bytes = Vec::new(); + #crate_path::storage::utils::hash_bytes(s, #crate_path::storage::utils::StorageHasher::Blake2_256, &mut bytes); + let arr: [u8; 32] = bytes.try_into().expect("Invalid hashing output provided"); + arr.into() + } + } + }); + } + } + + if name_str == "Header" { + // Extract header number from the provided type. + let Ok(ty_res) = type_gen.resolve_type(custom.type_id()) else { + return quote! {}; + }; + + let TypeDef::Composite(composite) = &ty_res.type_def else { + return quote! {}; + }; + + // Sanity check for the number type. + let number_ty = composite.fields.iter().find_map( + |field| if let Some(n) = &field.name { + if n == "number" { + Some(field.ty.id) + } else { + None + } + } else { + None + } + ); + + if let Some(num) = number_ty { + + let Ok(ty_path) = type_gen.resolve_type_path(num) else { + return quote! {}; + }; + + if !ty_path.is_compact() { + let ty = ty_path.to_token_stream(type_gen.settings()); + + extra = Some(quote! { + pub type HeaderNumber = #ty; + }); + } else { + // Ty is compact. + let Ok(ty_res) = type_gen.resolve_type(num) else { + return quote! {}; + }; + + let TypeDef::Compact(compact) = &ty_res.type_def else { + return quote! {}; + }; + let compact_ty_id = compact.type_param.id; + + let Ok(ty_path) = type_gen.resolve_type_path(compact_ty_id) else { + return quote! {}; + }; + let ty = ty_path.to_token_stream(type_gen.settings()); + + extra = Some(quote! { + pub type HeaderNumber = #ty; + }); + } + + + maybe_impl = Some(quote! { + impl #crate_path::config::Header for #ty { + type Number = HeaderNumber; + type Hasher = Hashing; + + // If we got to this point, the `number` field exists on the header + // structure. + fn number(&self) -> Self::Number { + self.number + } + } + }); + } + } + + quote! { + pub type #name = #ty; + + #maybe_impl + #extra + } + }); + let custom_values_fns = custom.iter().filter_map(|custom_value| { generate_custom_value_fn(custom_value, type_gen, crate_path, &mut fn_names_taken) }); @@ -29,6 +141,12 @@ pub fn generate_custom_values( impl CustomValuesApi { #(#custom_values_fns)* } + + pub mod custom_types { + pub use super::*; + + #(#custom_types)* + } } } diff --git a/codegen/src/api/mod.rs b/codegen/src/api/mod.rs index 7a8e8eee2f..9553e43916 100644 --- a/codegen/src/api/mod.rs +++ b/codegen/src/api/mod.rs @@ -4,6 +4,7 @@ //! Generate code for submitting extrinsics and query storage of a Substrate runtime. +mod associated_types; mod calls; mod constants; mod custom_values; @@ -171,10 +172,21 @@ impl RuntimeGenerator { let errors = errors::generate_error_type_alias(&type_gen, pallet)?; + let associated_types = + associated_types::generate_associated_types(&type_gen, pallet, &crate_path)?; + Ok(quote! { pub mod #mod_name { use super::root_mod; use super::#types_mod_ident; + + pub mod associated_types { + use super::root_mod; + use super::#types_mod_ident; + + #associated_types + } + #errors #calls #event diff --git a/codegen/src/fetch_metadata.rs b/codegen/src/fetch_metadata.rs index ee7c554013..7b485f43d4 100644 --- a/codegen/src/fetch_metadata.rs +++ b/codegen/src/fetch_metadata.rs @@ -143,6 +143,8 @@ async fn fetch_metadata( Decode::decode(&mut &raw_bytes[..])? }; + // println!(" Metadata version: {supported_versions:?}"); + // Return the version the user wants if it's supported: let version = match version { MetadataVersion::Latest => *supported_versions @@ -172,6 +174,8 @@ async fn fetch_metadata( } }; + // println!(" Metadata version: {version}"); + let bytes = version.encode(); let version: String = format!("0x{}", hex::encode(&bytes)); diff --git a/core/src/config/polkadot.rs b/core/src/config/polkadot.rs index 7f4e3a88f9..160c25a2df 100644 --- a/core/src/config/polkadot.rs +++ b/core/src/config/polkadot.rs @@ -17,14 +17,22 @@ pub use primitive_types::{H256, U256}; pub enum PolkadotConfig {} impl Config for PolkadotConfig { - type Hash = ::Hash; - type AccountId = ::AccountId; - type Address = MultiAddress; - type Signature = ::Signature; - type Hasher = ::Hasher; - type Header = ::Header; + // coming from: System::Config + type Hash = ::Hash; // Done + type AccountId = ::AccountId; // Done + type Hasher = ::Hasher; // Done + + // coming from ::Extrinsic type + type Address = MultiAddress; // Done + type Signature = ::Signature; // Done + + // coming from ::Header type + type Header = ::Header; // Done + type ExtrinsicParams = PolkadotExtrinsicParams; - type AssetId = u32; + + // coming from Assets::Config (interested in foreign Assets specifically) + type AssetId = u32; // Done } /// A struct representing the signed extra and additional parameters required diff --git a/core/src/storage/mod.rs b/core/src/storage/mod.rs index c11957a665..a019ce82ab 100644 --- a/core/src/storage/mod.rs +++ b/core/src/storage/mod.rs @@ -42,7 +42,7 @@ //! ``` mod storage_key; -mod utils; +pub mod utils; pub mod address; diff --git a/core/src/storage/utils.rs b/core/src/storage/utils.rs index dc5d10ace4..6a891c455e 100644 --- a/core/src/storage/utils.rs +++ b/core/src/storage/utils.rs @@ -11,7 +11,8 @@ use crate::error::{Error, MetadataError}; use crate::metadata::Metadata; use alloc::borrow::ToOwned; use alloc::vec::Vec; -use subxt_metadata::{PalletMetadata, StorageEntryMetadata, StorageHasher}; +pub use subxt_metadata::StorageHasher; +use subxt_metadata::{PalletMetadata, StorageEntryMetadata}; /// Return the root of a given [`Address`]: hash the pallet name and entry name /// and append those bytes to the output. diff --git a/metadata/Cargo.toml b/metadata/Cargo.toml index 0ad5316100..bdc8edb93f 100644 --- a/metadata/Cargo.toml +++ b/metadata/Cargo.toml @@ -19,7 +19,7 @@ std = ["scale-info/std", "frame-metadata/std"] [dependencies] scale-info = { workspace = true, default-features = false } -frame-metadata = { workspace = true, default-features = false, features = ["current", "decode"] } +frame-metadata = { workspace = true, default-features = false, features = ["current", "decode", "unstable"] } codec = { package = "parity-scale-codec", workspace = true, default-features = false, features = ["derive"] } sp-crypto-hashing = { workspace = true } hashbrown = { workspace = true } diff --git a/metadata/src/from_into/mod.rs b/metadata/src/from_into/mod.rs index 13bb2f40c2..12d54c87a5 100644 --- a/metadata/src/from_into/mod.rs +++ b/metadata/src/from_into/mod.rs @@ -8,6 +8,7 @@ use alloc::string::String; mod v14; mod v15; +mod v16; /// An error emitted if something goes wrong converting [`frame_metadata`] /// types into [`crate::Metadata`]. @@ -108,6 +109,7 @@ impl TryFrom for crate::Metadata { } frame_metadata::RuntimeMetadata::V14(m) => m.try_into(), frame_metadata::RuntimeMetadata::V15(m) => m.try_into(), + frame_metadata::RuntimeMetadata::V16(m) => m.try_into(), } } } diff --git a/metadata/src/from_into/v15.rs b/metadata/src/from_into/v15.rs index 37e4336543..026d854409 100644 --- a/metadata/src/from_into/v15.rs +++ b/metadata/src/from_into/v15.rs @@ -2,6 +2,9 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. +extern crate alloc; +use alloc::vec::Vec; + use super::TryFromError; use crate::utils::variant_index::VariantIndex; @@ -66,6 +69,7 @@ mod from_v15 { error_variant_index, constants: constants.collect(), docs: p.docs, + associated_types: Vec::new(), }, ); } diff --git a/metadata/src/from_into/v16.rs b/metadata/src/from_into/v16.rs new file mode 100644 index 0000000000..85b6b00bfa --- /dev/null +++ b/metadata/src/from_into/v16.rs @@ -0,0 +1,458 @@ +// 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 super::TryFromError; + +use crate::utils::variant_index::VariantIndex; +use crate::{ + utils::ordered_map::OrderedMap, ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata, + OuterEnumsMetadata, PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadata, + RuntimeApiMethodParamMetadata, SignedExtensionMetadata, StorageEntryMetadata, + StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata, +}; +use alloc::borrow::ToOwned; +use alloc::vec; +use frame_metadata::v16; +use hashbrown::HashMap; +use scale_info::form::PortableForm; + +// Converting from V16 metadata into our Subxt repr. +mod from_v16 { + use frame_metadata::v15; + + use crate::AssociatedTypeMetadata; + + use super::*; + + impl TryFrom for Metadata { + type Error = TryFromError; + fn try_from(m: v16::RuntimeMetadataV16) -> 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, + associated_types: p + .associated_types + .into_iter() + .map(from_associated_type_metadata) + .collect(), + }, + ); + } + + 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: 0, + 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: Default::default(), + }, + }) + } + } + + fn from_signed_extension_metadata( + value: v16::TransactionExtensionMetadata, + ) -> SignedExtensionMetadata { + SignedExtensionMetadata { + identifier: value.identifier, + extra_ty: value.ty.id, + additional_ty: value.additional_signed.id, + } + } + + fn from_extrinsic_metadata(value: v16::ExtrinsicMetadata) -> ExtrinsicMetadata { + ExtrinsicMetadata { + version: value.versions[0], + signed_extensions: value + .transaction_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, + } + } + + 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_associated_type_metadata( + value: v16::PalletAssociatedTypeMetadata, + ) -> AssociatedTypeMetadata { + AssociatedTypeMetadata { + name: value.name, + ty: value.ty.id, + docs: value.docs, + } + } + + 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, + ) -> 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: v16::RuntimeApiMethodParamMetadata, + ) -> RuntimeApiMethodParamMetadata { + RuntimeApiMethodParamMetadata { + name: s.name, + ty: s.ty.id, + } + } +} + +// Converting from our metadata repr to v16 metadata. +mod into_v16 { + use crate::AssociatedTypeMetadata; + + use super::*; + + impl From for v16::RuntimeMetadataV16 { + fn from(m: Metadata) -> Self { + let pallets = m.pallets.into_values().into_iter().map(|p| { + let storage = p.storage.map(|s| v16::PalletStorageMetadata { + prefix: s.prefix, + entries: s + .entries + .into_values() + .into_iter() + .map(from_storage_entry_metadata) + .collect(), + }); + + v16::PalletMetadata { + name: (*p.name).to_owned(), + calls: p.call_ty.map(|id| v16::PalletCallMetadata { + ty: id.into(), + deprecation_info: v16::DeprecationInfo::NotDeprecated, + }), + event: p.event_ty.map(|id| v16::PalletEventMetadata { + ty: id.into(), + deprecation_info: v16::DeprecationInfo::NotDeprecated, + }), + error: p.error_ty.map(|id| v16::PalletErrorMetadata { + ty: id.into(), + deprecation_info: v16::DeprecationInfo::NotDeprecated, + }), + storage, + constants: p + .constants + .into_values() + .into_iter() + .map(from_constant_metadata) + .collect(), + index: p.index, + docs: p.docs, + associated_types: p + .associated_types + .into_iter() + .map(from_associated_type_metadata) + .collect(), + deprecation_info: v16::DeprecationStatus::NotDeprecated, + } + }); + + v16::RuntimeMetadataV16 { + types: m.types, + pallets: pallets.collect(), + extrinsic: from_extrinsic_metadata(m.extrinsic), + apis: m + .apis + .into_values() + .into_iter() + .map(from_runtime_api_metadata) + .collect(), + outer_enums: v16::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: v16::CustomMetadata { + map: Default::default(), + }, + } + } + } + + fn from_associated_type_metadata( + a: AssociatedTypeMetadata, + ) -> v16::PalletAssociatedTypeMetadata { + v16::PalletAssociatedTypeMetadata { + name: a.name, + ty: a.ty.into(), + docs: a.docs, + } + } + + fn from_runtime_api_metadata( + r: RuntimeApiMetadataInner, + ) -> v16::RuntimeApiMetadata { + v16::RuntimeApiMetadata { + name: (*r.name).to_owned(), + methods: r + .methods + .into_values() + .into_iter() + .map(from_runtime_api_method_metadata) + .collect(), + docs: r.docs, + deprecation_info: v16::DeprecationStatus::NotDeprecated, + } + } + + fn from_runtime_api_method_metadata( + m: RuntimeApiMethodMetadata, + ) -> v16::RuntimeApiMethodMetadata { + v16::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, + deprecation_info: v16::DeprecationStatus::NotDeprecated, + } + } + + fn from_runtime_api_method_param_metadata( + p: RuntimeApiMethodParamMetadata, + ) -> v16::RuntimeApiMethodParamMetadata { + v16::RuntimeApiMethodParamMetadata { + name: p.name, + ty: p.ty.into(), + } + } + + fn from_extrinsic_metadata(e: ExtrinsicMetadata) -> v16::ExtrinsicMetadata { + v16::ExtrinsicMetadata { + versions: vec![e.version], + transaction_extensions: e + .signed_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: SignedExtensionMetadata, + ) -> v16::TransactionExtensionMetadata { + v16::TransactionExtensionMetadata { + identifier: s.identifier, + ty: s.extra_ty.into(), + additional_signed: s.additional_ty.into(), + } + } + + fn from_constant_metadata(c: ConstantMetadata) -> v16::PalletConstantMetadata { + v16::PalletConstantMetadata { + name: (*c.name).to_owned(), + ty: c.ty.into(), + value: c.value, + docs: c.docs, + deprecation_info: v16::DeprecationStatus::NotDeprecated, + } + } + + fn from_storage_entry_metadata( + s: StorageEntryMetadata, + ) -> v16::StorageEntryMetadata { + v16::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), + deprecation_info: v16::DeprecationStatus::NotDeprecated, + } + } + + fn from_storage_entry_modifier(s: StorageEntryModifier) -> v16::StorageEntryModifier { + match s { + StorageEntryModifier::Default => v16::StorageEntryModifier::Default, + StorageEntryModifier::Optional => v16::StorageEntryModifier::Optional, + } + } + + fn from_storage_entry_type(s: StorageEntryType) -> v16::StorageEntryType { + match s { + StorageEntryType::Plain(ty) => v16::StorageEntryType::Plain(ty.into()), + StorageEntryType::Map { + hashers, + key_ty, + value_ty, + } => v16::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) -> v16::StorageHasher { + match s { + StorageHasher::Blake2_128 => v16::StorageHasher::Blake2_128, + StorageHasher::Blake2_256 => v16::StorageHasher::Blake2_256, + StorageHasher::Blake2_128Concat => v16::StorageHasher::Blake2_128Concat, + StorageHasher::Twox128 => v16::StorageHasher::Twox128, + StorageHasher::Twox256 => v16::StorageHasher::Twox256, + StorageHasher::Twox64Concat => v16::StorageHasher::Twox64Concat, + StorageHasher::Identity => v16::StorageHasher::Identity, + } + } +} diff --git a/metadata/src/lib.rs b/metadata/src/lib.rs index fb86ddcbef..b273ee6535 100644 --- a/metadata/src/lib.rs +++ b/metadata/src/lib.rs @@ -230,6 +230,11 @@ impl<'a> PalletMetadata<'a> { ) } + /// Return all of the associated types. + pub fn associated_types(&self) -> &'a [AssociatedTypeMetadata] { + &self.inner.associated_types + } + /// 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) @@ -318,6 +323,36 @@ struct PalletMetadataInner { constants: OrderedMap, /// Pallet documentation. docs: Vec, + /// Pallet associated types. + associated_types: Vec, +} + +/// Metadata for an associated type. +#[derive(Debug, Clone)] +pub struct AssociatedTypeMetadata { + /// Name of the associated type. + name: String, + /// Type of the associated type. + ty: u32, + /// Associated type documentation. + docs: Vec, +} + +impl AssociatedTypeMetadata { + /// Name of the associated type. + pub fn name(&self) -> &str { + &self.name + } + + /// Type id of the associated type. + pub fn type_id(&self) -> u32 { + self.ty + } + + /// Associated type documentation. + pub fn docs(&self) -> &[String] { + &self.docs + } } /// Metadata for the storage entries in a pallet. @@ -773,6 +808,7 @@ impl codec::Decode for Metadata { let metadata = match metadata.1 { frame_metadata::RuntimeMetadata::V14(md) => md.try_into(), frame_metadata::RuntimeMetadata::V15(md) => md.try_into(), + frame_metadata::RuntimeMetadata::V16(md) => md.try_into(), _ => return Err("Cannot try_into() to Metadata: unsupported metadata version".into()), }; diff --git a/subxt/examples/metadata_config.rs b/subxt/examples/metadata_config.rs new file mode 100644 index 0000000000..37ff32b202 --- /dev/null +++ b/subxt/examples/metadata_config.rs @@ -0,0 +1,80 @@ +#![allow(missing_docs)] +use subxt::{OnlineClient, SubstrateConfig}; +use subxt_core::config::substrate::SubstrateHeader; +use subxt_core::config::{Config, DefaultExtrinsicParams}; +use subxt_signer::sr25519::dev; + +// Generate an interface that we can use from the node's metadata. +#[subxt::subxt( + runtime_metadata_insecure_url = "ws://localhost:9944", + unstable_metadata +)] +pub mod polkadot {} + +// Derives aren't strictly needed, they just make developer life easier. +#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum MetadataConfig {} + +impl Config for MetadataConfig { + // Extracted from metadata directly: + type Hash = polkadot::system::associated_types::Hash; + type AccountId = polkadot::system::associated_types::AccountId; + type AssetId = polkadot::assets::associated_types::AssetId; + type Address = polkadot::system::associated_types::Address; + + // Present in metadata but this PoC needs to add + // fn specific per name of type to impl the hashing fn. + // type Hasher = ::Hasher; + // + // TODO: Eventually extend this to a DynamicHasher object instead. + // Similar logic already exists for StorageHasher. + type Hasher = polkadot::system::associated_types::Hashing; + + // Present in metadata but this PoC needs to impl the header + // trait to make use of this. + // type Header = ::Header; + + // The metadata header type must implement `Deserialize`, which + // must be manually implemented for the Digest logs of the header. + // An alternative is to extract the header number and use the + // generated hasher to expose the same information, this is not + // as robust and codgen can be used instead. + // type Header = polkadot::custom_types::Header; + type Header = SubstrateHeader; + + // Same story, present in md but needs subxt::tx::Signer. + // type Signature = polkadot::custom_types::Signature; + type Signature = ::Signature; + // Not exposed in metadata, seems like heavily involved with + // code functionality which cannot safely be expressed in the + // metadata. + type ExtrinsicParams = DefaultExtrinsicParams; +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a new API client, configured to talk to nodes. + let api = OnlineClient::::from_insecure_url("ws://localhost:9944").await?; + + // Build a balance transfer extrinsic. + let dest = dev::bob().public_key().into(); + let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000); + + // Submit the balance transfer extrinsic from Alice, and wait for it to be successful + // and in a finalized block. We get back the extrinsic events if all is well. + let from = dev::alice(); + let events = api + .tx() + .sign_and_submit_then_watch_default(&balance_transfer_tx, &from) + .await? + .wait_for_finalized_success() + .await?; + + // Find a Transfer event and print it. + let transfer_event = events.find_first::()?; + if let Some(event) = transfer_event { + println!("Balance transfer success: {event:?}"); + } + + Ok(()) +}