fix: Resolve cargo clippy errors and add CI workflow plan

## Changes

### Clippy Fixes
- Fixed deprecated `cargo_bin` usage in 27 test files (added #![allow(deprecated)])
- Fixed uninlined_format_args in zombienet-sdk-tests
- Fixed subxt API changes in revive/rpc/tests.rs (fetch signature, StorageValue)
- Fixed dead_code warnings in validator-pool and identity-kyc mocks
- Fixed field name `i` -> `_i` in tasks example

### CI Infrastructure
- Added .claude/WORKFLOW_PLAN.md for tracking CI fix progress
- Updated lychee.toml and taplo.toml configs

### Files Modified
- 27 test files with deprecated cargo_bin fix
- bizinikiwi/pezframe/revive/rpc/src/tests.rs (subxt API)
- pezkuwi/pezpallets/validator-pool/src/{mock,tests}.rs
- pezcumulus/teyrchains/pezpallets/identity-kyc/src/mock.rs
- bizinikiwi/pezframe/examples/tasks/src/tests.rs

## Status
- cargo clippy: PASSING
- Next: cargo fmt, zepter, workspace checks
This commit is contained in:
2025-12-22 16:36:14 +03:00
parent 8acf59c6aa
commit 65b7f5e640
1393 changed files with 17834 additions and 179151 deletions
-141
View File
@@ -1,141 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::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::{TypeGenerator, typegen::ir::type_ir::CompositeIRKind};
use pezkuwi_subxt_metadata::PalletMetadata;
/// Generate calls from the provided pallet's metadata. Each call returns a `StaticPayload`
/// that can be passed to the subxt client to submit/sign/encode.
///
/// # Arguments
///
/// - `type_gen` - [`scale_typegen::TypeGenerator`] that contains settings and all types from the runtime metadata.
/// - `pallet` - Pallet metadata from which the calls are generated.
/// - `crate_path` - The crate path under which the `subxt-core` crate is located, e.g. `::pezkuwi_subxt::ext::pezkuwi_subxt_core` when using subxt as a dependency.
pub fn generate_calls(
type_gen: &TypeGenerator,
pallet: &PalletMetadata,
crate_path: &syn::Path,
) -> Result<TokenStream2, CodegenError> {
// Early return if the pallet has no calls.
let Some(call_ty) = pallet.call_ty_id() else {
return Ok(quote!());
};
let variant_names_and_struct_defs = super::generate_structs_from_variants(
type_gen,
call_ty,
|name| name.to_upper_camel_case().into(),
"Call",
)?;
let (call_structs, call_fns): (Vec<_>, Vec<_>) = variant_names_and_struct_defs
.into_iter()
.map(|var| {
let (call_fn_args, call_args): (Vec<_>, Vec<_>) = match &var.composite.kind {
CompositeIRKind::Named(named_fields) => named_fields
.iter()
.map(|(name, field)| {
// Note: fn_arg_type this is relative the type path of the type alias when prefixed with `types::`, e.g. `set_max_code_size::New`
let fn_arg_type = field.type_path.to_token_stream(type_gen.settings());
let call_arg = if field.is_boxed {
quote! { #name: #crate_path::alloc::boxed::Box::new(#name) }
} else {
quote! { #name }
};
(quote!( #name: types::#fn_arg_type ), call_arg)
})
.unzip(),
CompositeIRKind::NoFields => Default::default(),
CompositeIRKind::Unnamed(_) => {
return Err(CodegenError::InvalidCallVariant(call_ty));
}
};
let pallet_name = pallet.name();
let call_name = &var.variant_name;
let struct_name = &var.composite.name;
let Some(call_hash) = pallet.call_hash(call_name) else {
return Err(CodegenError::MissingCallMetadata(
pallet_name.into(),
call_name.to_string(),
));
};
let fn_name = format_ident!("{}", var.variant_name.to_snake_case());
// Propagate the documentation just to `TransactionApi` methods, while
// draining the documentation of inner call structures.
let docs = &var.composite.docs;
// this converts the composite into a full struct type. No Type Parameters needed here.
let struct_def = type_gen
.upcast_composite(&var.composite)
.to_token_stream(type_gen.settings());
let alias_mod = var.type_alias_mod;
// The call structure's documentation was stripped above.
let call_struct = quote! {
#struct_def
#alias_mod
impl #crate_path::blocks::StaticExtrinsic for #struct_name {
const PALLET: &'static str = #pallet_name;
const CALL: &'static str = #call_name;
}
};
let client_fn = quote! {
#docs
pub fn #fn_name(
&self,
#( #call_fn_args, )*
) -> #crate_path::tx::payload::StaticPayload<types::#struct_name> {
#crate_path::tx::payload::StaticPayload::new_static(
#pallet_name,
#call_name,
types::#struct_name { #( #call_args, )* },
[#(#call_hash,)*]
)
}
};
Ok((call_struct, client_fn))
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.unzip();
let call_type = type_gen
.resolve_type_path(call_ty)?
.to_token_stream(type_gen.settings());
let call_ty = type_gen.resolve_type(call_ty)?;
let docs = type_gen.docs_from_scale_info(&call_ty.docs);
let types_mod_ident = type_gen.types_mod_ident();
Ok(quote! {
#docs
pub type Call = #call_type;
pub mod calls {
use super::root_mod;
use super::#types_mod_ident;
type DispatchError = ::pezsp_runtime::DispatchError;
pub mod types {
use super::#types_mod_ident;
#( #call_structs )*
}
pub struct TransactionApi;
impl TransactionApi {
#( #call_fns )*
}
}
})
}
-95
View File
@@ -1,95 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use heck::ToSnakeCase as _;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use scale_typegen::TypeGenerator;
use scale_typegen::typegen::ir::ToTokensWithSettings;
use pezkuwi_subxt_metadata::PalletMetadata;
use super::CodegenError;
/// Generate constants from the provided pallet's metadata.
///
/// The function creates a new module named `constants` under the pallet's module.
/// ```rust,ignore
/// pub mod PalletName {
/// pub mod constants {
/// ...
/// }
/// }
/// ```
///
/// The constants are exposed via the `ConstantsApi` wrapper.
///
/// Although the constants are defined in the provided static metadata, the API
/// ensures that the constants are returned from the runtime metadata of the node.
/// This ensures that if the node's constants change value, we'll always see the latest values.
///
/// # Arguments
///
/// - `type_gen` - [`scale_typegen::TypeGenerator`] that contains settings and all types from the runtime metadata.
/// - `pallet` - Pallet metadata from which the constants are generated.
/// - `crate_path` - The crate path under which the `subxt-core` crate is located, e.g. `::pezkuwi_subxt::ext::pezkuwi_subxt_core` when using subxt as a dependency.
pub fn generate_constants(
type_gen: &TypeGenerator,
pallet: &PalletMetadata,
crate_path: &syn::Path,
) -> Result<TokenStream2, CodegenError> {
// Early return if the pallet has no constants.
if pallet.constants().len() == 0 {
return Ok(quote!());
}
let constant_fns = pallet
.constants()
.map(|constant| {
let fn_name = format_ident!("{}", constant.name().to_snake_case());
let pallet_name = pallet.name();
let constant_name = constant.name();
let Some(constant_hash) = pallet.constant_hash(constant_name) else {
return Err(CodegenError::MissingConstantMetadata(
constant_name.into(),
pallet_name.into(),
));
};
let return_ty = type_gen
.resolve_type_path(constant.ty())?
.to_token_stream(type_gen.settings());
let docs = constant.docs();
let docs = type_gen
.settings()
.should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
Ok(quote! {
#docs
pub fn #fn_name(&self) -> #crate_path::constants::address::StaticAddress<#return_ty> {
#crate_path::constants::address::StaticAddress::new_static(
#pallet_name,
#constant_name,
[#(#constant_hash,)*]
)
}
})
})
.collect::<Result<Vec<_>, _>>()?;
let types_mod_ident = type_gen.types_mod_ident();
Ok(quote! {
pub mod constants {
use super::#types_mod_ident;
pub struct ConstantsApi;
impl ConstantsApi {
#(#constant_fns)*
}
}
})
}
-78
View File
@@ -1,78 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use heck::ToSnakeCase as _;
use scale_typegen::TypeGenerator;
use scale_typegen::typegen::ir::ToTokensWithSettings;
use std::collections::HashSet;
use pezkuwi_subxt_metadata::{CustomValueMetadata, Metadata};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
/// Generate the custom values mod, if there are any custom values in the metadata. Else returns None.
pub fn generate_custom_values(
metadata: &Metadata,
type_gen: &TypeGenerator,
crate_path: &syn::Path,
) -> TokenStream2 {
let mut fn_names_taken = HashSet::new();
let custom = metadata.custom();
let custom_values_fns = custom.iter().filter_map(|custom_value| {
generate_custom_value_fn(custom_value, type_gen, crate_path, &mut fn_names_taken)
});
quote! {
pub struct CustomValuesApi;
impl CustomValuesApi {
#(#custom_values_fns)*
}
}
}
/// Generates runtime functions for the given API metadata.
/// Returns None, if the name would not make for a valid identifier.
fn generate_custom_value_fn(
custom_value: CustomValueMetadata,
type_gen: &TypeGenerator,
crate_path: &syn::Path,
fn_names_taken: &mut HashSet<String>,
) -> Option<TokenStream2> {
// names are transformed to snake case to make for good function identifiers.
let name = custom_value.name();
let fn_name = name.to_snake_case();
if fn_names_taken.contains(&fn_name) {
return None;
}
// if the fn_name would be an invalid ident, return None:
let fn_name_ident = syn::parse_str::<syn::Ident>(&fn_name).ok()?;
fn_names_taken.insert(fn_name);
let custom_value_hash = custom_value.hash();
// for custom values it is important to check if the type id is actually in the metadata:
let type_is_valid = custom_value
.types()
.resolve(custom_value.type_id())
.is_some();
let (return_ty, decodable) = if type_is_valid {
let return_ty = type_gen
.resolve_type_path(custom_value.type_id())
.expect("type is in metadata; qed")
.to_token_stream(type_gen.settings());
let decodable = quote!(#crate_path::utils::Maybe);
(return_ty, decodable)
} else {
// if type registry does not contain the type, we can just return the Encoded scale bytes.
(quote!(()), quote!(#crate_path::utils::No))
};
Some(quote!(
pub fn #fn_name_ident(&self) -> #crate_path::custom_values::address::StaticAddress<#return_ty, #decodable> {
#crate_path::custom_values::address::StaticAddress::new_static(#name, [#(#custom_value_hash,)*])
}
))
}
-36
View File
@@ -1,36 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use scale_typegen::TypeGenerator;
use pezkuwi_subxt_metadata::PalletMetadata;
use super::CodegenError;
use scale_typegen::typegen::ir::ToTokensWithSettings;
/// Generate error type alias from the provided pallet metadata.
pub fn generate_error_type_alias(
type_gen: &TypeGenerator,
pallet: &PalletMetadata,
) -> Result<TokenStream2, CodegenError> {
let Some(error_ty) = pallet.error_ty_id() else {
return Ok(quote!());
};
let error_type = type_gen
.resolve_type_path(error_ty)?
.to_token_stream(type_gen.settings());
let error_ty = type_gen.resolve_type(error_ty)?;
let docs = &error_ty.docs;
let docs = type_gen
.settings()
.should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
Ok(quote! {
#docs
pub type Error = #error_type;
})
}
-93
View File
@@ -1,93 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::CodegenError;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use scale_typegen::TypeGenerator;
use scale_typegen::typegen::ir::ToTokensWithSettings;
use pezkuwi_subxt_metadata::PalletMetadata;
/// Generate events from the provided pallet metadata.
///
/// The function creates a new module named `events` under the pallet's module.
///
/// ```rust,ignore
/// pub mod PalletName {
/// pub mod events {
/// ...
/// }
/// }
/// ```
///
/// The function generates the events as rust structs that implement the `subxt::event::StaticEvent` trait
/// to uniquely identify the event's identity when creating the extrinsic.
///
/// ```rust,ignore
/// pub struct EventName {
/// pub event_param: type,
/// }
/// impl ::pezkuwi_subxt::events::StaticEvent for EventName {
/// ...
/// }
/// ```
///
/// # Arguments
///
/// - `type_gen` - [`scale_typegen::TypeGenerator`] that contains settings and all types from the runtime metadata.
/// - `pallet` - Pallet metadata from which the events are generated.
/// - `crate_path` - The crate path under which the `subxt-core` crate is located, e.g. `::pezkuwi_subxt::ext::pezkuwi_subxt_core` when using subxt as a dependency.
pub fn generate_events(
type_gen: &TypeGenerator,
pallet: &PalletMetadata,
crate_path: &syn::Path,
) -> Result<TokenStream2, CodegenError> {
// Early return if the pallet has no events.
let Some(event_ty) = pallet.event_ty_id() else {
return Ok(quote!());
};
let variant_names_and_struct_defs =
super::generate_structs_from_variants(type_gen, event_ty, |name| name.into(), "Event")?;
let event_structs = variant_names_and_struct_defs.into_iter().map(|var| {
let pallet_name = pallet.name();
let event_struct_name = &var.composite.name;
let event_name = var.variant_name;
let alias_mod = var.type_alias_mod;
let struct_def = type_gen
.upcast_composite(&var.composite)
.to_token_stream(type_gen.settings());
quote! {
#struct_def
#alias_mod
impl #crate_path::events::StaticEvent for #event_struct_name {
const PALLET: &'static str = #pallet_name;
const EVENT: &'static str = #event_name;
}
}
});
let event_type = type_gen
.resolve_type_path(event_ty)?
.to_token_stream(type_gen.settings());
let event_ty = type_gen.resolve_type(event_ty)?;
let docs = &event_ty.docs;
let docs = type_gen
.settings()
.should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
let types_mod_ident = type_gen.types_mod_ident();
Ok(quote! {
#docs
pub type Event = #event_type;
pub mod events {
use super::#types_mod_ident;
#( #event_structs )*
}
})
}
-475
View File
@@ -1,475 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Generate code for submitting extrinsics and query storage of a Substrate runtime.
mod calls;
mod constants;
mod custom_values;
mod errors;
mod events;
mod pallet_view_functions;
mod runtime_apis;
mod storage;
use scale_typegen::TypeGenerator;
use scale_typegen::typegen::ir::ToTokensWithSettings;
use scale_typegen::typegen::ir::type_ir::{CompositeFieldIR, CompositeIR, CompositeIRKind};
use scale_typegen::typegen::type_params::TypeParameters;
use scale_typegen::typegen::type_path::TypePath;
use pezkuwi_subxt_metadata::Metadata;
use syn::{Ident, parse_quote};
use crate::error::CodegenError;
use crate::subxt_type_gen_settings;
use crate::{api::custom_values::generate_custom_values, ir};
use heck::{ToSnakeCase as _, ToUpperCamelCase};
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
/// Create the API for interacting with a Substrate runtime.
pub struct RuntimeGenerator {
metadata: Metadata,
}
impl RuntimeGenerator {
/// Create a new runtime generator from the provided metadata.
///
/// **Note:** If you have the metadata path, URL or bytes to hand, prefer to use
/// `GenerateRuntimeApi` for generating the runtime API from that.
///
/// # Panics
///
/// Panics if the runtime metadata version is not supported.
///
/// Supported versions: v14 and v15.
pub fn new(mut metadata: Metadata) -> Self {
scale_typegen::utils::ensure_unique_type_paths(metadata.types_mut())
.expect("Duplicate type paths in metadata; this is bug please file an issue.");
RuntimeGenerator { metadata }
}
/// Generate the API for interacting with a Substrate runtime.
///
/// # Arguments
///
/// * `item_mod` - The module declaration for which the API is implemented.
/// * `derives` - Provide custom derives for the generated types.
/// * `type_substitutes` - Provide custom type substitutes.
/// * `crate_path` - Path to the `subxt` crate.
/// * `should_gen_docs` - True if the generated API contains the documentation from the metadata.
pub fn generate_runtime_types(
&self,
item_mod: syn::ItemMod,
derives: scale_typegen::DerivesRegistry,
type_substitutes: scale_typegen::TypeSubstitutes,
crate_path: syn::Path,
should_gen_docs: bool,
) -> Result<TokenStream2, CodegenError> {
let item_mod_attrs = item_mod.attrs.clone();
let item_mod_ir = ir::ItemMod::try_from(item_mod)?;
let settings =
subxt_type_gen_settings(derives, type_substitutes, &crate_path, should_gen_docs);
let type_gen = TypeGenerator::new(self.metadata.types(), &settings);
let types_mod = type_gen
.generate_types_mod()?
.to_token_stream(type_gen.settings());
let mod_ident = &item_mod_ir.ident;
let rust_items = item_mod_ir.rust_items();
Ok(quote! {
#( #item_mod_attrs )*
#[allow(dead_code, unused_imports, non_camel_case_types, unreachable_patterns)]
#[allow(clippy::all)]
#[allow(rustdoc::broken_intra_doc_links)]
pub mod #mod_ident {
// Preserve any Rust items that were previously defined in the adorned module
#( #rust_items ) *
// Make it easy to access the root items via `root_mod` at different levels
// without reaching out of this module.
#[allow(unused_imports)]
mod root_mod {
pub use super::*;
}
#types_mod
}
})
}
/// Generate the API for interacting with a Substrate runtime.
///
/// # Arguments
///
/// * `item_mod` - The module declaration for which the API is implemented.
/// * `derives` - Provide custom derives for the generated types.
/// * `type_substitutes` - Provide custom type substitutes.
/// * `crate_path` - Path to the `subxt` crate.
/// * `should_gen_docs` - True if the generated API contains the documentation from the metadata.
pub fn generate_runtime(
&self,
item_mod: syn::ItemMod,
derives: scale_typegen::DerivesRegistry,
type_substitutes: scale_typegen::TypeSubstitutes,
crate_path: syn::Path,
should_gen_docs: bool,
) -> Result<TokenStream2, CodegenError> {
let item_mod_attrs = item_mod.attrs.clone();
let item_mod_ir = ir::ItemMod::try_from(item_mod)?;
let settings =
subxt_type_gen_settings(derives, type_substitutes, &crate_path, should_gen_docs);
let type_gen = TypeGenerator::new(self.metadata.types(), &settings);
let types_mod = type_gen
.generate_types_mod()?
.to_token_stream(type_gen.settings());
let types_mod_ident = type_gen.types_mod_ident();
let pallets_with_mod_names = self
.metadata
.pallets()
.map(|pallet| {
(
pallet,
format_ident!("{}", pallet.name().to_string().to_snake_case()),
)
})
.collect::<Vec<_>>();
// 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.
let pallet_names: Vec<_> = self
.metadata
.pallets()
.map(|pallet| pallet.name())
.collect();
let pallet_names_len = pallet_names.len();
let runtime_api_names: Vec<_> = self
.metadata
.runtime_api_traits()
.map(|api| api.name().to_string())
.collect();
let runtime_api_names_len = runtime_api_names.len();
let modules = pallets_with_mod_names
.iter()
.map(|(pallet, mod_name)| {
let calls = calls::generate_calls(&type_gen, pallet, &crate_path)?;
let event = events::generate_events(&type_gen, pallet, &crate_path)?;
let storage_mod = storage::generate_storage(&type_gen, pallet, &crate_path)?;
let constants_mod = constants::generate_constants(&type_gen, pallet, &crate_path)?;
let errors = errors::generate_error_type_alias(&type_gen, pallet)?;
let view_functions = pallet_view_functions::generate_pallet_view_functions(
&type_gen,
pallet,
&crate_path,
)?;
Ok(quote! {
pub mod #mod_name {
use super::root_mod;
use super::#types_mod_ident;
#errors
#calls
#view_functions
#event
#storage_mod
#constants_mod
}
})
})
.collect::<Result<Vec<_>, CodegenError>>()?;
let mod_ident = &item_mod_ir.ident;
let pallets_with_constants: Vec<_> = pallets_with_mod_names
.iter()
.filter_map(|(pallet, pallet_mod_name)| {
pallet
.constants()
.next()
.is_some()
.then_some(pallet_mod_name)
})
.collect();
let pallets_with_storage: Vec<_> = pallets_with_mod_names
.iter()
.filter_map(|(pallet, pallet_mod_name)| pallet.storage().map(|_| pallet_mod_name))
.collect();
let pallets_with_calls: Vec<_> = pallets_with_mod_names
.iter()
.filter_map(|(pallet, pallet_mod_name)| pallet.call_ty_id().map(|_| pallet_mod_name))
.collect();
let pallets_with_view_functions: Vec<_> = pallets_with_mod_names
.iter()
.filter(|(pallet, _pallet_mod_name)| pallet.has_view_functions())
.map(|(_, pallet_mod_name)| pallet_mod_name)
.collect();
let rust_items = item_mod_ir.rust_items();
let apis_mod = runtime_apis::generate_runtime_apis(
&self.metadata,
&type_gen,
types_mod_ident,
&crate_path,
)?;
// Fetch the paths of the outer enums.
// Substrate exposes those under `kitchensink_runtime`, while Polkadot under `polkadot_runtime`.
let call_path = type_gen
.resolve_type_path(self.metadata.outer_enums().call_enum_ty())?
.to_token_stream(type_gen.settings());
let event_path = type_gen
.resolve_type_path(self.metadata.outer_enums().event_enum_ty())?
.to_token_stream(type_gen.settings());
let error_path = type_gen
.resolve_type_path(self.metadata.outer_enums().error_enum_ty())?
.to_token_stream(type_gen.settings());
let metadata_hash = self.metadata.hasher().hash();
let custom_values = generate_custom_values(&self.metadata, &type_gen, &crate_path);
Ok(quote! {
#( #item_mod_attrs )*
#[allow(dead_code, unused_imports, non_camel_case_types, unreachable_patterns)]
#[allow(clippy::all)]
#[allow(rustdoc::broken_intra_doc_links)]
pub mod #mod_ident {
// Preserve any Rust items that were previously defined in the adorned module.
#( #rust_items ) *
// Make it easy to access the root items via `root_mod` at different levels
// without reaching out of this module.
#[allow(unused_imports)]
mod root_mod {
pub use super::*;
}
// Identify the pallets composing the static metadata by name.
pub static PALLETS: [&str; #pallet_names_len] = [ #(#pallet_names,)* ];
// Runtime APIs in the metadata by name.
pub static RUNTIME_APIS: [&str; #runtime_api_names_len] = [ #(#runtime_api_names,)* ];
/// The error type that is returned when there is a runtime issue.
pub type DispatchError = ::pezsp_runtime::DispatchError;
/// The outer event enum.
pub type Event = #event_path;
/// The outer extrinsic enum.
pub type Call = #call_path;
/// The outer error enum represents the DispatchError's Module variant.
pub type Error = #error_path;
pub fn constants() -> ConstantsApi {
ConstantsApi
}
pub fn storage() -> StorageApi {
StorageApi
}
pub fn tx() -> TransactionApi {
TransactionApi
}
pub fn apis() -> runtime_apis::RuntimeApi {
runtime_apis::RuntimeApi
}
#apis_mod
pub fn view_functions() -> ViewFunctionsApi {
ViewFunctionsApi
}
pub fn custom() -> CustomValuesApi {
CustomValuesApi
}
#custom_values
pub struct ConstantsApi;
impl ConstantsApi {
#(
pub fn #pallets_with_constants(&self) -> #pallets_with_constants::constants::ConstantsApi {
#pallets_with_constants::constants::ConstantsApi
}
)*
}
pub struct StorageApi;
impl StorageApi {
#(
pub fn #pallets_with_storage(&self) -> #pallets_with_storage::storage::StorageApi {
#pallets_with_storage::storage::StorageApi
}
)*
}
pub struct TransactionApi;
impl TransactionApi {
#(
pub fn #pallets_with_calls(&self) -> #pallets_with_calls::calls::TransactionApi {
#pallets_with_calls::calls::TransactionApi
}
)*
}
pub struct ViewFunctionsApi;
impl ViewFunctionsApi {
#(
pub fn #pallets_with_view_functions(&self) -> #pallets_with_view_functions::view_functions::ViewFunctionsApi {
#pallets_with_view_functions::view_functions::ViewFunctionsApi
}
)*
}
/// check whether the metadata provided is aligned with this statically generated code.
pub fn is_codegen_valid_for(metadata: &#crate_path::Metadata) -> bool {
let runtime_metadata_hash = metadata
.hasher()
.only_these_pallets(&PALLETS)
.only_these_runtime_apis(&RUNTIME_APIS)
.hash();
runtime_metadata_hash == [ #(#metadata_hash,)* ]
}
#( #modules )*
#types_mod
}
})
}
}
/// Return a vector of tuples of variant names and corresponding struct definitions.
pub fn generate_structs_from_variants<F>(
type_gen: &TypeGenerator,
type_id: u32,
variant_to_struct_name: F,
error_message_type_name: &str,
) -> Result<Vec<StructFromVariant>, CodegenError>
where
F: Fn(&str) -> std::borrow::Cow<str>,
{
let ty = type_gen.resolve_type(type_id)?;
let scale_info::TypeDef::Variant(variant) = &ty.type_def else {
return Err(CodegenError::InvalidType(error_message_type_name.into()));
};
variant
.variants
.iter()
.map(|var| {
let mut type_params = TypeParameters::from_scale_info(&[]);
let composite_ir_kind =
type_gen.create_composite_ir_kind(&var.fields, &mut type_params)?;
let struct_name = variant_to_struct_name(&var.name);
let mut composite = CompositeIR::new(
syn::parse_str(&struct_name).expect("enum variant is a valid ident; qed"),
composite_ir_kind,
type_gen.docs_from_scale_info(&var.docs),
);
let type_alias_mod = generate_type_alias_mod(&mut composite, type_gen);
Ok(StructFromVariant {
variant_name: var.name.to_string(),
composite,
type_alias_mod,
})
})
.collect()
}
pub struct StructFromVariant {
variant_name: String,
composite: CompositeIR,
type_alias_mod: TokenStream2,
}
/// Modifies the composite, by replacing its types with references to the generated type alias module.
/// Returns the TokenStream of the type alias module.
///
/// E.g a struct like this:
///
/// ```rust,ignore
/// pub struct SetMaxCodeSize {
/// pub new: ::core::primitive::u32,
/// }
/// ```
///
/// will be made into this:
///
/// ```rust,ignore
/// pub struct SetMaxCodeSize {
/// pub new: set_max_code_size::New,
/// }
/// ```
///
/// And the type alias module will look like this:
///
/// ```rust,ignore
/// pub mod set_max_code_size {
/// use super::runtime_types;
/// pub type New = ::core::primitive::u32;
/// }
/// ```
pub fn generate_type_alias_mod(
composite: &mut CompositeIR,
type_gen: &TypeGenerator,
) -> TokenStream2 {
let mut aliases: Vec<TokenStream2> = vec![];
let alias_mod_name: Ident = syn::parse_str(&composite.name.to_string().to_snake_case())
.expect("composite name in snake_case should be a valid identifier");
let mut modify_field_to_be_type_alias = |field: &mut CompositeFieldIR, alias_name: Ident| {
let type_path = field.type_path.to_token_stream(type_gen.settings());
aliases.push(quote!(pub type #alias_name = #type_path;));
let type_alias_path: syn::Path = parse_quote!(#alias_mod_name::#alias_name);
field.type_path = TypePath::from_syn_path(type_alias_path);
};
match &mut composite.kind {
CompositeIRKind::NoFields => {
return quote!(); // no types mod generated for unit structs.
}
CompositeIRKind::Named(named) => {
for (name, field) in named.iter_mut() {
let alias_name = format_ident!("{}", name.to_string().to_upper_camel_case());
modify_field_to_be_type_alias(field, alias_name);
}
}
CompositeIRKind::Unnamed(unnamed) => {
for (i, field) in unnamed.iter_mut().enumerate() {
let alias_name = format_ident!("Field{}", i);
modify_field_to_be_type_alias(field, alias_name);
}
}
};
let types_mod_ident = type_gen.types_mod_ident();
quote!(pub mod #alias_mod_name {
use super::#types_mod_ident;
#( #aliases )*
})
}
@@ -1,184 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use heck::ToUpperCamelCase as _;
use crate::CodegenError;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use scale_typegen::TypeGenerator;
use scale_typegen::typegen::ir::ToTokensWithSettings;
use std::collections::HashSet;
use pezkuwi_subxt_metadata::{PalletMetadata, ViewFunctionMetadata};
pub fn generate_pallet_view_functions(
type_gen: &TypeGenerator,
pallet: &PalletMetadata,
crate_path: &syn::Path,
) -> Result<TokenStream2, CodegenError> {
if !pallet.has_view_functions() {
// If there are no view functions in this pallet, we
// don't generate anything.
return Ok(quote! {});
}
let view_functions: Vec<_> = pallet
.view_functions()
.map(|vf| generate_pallet_view_function(pallet.name(), vf, type_gen, crate_path))
.collect::<Result<_, _>>()?;
let view_functions_types = view_functions.iter().map(|(apis, _)| apis);
let view_functions_methods = view_functions.iter().map(|(_, getters)| getters);
let types_mod_ident = type_gen.types_mod_ident();
Ok(quote! {
pub mod view_functions {
use super::root_mod;
use super::#types_mod_ident;
pub struct ViewFunctionsApi;
impl ViewFunctionsApi {
#( #view_functions_methods )*
}
#( #view_functions_types )*
}
})
}
fn generate_pallet_view_function(
pallet_name: &str,
view_function: ViewFunctionMetadata<'_>,
type_gen: &TypeGenerator,
crate_path: &syn::Path,
) -> Result<(TokenStream2, TokenStream2), CodegenError> {
let types_mod_ident = type_gen.types_mod_ident();
let view_function_name_str = view_function.name();
let view_function_name_ident = format_ident!("{view_function_name_str}");
let validation_hash = view_function.hash();
let docs = view_function.docs();
let docs: TokenStream2 = type_gen
.settings()
.should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
struct Input {
name: syn::Ident,
type_alias: syn::Ident,
type_path: TokenStream2,
}
let view_function_inputs: Vec<Input> = {
let mut unique_names = HashSet::new();
let mut unique_aliases = HashSet::new();
view_function
.inputs()
.enumerate()
.map(|(idx, input)| {
// These are method names, which can just be '_', but struct field names can't
// just be an underscore, so fix any such names we find to work in structs.
let mut name = input.name.trim_start_matches('_').to_string();
if name.is_empty() {
name = format!("_{idx}");
}
while !unique_names.insert(name.clone()) {
name = format!("{name}_param{idx}");
}
// The alias type name is based on the name, above.
let mut alias = name.to_upper_camel_case();
// Note: name is not empty.
if alias.as_bytes()[0].is_ascii_digit() {
alias = format!("Param{alias}");
}
while !unique_aliases.insert(alias.clone()) {
alias = format!("{alias}Param{idx}");
}
// Path to the actual type we'll have generated for this input.
let type_path = type_gen
.resolve_type_path(input.id)
.expect("view function input type is in metadata; qed")
.to_token_stream(type_gen.settings());
Input {
name: format_ident!("{name}"),
type_alias: format_ident!("{alias}"),
type_path,
}
})
.collect()
};
let input_tuple_types = view_function_inputs
.iter()
.map(|i| {
let ty = &i.type_alias;
quote!(#view_function_name_ident::#ty)
})
.collect::<Vec<_>>();
let input_args = view_function_inputs
.iter()
.map(|i| {
let arg = &i.name;
let ty = &i.type_alias;
quote!(#arg: #view_function_name_ident::#ty)
})
.collect::<Vec<_>>();
let input_type_aliases = view_function_inputs.iter().map(|i| {
let ty = &i.type_alias;
let path = &i.type_path;
quote!(pub type #ty = #path;)
});
let input_param_names = view_function_inputs.iter().map(|i| &i.name);
let output_type_path = type_gen
.resolve_type_path(view_function.output_ty())?
.to_token_stream(type_gen.settings());
// Define the input and output type bits.
let view_function_types = quote!(
pub mod #view_function_name_ident {
use super::root_mod;
use super::#types_mod_ident;
#(#input_type_aliases)*
pub mod output {
use super::#types_mod_ident;
pub type Output = #output_type_path;
}
}
);
// Define the getter method that will live on the `ViewFunctionApi` type.
let view_function_method = quote!(
#docs
pub fn #view_function_name_ident(
&self,
#(#input_args),*
) -> #crate_path::view_functions::payload::StaticPayload<
(#(#input_tuple_types,)*),
#view_function_name_ident::output::Output
> {
#crate_path::view_functions::payload::StaticPayload::new_static(
#pallet_name,
#view_function_name_str,
(#(#input_param_names,)*),
[#(#validation_hash,)*],
)
}
);
Ok((view_function_types, view_function_method))
}
-433
View File
@@ -1,433 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use std::collections::HashSet;
use heck::ToSnakeCase as _;
use heck::ToUpperCamelCase as _;
use scale_typegen::TypeGenerator;
use scale_typegen::typegen::ir::ToTokensWithSettings;
use pezkuwi_subxt_metadata::{Metadata, RuntimeApiMetadata};
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use crate::CodegenError;
/// Generate the runtime APIs.
pub fn generate_runtime_apis(
metadata: &Metadata,
type_gen: &TypeGenerator,
types_mod_ident: &syn::Ident,
crate_path: &syn::Path,
) -> Result<TokenStream2, CodegenError> {
let runtime_fns: Vec<_> = metadata
.runtime_api_traits()
.map(|api| generate_runtime_api(api, type_gen, crate_path))
.collect::<Result<_, _>>()?;
let trait_defs = runtime_fns.iter().map(|(apis, _)| apis);
let trait_getters = runtime_fns.iter().map(|(_, getters)| getters);
Ok(quote! {
pub mod runtime_apis {
use super::root_mod;
use super::#types_mod_ident;
use #crate_path::ext::codec::Encode;
pub struct RuntimeApi;
impl RuntimeApi {
#( #trait_getters )*
}
#( #trait_defs )*
}
})
}
/// Generates runtime functions for the given API metadata.
fn generate_runtime_api(
api: RuntimeApiMetadata,
type_gen: &TypeGenerator,
crate_path: &syn::Path,
) -> Result<(TokenStream2, TokenStream2), CodegenError> {
let types_mod_ident = type_gen.types_mod_ident();
// Trait name must remain as is (upper case) to identify the runtime call.
let trait_name_str = api.name();
// The snake case for the trait name.
let trait_name_snake = format_ident!("{}", api.name().to_snake_case());
let docs = api.docs();
let docs: TokenStream2 = type_gen
.settings()
.should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
let types_and_methods = api
.methods()
.map(|method| {
let method_name = format_ident!("{}", method.name());
let method_name_str = method.name();
let validation_hash = method.hash();
let docs = method.docs();
let docs: TokenStream2 = type_gen
.settings()
.should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
struct Input {
name: syn::Ident,
type_alias: syn::Ident,
type_path: TokenStream2,
}
let runtime_api_inputs: Vec<Input> = {
let mut unique_names = HashSet::new();
let mut unique_aliases = HashSet::new();
method
.inputs()
.enumerate()
.map(|(idx, input)| {
// The method argument name is either the input name or the
// index (eg _1, _2 etc) if one isn't provided.
// if we get unlucky we'll end up with param_param1 etc.
let mut name = input.name.trim_start_matches('_').to_string();
if name.is_empty() {
name = format!("_{idx}");
}
while !unique_names.insert(name.clone()) {
name = format!("{name}_param{idx}");
}
// The alias is either InputName if provided, or Param1, Param2 etc if not.
// If we get unlucky we may even end up with ParamParam1 etc.
let mut alias = name.trim_start_matches('_').to_upper_camel_case();
// Note: name is not empty.
if alias.as_bytes()[0].is_ascii_digit() {
alias = format!("Param{alias}");
}
while !unique_aliases.insert(alias.clone()) {
alias = format!("{alias}Param{idx}");
}
// Generate alias for runtime type.
let type_path = type_gen
.resolve_type_path(input.id)
.expect("runtime api input type is in metadata; qed")
.to_token_stream(type_gen.settings());
Input {
name: format_ident!("{name}"),
type_alias: format_ident!("{alias}"),
type_path,
}
})
.collect()
};
let input_tuple_types = runtime_api_inputs
.iter()
.map(|i| {
let ty = &i.type_alias;
quote!(#method_name::#ty)
})
.collect::<Vec<_>>();
let input_args = runtime_api_inputs
.iter()
.map(|i| {
let arg = &i.name;
let ty = &i.type_alias;
quote!(#arg: #method_name::#ty)
})
.collect::<Vec<_>>();
let input_param_names = runtime_api_inputs.iter().map(|i| &i.name);
let input_type_aliases = runtime_api_inputs.iter().map(|i| {
let ty = &i.type_alias;
let path = &i.type_path;
quote!(pub type #ty = #path;)
});
let output_type_path = type_gen
.resolve_type_path(method.output_ty())?
.to_token_stream(type_gen.settings());
// Define the input and output type bits for the method.
let runtime_api_types = quote! {
pub mod #method_name {
use super::root_mod;
use super::#types_mod_ident;
#(#input_type_aliases)*
pub mod output {
use super::#types_mod_ident;
pub type Output = #output_type_path;
}
}
};
// Define the getter method that will live on the `ViewFunctionApi` type.
let runtime_api_method = quote!(
#docs
pub fn #method_name(
&self,
#(#input_args),*
) -> #crate_path::runtime_api::payload::StaticPayload<
(#(#input_tuple_types,)*),
#method_name::output::Output
> {
#crate_path::runtime_api::payload::StaticPayload::new_static(
#trait_name_str,
#method_name_str,
(#(#input_param_names,)*),
[#(#validation_hash,)*],
)
}
);
Ok((runtime_api_types, runtime_api_method))
})
.collect::<Result<Vec<_>, CodegenError>>()?;
let trait_name = format_ident!("{}", trait_name_str);
let types = types_and_methods.iter().map(|(types, _)| types);
let methods = types_and_methods.iter().map(|(_, methods)| methods);
// The runtime API definition and types.
let trait_defs = quote!(
pub mod #trait_name_snake {
use super::root_mod;
use super::#types_mod_ident;
#docs
pub struct #trait_name;
impl #trait_name {
#( #methods )*
}
#( #types )*
}
);
// A getter for the `RuntimeApi` to get the trait structure.
let trait_getter = quote!(
pub fn #trait_name_snake(&self) -> #trait_name_snake::#trait_name {
#trait_name_snake::#trait_name
}
);
Ok((trait_defs, trait_getter))
}
#[cfg(test)]
mod tests {
use crate::RuntimeGenerator;
use frame_metadata::v15::{
self, RuntimeApiMetadata, RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata,
};
use quote::quote;
use scale_info::meta_type;
use pezkuwi_subxt_metadata::Metadata;
fn metadata_with_runtime_apis(runtime_apis: Vec<RuntimeApiMetadata>) -> Metadata {
let extrinsic_metadata = v15::ExtrinsicMetadata {
version: 0,
signed_extensions: vec![],
address_ty: meta_type::<()>(),
call_ty: meta_type::<()>(),
signature_ty: meta_type::<()>(),
extra_ty: meta_type::<()>(),
};
let metadata: Metadata = v15::RuntimeMetadataV15::new(
vec![],
extrinsic_metadata,
meta_type::<()>(),
runtime_apis,
v15::OuterEnums {
call_enum_ty: meta_type::<()>(),
event_enum_ty: meta_type::<()>(),
error_enum_ty: meta_type::<()>(),
},
v15::CustomMetadata {
map: Default::default(),
},
)
.try_into()
.expect("can build valid metadata");
metadata
}
fn generate_code(runtime_apis: Vec<RuntimeApiMetadata>) -> String {
let metadata = metadata_with_runtime_apis(runtime_apis);
let item_mod = syn::parse_quote!(
pub mod api {}
);
let generator = RuntimeGenerator::new(metadata);
let generated = generator
.generate_runtime(
item_mod,
Default::default(),
Default::default(),
syn::parse_str("::subxt_path").unwrap(),
false,
)
.expect("should be able to generate runtime");
generated.to_string()
}
#[test]
fn unique_param_names() {
let runtime_apis = vec![RuntimeApiMetadata {
name: "Test",
methods: vec![RuntimeApiMethodMetadata {
name: "test",
inputs: vec![
RuntimeApiMethodParamMetadata {
name: "foo",
ty: meta_type::<bool>(),
},
RuntimeApiMethodParamMetadata {
name: "bar",
ty: meta_type::<bool>(),
},
],
output: meta_type::<bool>(),
docs: vec![],
}],
docs: vec![],
}];
let code = generate_code(runtime_apis);
let expected_alias = quote!(
pub mod test {
use super::root_mod;
use super::runtime_types;
pub type Foo = ::core::primitive::bool;
pub type Bar = ::core::primitive::bool;
pub mod output {
use super::runtime_types;
pub type Output = ::core::primitive::bool;
}
}
);
assert!(code.contains(&expected_alias.to_string()));
}
#[test]
fn duplicate_param_names() {
let runtime_apis = vec![RuntimeApiMetadata {
name: "Test",
methods: vec![RuntimeApiMethodMetadata {
name: "test",
inputs: vec![
RuntimeApiMethodParamMetadata {
name: "_a",
ty: meta_type::<bool>(),
},
RuntimeApiMethodParamMetadata {
name: "a",
ty: meta_type::<bool>(),
},
RuntimeApiMethodParamMetadata {
name: "__a",
ty: meta_type::<bool>(),
},
],
output: meta_type::<bool>(),
docs: vec![],
}],
docs: vec![],
}];
let code = generate_code(runtime_apis);
let expected_alias = quote!(
pub mod test {
use super::root_mod;
use super::runtime_types;
pub type A = ::core::primitive::bool;
pub type AParam1 = ::core::primitive::bool;
pub type AParam2 = ::core::primitive::bool;
pub mod output {
use super::runtime_types;
pub type Output = ::core::primitive::bool;
}
}
);
assert!(code.contains(&expected_alias.to_string()));
}
#[test]
fn duplicate_param_and_alias_names() {
let runtime_apis = vec![RuntimeApiMetadata {
name: "Test",
methods: vec![RuntimeApiMethodMetadata {
name: "test",
inputs: vec![
RuntimeApiMethodParamMetadata {
name: "_",
ty: meta_type::<bool>(),
},
RuntimeApiMethodParamMetadata {
name: "_a",
ty: meta_type::<bool>(),
},
RuntimeApiMethodParamMetadata {
name: "_param_0",
ty: meta_type::<bool>(),
},
RuntimeApiMethodParamMetadata {
name: "__",
ty: meta_type::<bool>(),
},
RuntimeApiMethodParamMetadata {
name: "___param_0_param_2",
ty: meta_type::<bool>(),
},
],
output: meta_type::<bool>(),
docs: vec![],
}],
docs: vec![],
}];
let code = generate_code(runtime_apis);
let expected_alias = quote!(
pub mod test {
use super::root_mod;
use super::runtime_types;
pub type Param0 = ::core::primitive::bool;
pub type A = ::core::primitive::bool;
pub type Param0Param2 = ::core::primitive::bool;
pub type Param3 = ::core::primitive::bool;
pub type Param0Param2Param4 = ::core::primitive::bool;
pub mod output {
use super::runtime_types;
pub type Output = ::core::primitive::bool;
}
}
);
assert!(code.contains(&expected_alias.to_string()));
}
}
-239
View File
@@ -1,239 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use heck::ToSnakeCase as _;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use scale_typegen::TypeGenerator;
use pezkuwi_subxt_metadata::{PalletMetadata, StorageEntryMetadata};
use super::CodegenError;
use scale_typegen::typegen::ir::ToTokensWithSettings;
/// Generate functions which create storage addresses from the provided pallet's metadata.
/// These addresses can be used to access and iterate over storage values.
///
/// # Arguments
///
/// - `type_gen` - [`scale_typegen::TypeGenerator`] that contains settings and all types from the runtime metadata.
/// - `pallet` - Pallet metadata from which the storage items are generated.
/// - `crate_path` - The crate path under which the `subxt-core` crate is located, e.g. `::pezkuwi_subxt::ext::pezkuwi_subxt_core` when using subxt as a dependency.
pub fn generate_storage(
type_gen: &TypeGenerator,
pallet: &PalletMetadata,
crate_path: &syn::Path,
) -> Result<TokenStream2, CodegenError> {
let Some(storage) = pallet.storage() else {
// If there are no storage entries in this pallet, we
// don't generate anything.
return Ok(quote!());
};
let storage_entries = storage
.entries()
.iter()
.map(|entry| generate_storage_entry_fns(type_gen, pallet, entry, crate_path))
.collect::<Result<Vec<_>, CodegenError>>()?;
let storage_entry_types = storage_entries.iter().map(|(types, _)| types);
let storage_entry_methods = storage_entries.iter().map(|(_, method)| method);
let types_mod_ident = type_gen.types_mod_ident();
Ok(quote! {
pub mod storage {
use super::root_mod;
use super::#types_mod_ident;
pub struct StorageApi;
impl StorageApi {
#( #storage_entry_methods )*
}
#( #storage_entry_types )*
}
})
}
/// Returns storage entry functions and alias modules.
fn generate_storage_entry_fns(
type_gen: &TypeGenerator,
pallet: &PalletMetadata,
storage_entry: &StorageEntryMetadata,
crate_path: &syn::Path,
) -> Result<(TokenStream2, TokenStream2), CodegenError> {
let types_mod_ident = type_gen.types_mod_ident();
let pallet_name = pallet.name();
let storage_entry_name_str = storage_entry.name();
let storage_entry_snake_case_name = storage_entry_name_str.to_snake_case();
let storage_entry_snake_case_ident = format_ident!("{storage_entry_snake_case_name}");
let Some(validation_hash) = pallet.storage_hash(storage_entry_name_str) else {
return Err(CodegenError::MissingStorageMetadata(
pallet_name.into(),
storage_entry_name_str.into(),
));
};
let docs = storage_entry.docs();
let docs: TokenStream2 = type_gen
.settings()
.should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
struct Input {
type_alias: syn::Ident,
type_path: TokenStream2,
}
let storage_key_types: Vec<Input> = storage_entry
.keys()
.enumerate()
.map(|(idx, key)| {
// Storage key aliases are just indexes; no names to use.
let type_alias = format_ident!("Param{}", idx);
// Path to the actual type we'll have generated for this input.
let type_path = type_gen
.resolve_type_path(key.key_id)
.expect("view function input type is in metadata; qed")
.to_token_stream(type_gen.settings());
Input {
type_alias,
type_path,
}
})
.collect();
let storage_key_tuple_types = storage_key_types
.iter()
.map(|i| {
let ty = &i.type_alias;
quote!(#storage_entry_snake_case_ident::#ty)
})
.collect::<Vec<_>>();
let storage_key_type_aliases = storage_key_types
.iter()
.map(|i| {
let ty = &i.type_alias;
let path = &i.type_path;
quote!(pub type #ty = #path;)
})
.collect::<Vec<_>>();
let storage_value_type_path = type_gen
.resolve_type_path(storage_entry.value_ty())?
.to_token_stream(type_gen.settings());
let is_plain = if storage_entry.keys().len() == 0 {
quote!(#crate_path::utils::Yes)
} else {
quote!(#crate_path::utils::Maybe)
};
let storage_entry_types = quote!(
pub mod #storage_entry_snake_case_ident {
use super::root_mod;
use super::#types_mod_ident;
#(#storage_key_type_aliases)*
pub mod output {
use super::#types_mod_ident;
pub type Output = #storage_value_type_path;
}
}
);
let storage_entry_method = quote!(
#docs
pub fn #storage_entry_snake_case_ident(&self) -> #crate_path::storage::address::StaticAddress<
(#(#storage_key_tuple_types,)*),
#storage_entry_snake_case_ident::output::Output,
#is_plain
> {
#crate_path::storage::address::StaticAddress::new_static(
#pallet_name,
#storage_entry_name_str,
[#(#validation_hash,)*],
)
}
);
Ok((storage_entry_types, storage_entry_method))
}
#[cfg(test)]
mod tests {
use frame_metadata::v15;
use scale_info::{MetaType, meta_type};
use pezkuwi_subxt_metadata::Metadata;
// TODO: Think about adding tests for storage codegen which can use this sort of function.
#[allow(dead_code)]
fn metadata_with_storage_entries(
storage_entries: impl IntoIterator<Item = (&'static str, MetaType)>,
) -> Metadata {
let storage_entries: Vec<v15::StorageEntryMetadata> = storage_entries
.into_iter()
.map(|(name, key)| v15::StorageEntryMetadata {
name,
modifier: v15::StorageEntryModifier::Optional,
ty: v15::StorageEntryType::Map {
hashers: vec![v15::StorageHasher::Blake2_128Concat],
key,
value: meta_type::<bool>(),
},
default: vec![],
docs: vec![],
})
.collect();
let pallet_1 = v15::PalletMetadata {
name: "Pallet1",
storage: Some(v15::PalletStorageMetadata {
prefix: Default::default(),
entries: storage_entries,
}),
calls: None,
event: None,
constants: vec![],
error: None,
index: 0,
docs: vec![],
};
let extrinsic_metadata = v15::ExtrinsicMetadata {
version: 0,
signed_extensions: vec![],
address_ty: meta_type::<()>(),
call_ty: meta_type::<()>(),
signature_ty: meta_type::<()>(),
extra_ty: meta_type::<()>(),
};
let metadata: Metadata = v15::RuntimeMetadataV15::new(
vec![pallet_1],
extrinsic_metadata,
meta_type::<()>(),
vec![],
v15::OuterEnums {
call_enum_ty: meta_type::<()>(),
event_enum_ty: meta_type::<()>(),
error_enum_ty: meta_type::<()>(),
},
v15::CustomMetadata {
map: Default::default(),
},
)
.try_into()
.expect("can build valid metadata");
metadata
}
}
-101
View File
@@ -1,101 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Errors that can be emitted from codegen.
use proc_macro2::{Span, TokenStream as TokenStream2};
use scale_typegen::TypegenError;
/// Error returned when the Codegen cannot generate the runtime API.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum CodegenError {
/// Cannot decode the metadata bytes.
#[error("Could not decode metadata, only V14 and V15 metadata are supported: {0}")]
Decode(#[from] codec::Error),
/// Out of line modules are not supported.
#[error(
"Out-of-line subxt modules are not supported, make sure you are providing a body to your module: pub mod polkadot {{ ... }}"
)]
InvalidModule(Span),
/// Invalid type path.
#[error("Invalid type path {0}: {1}")]
InvalidTypePath(String, syn::Error),
/// Metadata for constant could not be found.
#[error(
"Metadata for constant entry {0}_{1} could not be found. Make sure you are providing a valid substrate-based metadata"
)]
MissingConstantMetadata(String, String),
/// Metadata for storage could not be found.
#[error(
"Metadata for storage entry {0}_{1} could not be found. Make sure you are providing a valid substrate-based metadata"
)]
MissingStorageMetadata(String, String),
/// Metadata for call could not be found.
#[error(
"Metadata for call entry {0}_{1} could not be found. Make sure you are providing a valid substrate-based metadata"
)]
MissingCallMetadata(String, String),
/// Metadata for call could not be found.
#[error(
"Metadata for runtime API entry {0}_{1} could not be found. Make sure you are providing a valid substrate-based metadata"
)]
MissingRuntimeApiMetadata(String, String),
/// Call variant must have all named fields.
#[error(
"Call variant for type {0} must have all named fields. Make sure you are providing a valid substrate-based metadata"
)]
InvalidCallVariant(u32),
/// Type should be an variant/enum.
#[error(
"{0} type should be an variant/enum type. Make sure you are providing a valid substrate-based metadata"
)]
InvalidType(String),
/// Extrinsic call type could not be found.
#[error(
"Extrinsic call type could not be found. Make sure you are providing a valid substrate-based metadata"
)]
MissingCallType,
/// There are too many or too few hashers.
#[error(
"Could not generate functions for storage entry {storage_entry_name}. There are {key_count} keys, but only {hasher_count} hashers. The number of hashers must equal the number of keys or be exactly 1."
)]
InvalidStorageHasherCount {
/// The name of the storage entry
storage_entry_name: String,
/// Number of keys
key_count: usize,
/// Number of hashers
hasher_count: usize,
},
/// Cannot generate types.
#[error("Type Generation failed: {0}")]
TypeGeneration(#[from] TypegenError),
/// Error when generating metadata from Wasm-runtime
#[error("Failed to generate metadata from wasm file. reason: {0}")]
Wasm(String),
/// Other error.
#[error("Other error: {0}")]
Other(String),
}
impl CodegenError {
/// Fetch the location for this error.
// Todo: Probably worth storing location outside of the variant,
// so that there's a common way to set a location for some error.
fn get_location(&self) -> Span {
match self {
Self::InvalidModule(span) => *span,
Self::TypeGeneration(TypegenError::InvalidSubstitute(err)) => err.span,
Self::InvalidTypePath(_, err) => err.span(),
_ => proc_macro2::Span::call_site(),
}
}
/// Render the error as an invocation of syn::compile_error!.
pub fn into_compile_error(self) -> TokenStream2 {
let msg = self.to_string();
let span = self.get_location();
syn::Error::new(span, msg).into_compile_error()
}
}
-40
View File
@@ -1,40 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::error::CodegenError;
use syn::token;
#[derive(Debug, PartialEq, Eq)]
pub struct ItemMod {
vis: syn::Visibility,
mod_token: token::Mod,
pub ident: syn::Ident,
brace: token::Brace,
items: Vec<syn::Item>,
}
impl TryFrom<syn::ItemMod> for ItemMod {
type Error = CodegenError;
fn try_from(module: syn::ItemMod) -> Result<Self, Self::Error> {
let (brace, items) = match module.content {
Some((brace, items)) => (brace, items),
None => return Err(CodegenError::InvalidModule(module.ident.span())),
};
Ok(Self {
vis: module.vis,
mod_token: module.mod_token,
ident: module.ident,
brace,
items,
})
}
}
impl ItemMod {
pub fn rust_items(&self) -> impl Iterator<Item = &syn::Item> {
self.items.iter()
}
}
-445
View File
@@ -1,445 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Generate a type safe Subxt interface for a Substrate runtime from its metadata.
//! This is used by the `#[subxt]` macro and `subxt codegen` CLI command, but can also
//! be used directly if preferable.
#![deny(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
mod api;
pub mod error;
mod ir;
#[cfg(feature = "web")]
use getrandom as _;
use api::RuntimeGenerator;
use proc_macro2::TokenStream as TokenStream2;
use scale_typegen::typegen::settings::AllocCratePath;
use scale_typegen::{
DerivesRegistry, TypeGeneratorSettings, TypeSubstitutes, TypegenError,
typegen::settings::substitutes::absolute_path,
};
use std::collections::HashMap;
use syn::parse_quote;
// Part of the public interface, so expose:
pub use error::CodegenError;
pub use pezkuwi_subxt_metadata::Metadata;
pub use syn;
/// Generate a type safe interface to use with `subxt`.
/// The options exposed here are similar to those exposed via
/// the `#[subxt]` macro or via the `subxt codegen` CLI command.
/// Both use this under the hood.
///
/// # Example
///
/// Generating an interface using all of the defaults:
///
/// ```rust,standalone_crate
/// use codec::Decode;
/// use pezkuwi_subxt_codegen::{ Metadata, CodegenBuilder };
///
/// // Get hold of and decode some metadata:
/// let encoded = std::fs::read("../artifacts/polkadot_metadata_full.scale").unwrap();
/// let metadata = Metadata::decode(&mut &*encoded).unwrap();
///
/// // Generate a TokenStream representing the code for the interface.
/// // This can be converted to a string, displayed as-is or output from a macro.
/// let token_stream = CodegenBuilder::new().generate(metadata);
/// ````
pub struct CodegenBuilder {
crate_path: syn::Path,
use_default_derives: bool,
use_default_substitutions: bool,
generate_docs: bool,
runtime_types_only: bool,
item_mod: syn::ItemMod,
extra_global_derives: Vec<syn::Path>,
extra_global_attributes: Vec<syn::Attribute>,
type_substitutes: HashMap<syn::Path, syn::Path>,
derives_for_type: HashMap<syn::TypePath, Vec<syn::Path>>,
attributes_for_type: HashMap<syn::TypePath, Vec<syn::Attribute>>,
derives_for_type_recursive: HashMap<syn::TypePath, Vec<syn::Path>>,
attributes_for_type_recursive: HashMap<syn::TypePath, Vec<syn::Attribute>>,
}
impl Default for CodegenBuilder {
fn default() -> Self {
CodegenBuilder {
crate_path: syn::parse_quote!(::pezkuwi_subxt::ext::pezkuwi_subxt_core),
use_default_derives: true,
use_default_substitutions: true,
generate_docs: true,
runtime_types_only: false,
item_mod: syn::parse_quote!(
pub mod api {}
),
extra_global_derives: Vec::new(),
extra_global_attributes: Vec::new(),
type_substitutes: HashMap::new(),
derives_for_type: HashMap::new(),
attributes_for_type: HashMap::new(),
derives_for_type_recursive: HashMap::new(),
attributes_for_type_recursive: HashMap::new(),
}
}
}
impl CodegenBuilder {
/// Construct a builder to configure and generate a type-safe interface for Subxt.
pub fn new() -> Self {
CodegenBuilder::default()
}
/// Disable the default derives that are applied to all types.
///
/// # Warning
///
/// This is not recommended, and is highly likely to break some part of the
/// generated interface. Expect compile errors.
pub fn disable_default_derives(&mut self) {
self.use_default_derives = false;
}
/// Disable the default type substitutions that are applied to the generated
/// code.
///
/// # Warning
///
/// This is not recommended, and is highly likely to break some part of the
/// generated interface. Expect compile errors.
pub fn disable_default_substitutes(&mut self) {
self.use_default_substitutions = false;
}
/// Disable the output of doc comments associated with the generated types and
/// methods. This can reduce the generated code size at the expense of losing
/// documentation.
pub fn no_docs(&mut self) {
self.generate_docs = false;
}
/// Only generate the types, and don't generate the rest of the Subxt specific
/// interface.
pub fn runtime_types_only(&mut self) {
self.runtime_types_only = true;
}
/// Set the additional derives that will be applied to all types. By default,
/// a set of derives required for Subxt are automatically added for all types.
///
/// # Warning
///
/// Invalid derives, or derives that cannot be applied to _all_ of the generated
/// types (taking into account that some types are substituted for hand written ones
/// that we cannot add extra derives for) will lead to compile errors in the
/// generated code.
pub fn set_additional_global_derives(&mut self, derives: Vec<syn::Path>) {
self.extra_global_derives = derives;
}
/// Set the additional attributes that will be applied to all types. By default,
/// a set of attributes required for Subxt are automatically added for all types.
///
/// # Warning
///
/// Invalid attributes can very easily lead to compile errors in the generated code.
pub fn set_additional_global_attributes(&mut self, attributes: Vec<syn::Attribute>) {
self.extra_global_attributes = attributes;
}
/// Set additional derives for a specific type at the path given.
///
/// If you want to set the additional derives on all contained types recursively as well,
/// you can set the `recursive` argument to `true`. If you don't do that,
/// there might be compile errors in the generated code, if the derived trait
/// relies on the fact that contained types also implement that trait.
pub fn add_derives_for_type(
&mut self,
ty: syn::TypePath,
derives: impl IntoIterator<Item = syn::Path>,
recursive: bool,
) {
if recursive {
self.derives_for_type_recursive
.entry(ty)
.or_default()
.extend(derives);
} else {
self.derives_for_type.entry(ty).or_default().extend(derives);
}
}
/// Set additional attributes for a specific type at the path given.
///
/// Setting the `recursive` argument to `true` will additionally add the specified
/// attributes to all contained types recursively.
pub fn add_attributes_for_type(
&mut self,
ty: syn::TypePath,
attributes: impl IntoIterator<Item = syn::Attribute>,
recursive: bool,
) {
if recursive {
self.attributes_for_type_recursive
.entry(ty)
.or_default()
.extend(attributes);
} else {
self.attributes_for_type
.entry(ty)
.or_default()
.extend(attributes);
}
}
/// Substitute a type at the given path with some type at the second path. During codegen,
/// we will avoid generating the type at the first path given, and instead point any references
/// to that type to the second path given.
///
/// The substituted type will need to implement the relevant traits to be compatible with the
/// original, and it will need to SCALE encode and SCALE decode in a compatible way.
pub fn set_type_substitute(&mut self, ty: syn::Path, with: syn::Path) {
self.type_substitutes.insert(ty, with);
}
/// By default, all of the code is generated inside a module `pub mod api {}`. We decorate
/// this module with a few attributes to reduce compile warnings and things. You can provide a
/// target module here, allowing you to add additional attributes or inner code items (with the
/// warning that duplicate identifiers will lead to compile errors).
pub fn set_target_module(&mut self, item_mod: syn::ItemMod) {
self.item_mod = item_mod;
}
/// Set the path to the `subxt` crate. By default, we expect it to be at `::pezkuwi_subxt::ext::pezkuwi_subxt_core`.
///
/// # Panics
///
/// Panics if the path provided is not an absolute path.
pub fn set_subxt_crate_path(&mut self, crate_path: syn::Path) {
if absolute_path(crate_path.clone()).is_err() {
// Throw an error here, because otherwise we end up with a harder to comprehend error when
// substitute types don't begin with an absolute path.
panic!(
"The provided crate path must be an absolute path, ie prefixed with '::' or 'crate'"
);
}
self.crate_path = crate_path;
}
/// Generate an interface, assuming that the default path to the `subxt` crate is `::pezkuwi_subxt::ext::pezkuwi_subxt_core`.
/// If the `subxt` crate is not available as a top level dependency, use `generate` and provide
/// a valid path to the `subxt¦ crate.
pub fn generate(self, metadata: Metadata) -> Result<TokenStream2, CodegenError> {
let crate_path = self.crate_path;
let mut derives_registry: DerivesRegistry = if self.use_default_derives {
default_derives(&crate_path)
} else {
DerivesRegistry::new()
};
derives_registry.add_derives_for_all(self.extra_global_derives);
derives_registry.add_attributes_for_all(self.extra_global_attributes);
for (ty, derives) in self.derives_for_type {
derives_registry.add_derives_for(ty, derives, false);
}
for (ty, derives) in self.derives_for_type_recursive {
derives_registry.add_derives_for(ty, derives, true);
}
for (ty, attributes) in self.attributes_for_type {
derives_registry.add_attributes_for(ty, attributes, false);
}
for (ty, attributes) in self.attributes_for_type_recursive {
derives_registry.add_attributes_for(ty, attributes, true);
}
let mut type_substitutes: TypeSubstitutes = if self.use_default_substitutions {
default_substitutes(&crate_path)
} else {
TypeSubstitutes::new()
};
for (from, with) in self.type_substitutes {
let abs_path = absolute_path(with).map_err(TypegenError::from)?;
type_substitutes
.insert(from, abs_path)
.map_err(TypegenError::from)?;
}
let item_mod = self.item_mod;
let generator = RuntimeGenerator::new(metadata);
let should_gen_docs = self.generate_docs;
if self.runtime_types_only {
generator.generate_runtime_types(
item_mod,
derives_registry,
type_substitutes,
crate_path,
should_gen_docs,
)
} else {
generator.generate_runtime(
item_mod,
derives_registry,
type_substitutes,
crate_path,
should_gen_docs,
)
}
}
}
/// The default [`scale_typegen::TypeGeneratorSettings`], subxt is using for generating code.
/// Useful for emulating subxt's code generation settings from e.g. subxt-explorer.
pub fn default_subxt_type_gen_settings() -> TypeGeneratorSettings {
let crate_path: syn::Path = parse_quote!(::pezkuwi_subxt::ext::pezkuwi_subxt_core);
let derives = default_derives(&crate_path);
let substitutes = default_substitutes(&crate_path);
subxt_type_gen_settings(derives, substitutes, &crate_path, true)
}
fn subxt_type_gen_settings(
derives: scale_typegen::DerivesRegistry,
substitutes: scale_typegen::TypeSubstitutes,
crate_path: &syn::Path,
should_gen_docs: bool,
) -> TypeGeneratorSettings {
// Are we using codec::Encode or codec::Decode derives?
let are_codec_derives_used = derives.default_derives().derives().iter().any(|path| {
let mut segments_backwards = path.segments.iter().rev();
let ident = segments_backwards.next();
let module = segments_backwards.next();
let is_ident_match = ident.is_some_and(|s| s.ident == "Encode" || s.ident == "Decode");
let is_module_match = module.is_some_and(|s| s.ident == "codec");
is_ident_match && is_module_match
});
// If we're inserting the codec derives, we also should use `CompactAs` where necessary.
let compact_as_type_path =
are_codec_derives_used.then(|| parse_quote!(#crate_path::ext::codec::CompactAs));
TypeGeneratorSettings {
types_mod_ident: parse_quote!(runtime_types),
should_gen_docs,
derives,
substitutes,
decoded_bits_type_path: Some(parse_quote!(#crate_path::utils::bits::DecodedBits)),
compact_as_type_path,
compact_type_path: Some(parse_quote!(#crate_path::ext::codec::Compact)),
alloc_crate_path: AllocCratePath::Custom(parse_quote!(#crate_path::alloc)),
// Note: even when we don't use codec::Encode and codec::Decode, we need to keep #[codec(...)]
// attributes because `#[codec(skip)]` is still used/important with `EncodeAsType` and `DecodeAsType`.
insert_codec_attributes: true,
}
}
fn default_derives(crate_path: &syn::Path) -> DerivesRegistry {
let encode_crate_path = quote::quote! { #crate_path::ext::scale_encode }.to_string();
let decode_crate_path = quote::quote! { #crate_path::ext::scale_decode }.to_string();
let derives: [syn::Path; 3] = [
parse_quote!(#crate_path::ext::scale_encode::EncodeAsType),
parse_quote!(#crate_path::ext::scale_decode::DecodeAsType),
parse_quote!(Debug),
];
let attributes: [syn::Attribute; 2] = [
parse_quote!(#[encode_as_type(crate_path = #encode_crate_path)]),
parse_quote!(#[decode_as_type(crate_path = #decode_crate_path)]),
];
let mut derives_registry = DerivesRegistry::new();
derives_registry.add_derives_for_all(derives);
derives_registry.add_attributes_for_all(attributes);
derives_registry
}
fn default_substitutes(crate_path: &syn::Path) -> TypeSubstitutes {
let mut type_substitutes = TypeSubstitutes::new();
let defaults: [(syn::Path, syn::Path); 13] = [
(
parse_quote!(bitvec::order::Lsb0),
parse_quote!(#crate_path::utils::bits::Lsb0),
),
(
parse_quote!(bitvec::order::Msb0),
parse_quote!(#crate_path::utils::bits::Msb0),
),
(
parse_quote!(pezsp_core::crypto::AccountId32),
parse_quote!(#crate_path::utils::AccountId32),
),
(
parse_quote!(fp_account::AccountId20),
parse_quote!(#crate_path::utils::AccountId20),
),
(
parse_quote!(pezsp_runtime::multiaddress::MultiAddress),
parse_quote!(#crate_path::utils::MultiAddress),
),
(
parse_quote!(primitive_types::H160),
parse_quote!(#crate_path::utils::H160),
),
(
parse_quote!(primitive_types::H256),
parse_quote!(#crate_path::utils::H256),
),
(
parse_quote!(primitive_types::H512),
parse_quote!(#crate_path::utils::H512),
),
(
parse_quote!(pezframe_support::traits::misc::WrapperKeepOpaque),
parse_quote!(#crate_path::utils::WrapperKeepOpaque),
),
// BTreeMap and BTreeSet impose an `Ord` constraint on their key types. This
// can cause an issue with generated code that doesn't impl `Ord` by default.
// Decoding them to Vec by default (KeyedVec is just an alias for Vec with
// suitable type params) avoids these issues.
(
parse_quote!(BTreeMap),
parse_quote!(#crate_path::utils::KeyedVec),
),
(
parse_quote!(BinaryHeap),
parse_quote!(#crate_path::alloc::vec::Vec),
),
(
parse_quote!(BTreeSet),
parse_quote!(#crate_path::alloc::vec::Vec),
),
// The `UncheckedExtrinsic(pub Vec<u8>)` is part of the runtime API calls.
// The inner bytes represent the encoded extrinsic, however when deriving the
// `EncodeAsType` the bytes would be re-encoded. This leads to the bytes
// being altered by adding the length prefix in front of them.
// Note: Not sure if this is appropriate or not. The most recent polkadot.rs file does not have these.
(
parse_quote!(pezsp_runtime::generic::unchecked_extrinsic::UncheckedExtrinsic),
parse_quote!(#crate_path::utils::UncheckedExtrinsic),
),
];
let defaults = defaults.into_iter().map(|(from, to)| {
(
from,
absolute_path(to).expect("default substitutes above are absolute paths; qed"),
)
});
type_substitutes
.extend(defaults)
.expect("default substitutes can always be parsed; qed");
type_substitutes
}