feat: Vendor pezkuwi-subxt and pezkuwi-zombienet-sdk into monorepo
- Add pezkuwi-subxt crates to vendor/pezkuwi-subxt - Add pezkuwi-zombienet-sdk crates to vendor/pezkuwi-zombienet-sdk - Convert git dependencies to path dependencies - Add vendor crates to workspace members - Remove test/example crates from vendor (not needed for SDK) - Fix feature propagation issues detected by zepter - Fix workspace inheritance for internal dependencies - All 606 crates now in workspace - All 6919 internal dependency links verified correct - No git dependencies remaining
This commit is contained in:
+143
@@ -0,0 +1,143 @@
|
||||
// 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 pezkuwi_subxt_metadata::PalletMetadata;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use scale_typegen::{
|
||||
TypeGenerator,
|
||||
typegen::ir::{ToTokensWithSettings, type_ir::CompositeIRKind},
|
||||
};
|
||||
|
||||
/// 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 )*
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// 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 pezkuwi_subxt_metadata::PalletMetadata;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use scale_typegen::{TypeGenerator, typegen::ir::ToTokensWithSettings};
|
||||
|
||||
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)*
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// 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 pezkuwi_subxt_metadata::{CustomValueMetadata, Metadata};
|
||||
use scale_typegen::{TypeGenerator, typegen::ir::ToTokensWithSettings};
|
||||
use std::collections::HashSet;
|
||||
|
||||
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,)*])
|
||||
}
|
||||
))
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// 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 pezkuwi_subxt_metadata::PalletMetadata;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use scale_typegen::TypeGenerator;
|
||||
|
||||
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;
|
||||
})
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
// 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 pezkuwi_subxt_metadata::PalletMetadata;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use scale_typegen::{TypeGenerator, typegen::ir::ToTokensWithSettings};
|
||||
|
||||
/// 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 `pezkuwi_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 )*
|
||||
}
|
||||
})
|
||||
}
|
||||
+458
@@ -0,0 +1,458 @@
|
||||
// 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 Bizinikiwi runtime.
|
||||
|
||||
mod calls;
|
||||
mod constants;
|
||||
mod custom_values;
|
||||
mod errors;
|
||||
mod events;
|
||||
mod pallet_view_functions;
|
||||
mod runtime_apis;
|
||||
mod storage;
|
||||
|
||||
use pezkuwi_subxt_metadata::Metadata;
|
||||
use scale_typegen::{
|
||||
TypeGenerator,
|
||||
typegen::{
|
||||
ir::{
|
||||
ToTokensWithSettings,
|
||||
type_ir::{CompositeFieldIR, CompositeIR, CompositeIRKind},
|
||||
},
|
||||
type_params::TypeParameters,
|
||||
type_path::TypePath,
|
||||
},
|
||||
};
|
||||
use syn::{Ident, parse_quote};
|
||||
|
||||
use crate::{
|
||||
api::custom_values::generate_custom_values, error::CodegenError, ir, subxt_type_gen_settings,
|
||||
};
|
||||
|
||||
use heck::{ToSnakeCase as _, ToUpperCamelCase};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
/// Create the API for interacting with a Bizinikiwi 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 Bizinikiwi 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 Bizinikiwi 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.
|
||||
// Bizinikiwi exposes those under `kitchensink_runtime`, while Pezkuwi under
|
||||
// `pezkuwi_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 )*
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
// 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 pezkuwi_subxt_metadata::{PalletMetadata, ViewFunctionMetadata};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use scale_typegen::{TypeGenerator, typegen::ir::ToTokensWithSettings};
|
||||
use std::collections::HashSet;
|
||||
|
||||
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))
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
// 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 _, ToUpperCamelCase as _};
|
||||
|
||||
use pezkuwi_subxt_metadata::{Metadata, RuntimeApiMetadata};
|
||||
use scale_typegen::{TypeGenerator, typegen::ir::ToTokensWithSettings};
|
||||
|
||||
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 pezkuwi_subxt_metadata::Metadata;
|
||||
use quote::quote;
|
||||
use scale_info::meta_type;
|
||||
|
||||
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, 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, 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, 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()));
|
||||
}
|
||||
}
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
// 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 pezkuwi_subxt_metadata::{PalletMetadata, StorageEntryMetadata};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use scale_typegen::TypeGenerator;
|
||||
|
||||
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 pezkuwi_subxt_metadata::Metadata;
|
||||
use scale_info::{MetaType, meta_type};
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user