Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: Remove subxt Config associated types by relying on enriched metadata (v16) #1566

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
29 changes: 20 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
1 change: 1 addition & 0 deletions cli/src/commands/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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[..])?;
Expand Down
4 changes: 3 additions & 1 deletion cli/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?)
}
}
}
Expand Down
113 changes: 113 additions & 0 deletions codegen/src/api/associated_types.rs
Original file line number Diff line number Diff line change
@@ -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<TokenStream2> {
// 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<TokenStream2, CodegenError> {
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 )*
})
}
122 changes: 120 additions & 2 deletions codegen/src/api/custom_values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)
});
Expand All @@ -29,6 +141,12 @@ pub fn generate_custom_values(
impl CustomValuesApi {
#(#custom_values_fns)*
}

pub mod custom_types {
pub use super::*;

#(#custom_types)*
}
}
}

Expand Down
Loading
Loading