// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Generate code for submitting extrinsics and query storage of a Substrate runtime.
mod calls;
mod constants;
mod custom_values;
mod errors;
mod events;
mod runtime_apis;
mod storage;
use subxt_metadata::Metadata;
use super::DerivesRegistry;
use crate::api::custom_values::generate_custom_values;
use crate::error::CodegenError;
use crate::{
ir,
types::{CompositeDef, CompositeDefFields, TypeGenerator, TypeSubstitutes},
utils::{fetch_metadata_bytes_blocking, MetadataVersion, Url},
CratePath,
};
use codec::Decode;
use heck::ToSnakeCase as _;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use std::{collections::HashMap, fs, io::Read, path, string::ToString};
use syn::parse_quote;
/// Generate the runtime API for interacting with a substrate runtime.
pub struct GenerateRuntimeApi {
item_mod: syn::ItemMod,
derives: DerivesRegistry,
type_substitutes: TypeSubstitutes,
crate_path: CratePath,
should_gen_docs: bool,
runtime_types_only: bool,
unstable_metadata: bool,
}
impl GenerateRuntimeApi {
/// Construct a new [`GenerateRuntimeApi`].
pub fn new(item_mod: syn::ItemMod, crate_path: CratePath) -> Self {
GenerateRuntimeApi {
item_mod,
derives: DerivesRegistry::new(),
type_substitutes: TypeSubstitutes::new(),
crate_path,
should_gen_docs: false,
runtime_types_only: false,
unstable_metadata: false,
}
}
/// Provide custom derives for the generated types.
///
/// Default is no derives.
pub fn derives_registry(mut self, derives: DerivesRegistry) -> Self {
self.derives = derives;
self
}
/// Provide custom type substitutes.
///
/// Default is no substitutes.
pub fn type_substitutes(mut self, type_substitutes: TypeSubstitutes) -> Self {
self.type_substitutes = type_substitutes;
self
}
/// True if the generated API contains the documentation from the metadata.
///
/// Default: false.
pub fn generate_docs(mut self, should_gen_docs: bool) -> Self {
self.should_gen_docs = should_gen_docs;
self
}
/// Whether to limit code generation to only runtime types.
///
/// Default: false.
pub fn runtime_types_only(mut self, runtime_types_only: bool) -> Self {
self.runtime_types_only = runtime_types_only;
self
}
/// Whether to fetch the unstable metadata first.
///
/// # Note
///
/// This takes effect only if the API is generated from URL.
///
/// Default: false.
pub fn unstable_metadata(mut self, unstable_metadata: bool) -> Self {
self.unstable_metadata = unstable_metadata;
self
}
/// Generate the runtime API from path.
pub fn generate_from_path
(self, path: P) -> Result
where
P: AsRef,
{
let to_err = |err| CodegenError::Io(path.as_ref().to_string_lossy().into(), err);
let mut file = fs::File::open(&path).map_err(to_err)?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes).map_err(to_err)?;
let metadata = Metadata::decode(&mut &bytes[..])?;
generate_runtime_api_with_metadata(
self.item_mod,
metadata,
self.derives,
self.type_substitutes,
self.crate_path,
self.should_gen_docs,
self.runtime_types_only,
)
}
/// Generate the runtime API from the provided metadata bytes.
pub fn generate_from_bytes(self, bytes: &[u8]) -> Result {
let metadata = Metadata::decode(&mut &bytes[..])?;
generate_runtime_api_with_metadata(
self.item_mod,
metadata,
self.derives,
self.type_substitutes,
self.crate_path,
self.should_gen_docs,
self.runtime_types_only,
)
}
/// Generate the runtime API from URL.
///
/// The metadata will be downloaded from a node at the provided URL.
/// This function blocks while retrieving the metadata.
///
/// # Warning
///
/// Not recommended to be used in production environments.
pub fn generate_from_url(self, url: Url) -> Result {
fn fetch_metadata(url: Url, version: MetadataVersion) -> Result {
let bytes = fetch_metadata_bytes_blocking(url, version)?;
Ok(Metadata::decode(&mut &bytes[..])?)
}
let metadata = self
.unstable_metadata
.then(|| fetch_metadata(url.clone(), MetadataVersion::Unstable).ok())
.flatten();
let metadata = if let Some(unstable) = metadata {
unstable
} else {
match fetch_metadata(url.clone(), MetadataVersion::Version(15)) {
Ok(metadata) => metadata,
Err(_) => fetch_metadata(url, MetadataVersion::Version(14))?,
}
};
generate_runtime_api_with_metadata(
self.item_mod,
metadata,
self.derives,
self.type_substitutes,
self.crate_path,
self.should_gen_docs,
self.runtime_types_only,
)
}
}
/// Generates the API for interacting with a substrate runtime, using the `subxt::Metadata`.
fn generate_runtime_api_with_metadata(
item_mod: syn::ItemMod,
metadata: Metadata,
derives: DerivesRegistry,
type_substitutes: TypeSubstitutes,
crate_path: CratePath,
should_gen_docs: bool,
runtime_types_only: bool,
) -> Result {
let generator = RuntimeGenerator::new(metadata);
if runtime_types_only {
generator.generate_runtime_types(
item_mod,
derives,
type_substitutes,
crate_path,
should_gen_docs,
)
} else {
generator.generate_runtime(
item_mod,
derives,
type_substitutes,
crate_path,
should_gen_docs,
)
}
}
/// 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 {
Self::ensure_unique_type_paths(&mut metadata);
RuntimeGenerator { metadata }
}
/// Ensure that every unique type we'll be generating or referring to also has a
/// unique path, so that types with matching paths don't end up overwriting each other
/// in the codegen. We ignore any types with generics; Subxt actually endeavours to
/// de-duplicate those into single types with a generic.
fn ensure_unique_type_paths(metadata: &mut Metadata) {
let mut visited_path_counts = HashMap::, usize>::new();
for ty in metadata.types_mut().types.iter_mut() {
// Ignore types without a path (ie prelude types).
if ty.ty.path.namespace().is_empty() {
continue;
}
let has_valid_type_params = ty.ty.type_params.iter().any(|tp| tp.ty.is_some());
// Ignore types which have generic params that the type generation will use.
// Ordinarily we'd expect that any two types with identical paths must be parameterized
// in order to share the path. However scale-info doesn't understand all forms of generics
// properly I think (eg generics that have associated types that can differ), and so in
// those cases we need to fix the paths for Subxt to generate correct code.
if has_valid_type_params {
continue;
}
// Count how many times we've seen the same path already.
let visited_count = visited_path_counts
.entry(ty.ty.path.segments.clone())
.or_default();
*visited_count += 1;
// alter the type so that if it's been seen more than once, we append a number to
// its name to ensure that every unique type has a unique path, too.
if *visited_count > 1 {
if let Some(name) = ty.ty.path.segments.last_mut() {
*name = format!("{name}{visited_count}");
}
}
}
}
/// 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: DerivesRegistry,
type_substitutes: TypeSubstitutes,
crate_path: CratePath,
should_gen_docs: bool,
) -> Result {
let item_mod_attrs = item_mod.attrs.clone();
let item_mod_ir = ir::ItemMod::try_from(item_mod)?;
let mod_ident = &item_mod_ir.ident;
let rust_items = item_mod_ir.rust_items();
let type_gen = TypeGenerator::new(
self.metadata.types(),
"runtime_types",
type_substitutes,
derives,
crate_path,
should_gen_docs,
);
let types_mod = type_gen.generate_types_mod()?;
Ok(quote! {
#( #item_mod_attrs )*
#[allow(dead_code, unused_imports, non_camel_case_types)]
#[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: DerivesRegistry,
type_substitutes: TypeSubstitutes,
crate_path: CratePath,
should_gen_docs: bool,
) -> Result {
let item_mod_attrs = item_mod.attrs.clone();
let item_mod_ir = ir::ItemMod::try_from(item_mod)?;
let type_gen = TypeGenerator::new(
self.metadata.types(),
"runtime_types",
type_substitutes,
derives,
crate_path.clone(),
should_gen_docs,
);
let types_mod = type_gen.generate_types_mod()?;
let types_mod_ident = types_mod.ident();
let pallets_with_mod_names = self
.metadata
.pallets()
.map(|pallet| {
(
pallet,
format_ident!("{}", pallet.name().to_string().to_snake_case()),
)
})
.collect::>();
// 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 metadata_hash = self.metadata.hasher().hash();
let modules = pallets_with_mod_names
.iter()
.map(|(pallet, mod_name)| {
let calls = calls::generate_calls(
&type_gen,
pallet,
types_mod_ident,
&crate_path,
should_gen_docs,
)?;
let event = events::generate_events(
&type_gen,
pallet,
types_mod_ident,
&crate_path,
should_gen_docs,
)?;
let storage_mod = storage::generate_storage(
&type_gen,
pallet,
types_mod_ident,
&crate_path,
should_gen_docs,
)?;
let constants_mod = constants::generate_constants(
&type_gen,
pallet,
types_mod_ident,
&crate_path,
should_gen_docs,
)?;
let errors = errors::generate_error_type_alias(&type_gen, pallet, should_gen_docs)?;
Ok(quote! {
pub mod #mod_name {
use super::root_mod;
use super::#types_mod_ident;
#errors
#calls
#event
#storage_mod
#constants_mod
}
})
})
.collect::, 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 rust_items = item_mod_ir.rust_items();
let apis_mod = runtime_apis::generate_runtime_apis(
&self.metadata,
&type_gen,
types_mod_ident,
&crate_path,
should_gen_docs,
)?;
// 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());
let event_path = type_gen.resolve_type_path(self.metadata.outer_enums().event_enum_ty());
let error_path = type_gen.resolve_type_path(self.metadata.outer_enums().error_enum_ty());
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)]
#[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 returned when there is a runtime issue.
pub type DispatchError = #types_mod_ident::sp_runtime::DispatchError;
/// The outer event enum.
pub type Event = #event_path;
/// The outer extrinsic enum.
pub type Call = #call_path;
/// The outer error enum representing 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 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
}
)*
}
/// 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(
type_gen: &TypeGenerator,
type_id: u32,
variant_to_struct_name: F,
error_message_type_name: &str,
crate_path: &CratePath,
should_gen_docs: bool,
) -> Result, CodegenError>
where
F: Fn(&str) -> std::borrow::Cow,
{
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 struct_name = variant_to_struct_name(&var.name);
let fields = CompositeDefFields::from_scale_info_fields(
struct_name.as_ref(),
&var.fields,
&[],
type_gen,
)?;
let docs = should_gen_docs.then_some(&*var.docs).unwrap_or_default();
let struct_def = CompositeDef::struct_def(
&ty,
struct_name.as_ref(),
Default::default(),
fields,
Some(parse_quote!(pub)),
type_gen,
docs,
crate_path,
)?;
Ok((var.name.to_string(), struct_def))
})
.collect()
}