mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-22 19:38:00 +00:00
Use scale-encode and scale-decode to encode and decode based on metadata (#842)
* WIP EncodeAsType and DecodeAsType * remove silly cli experiment code * Get things finally compiling with EncodeAsType and DecodeAsType * update codegen test and WrapperKeepOpaque proper impl (in case it shows up in codegen) * fix tests * accomodate scale-value changes * starting to migrate to EncodeAsType/DecodeAsType * static event decoding and tx encoding to use DecodeAsFields/EncodeAsFields * some tidy up and add decode(skip) attrs where needed * fix root event decoding * #[codec(skip)] will do, and combine map_key stuff into storage_address since it's all specific to that * fmt and clippy * update Cargo.lock * remove patched scale-encode * bump scale-encode to 0.1 and remove unused dep in testing crate * update deps and use released scale-decode * update scale-value to latest to remove git branch * Apply suggestions from code review Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> * remove sorting in derives/attr generation; spit them out in order given * re-add derive sorting; it's a hashmap * StaticTxPayload and DynamicTxPayload rolled into single Payload struct * StaticStorageAddress and DynamicStorageAddress into single Address struct * Fix storage address byte retrieval * StaticConstantAddress and DynamicConstantAddress => Address * Simplify storage codegen to fix test * Add comments * Alias to RuntimeEvent rather than making another, and prep for substituting call type * remove unnecessary clone * Fix docs and failing UI test * root_bytes -> to_root_bytes * document error case in StorageClient::address_bytes() --------- Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
This commit is contained in:
@@ -104,8 +104,8 @@ pub fn generate_calls(
|
||||
pub fn #fn_name(
|
||||
&self,
|
||||
#( #call_fn_args, )*
|
||||
) -> #crate_path::tx::StaticTxPayload<#struct_name> {
|
||||
#crate_path::tx::StaticTxPayload::new(
|
||||
) -> #crate_path::tx::Payload<#struct_name> {
|
||||
#crate_path::tx::Payload::new_static(
|
||||
#pallet_name,
|
||||
#call_name,
|
||||
#struct_name { #( #call_args, )* },
|
||||
|
||||
@@ -73,8 +73,8 @@ pub fn generate_constants(
|
||||
|
||||
Ok(quote! {
|
||||
#docs
|
||||
pub fn #fn_name(&self) -> #crate_path::constants::StaticConstantAddress<#crate_path::metadata::DecodeStaticType<#return_ty>> {
|
||||
#crate_path::constants::StaticConstantAddress::new(
|
||||
pub fn #fn_name(&self) -> #crate_path::constants::Address<#return_ty> {
|
||||
#crate_path::constants::Address::new_static(
|
||||
#pallet_name,
|
||||
#constant_name,
|
||||
[#(#constant_hash,)*]
|
||||
|
||||
+65
-32
@@ -53,6 +53,9 @@ use syn::parse_quote;
|
||||
/// Error returned when the Codegen cannot generate the runtime API.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CodegenError {
|
||||
/// The given metadata type could not be found.
|
||||
#[error("Could not find type with ID {0} in the type registry; please raise a support issue.")]
|
||||
TypeNotFound(u32),
|
||||
/// Cannot fetch the metadata bytes.
|
||||
#[error("Failed to fetch metadata, make sure that you're pointing at a node which is providing V14 metadata: {0}")]
|
||||
Fetch(#[from] FetchMetadataError),
|
||||
@@ -80,12 +83,6 @@ pub enum CodegenError {
|
||||
/// 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 metadata V14")]
|
||||
MissingStorageMetadata(String, String),
|
||||
/// StorageNMap should have N hashers.
|
||||
#[error("Number of hashers ({0}) does not equal 1 for StorageMap, or match number of fields ({1}) for StorageNMap. Make sure you are providing a valid metadata V14")]
|
||||
MismatchHashers(usize, usize),
|
||||
/// Expected to find one hasher for StorageMap.
|
||||
#[error("No hasher found for single key. Make sure you are providing a valid metadata V14")]
|
||||
MissingHasher,
|
||||
/// 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 metadata V14")]
|
||||
MissingCallMetadata(String, String),
|
||||
@@ -319,13 +316,12 @@ impl RuntimeGenerator {
|
||||
) -> Result<TokenStream2, CodegenError> {
|
||||
let item_mod_attrs = item_mod.attrs.clone();
|
||||
let item_mod_ir = ir::ItemMod::try_from(item_mod)?;
|
||||
let default_derives = derives.default_derives();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&self.metadata.types,
|
||||
"runtime_types",
|
||||
type_substitutes,
|
||||
derives.clone(),
|
||||
derives,
|
||||
crate_path.clone(),
|
||||
should_gen_docs,
|
||||
);
|
||||
@@ -343,6 +339,28 @@ impl RuntimeGenerator {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Get the path to the `Runtime` struct. We assume that the same path contains
|
||||
// RuntimeCall and RuntimeEvent.
|
||||
let runtime_type_id = self.metadata.ty.id();
|
||||
let runtime_path_segments = self
|
||||
.metadata
|
||||
.types
|
||||
.resolve(runtime_type_id)
|
||||
.ok_or(CodegenError::TypeNotFound(runtime_type_id))?
|
||||
.path()
|
||||
.namespace()
|
||||
.iter()
|
||||
.map(|part| syn::PathSegment::from(format_ident!("{}", part)));
|
||||
let runtime_path_suffix = syn::Path {
|
||||
leading_colon: None,
|
||||
segments: syn::punctuated::Punctuated::from_iter(runtime_path_segments),
|
||||
};
|
||||
let runtime_path = if runtime_path_suffix.segments.is_empty() {
|
||||
quote!(#types_mod_ident)
|
||||
} else {
|
||||
quote!(#types_mod_ident::#runtime_path_suffix)
|
||||
};
|
||||
|
||||
// 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.
|
||||
@@ -407,26 +425,24 @@ impl RuntimeGenerator {
|
||||
})
|
||||
.collect::<Result<Vec<_>, CodegenError>>()?;
|
||||
|
||||
let outer_event_variants = self.metadata.pallets.iter().filter_map(|p| {
|
||||
let variant_name = format_ident!("{}", p.name);
|
||||
let mod_name = format_ident!("{}", p.name.to_string().to_snake_case());
|
||||
let index = proc_macro2::Literal::u8_unsuffixed(p.index);
|
||||
|
||||
let root_event_if_arms = self.metadata.pallets.iter().filter_map(|p| {
|
||||
let variant_name_str = &p.name;
|
||||
let variant_name = format_ident!("{}", variant_name_str);
|
||||
let mod_name = format_ident!("{}", variant_name_str.to_string().to_snake_case());
|
||||
p.event.as_ref().map(|_| {
|
||||
// An 'if' arm for the RootEvent impl to match this variant name:
|
||||
quote! {
|
||||
#[codec(index = #index)]
|
||||
#variant_name(#mod_name::Event),
|
||||
if pallet_name == #variant_name_str {
|
||||
return Ok(Event::#variant_name(#mod_name::Event::decode_with_metadata(
|
||||
&mut &*pallet_bytes,
|
||||
pallet_ty,
|
||||
metadata
|
||||
)?));
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let outer_event = quote! {
|
||||
#default_derives
|
||||
pub enum Event {
|
||||
#( #outer_event_variants )*
|
||||
}
|
||||
};
|
||||
|
||||
let mod_ident = &item_mod_ir.ident;
|
||||
let pallets_with_constants: Vec<_> = pallets_with_mod_names
|
||||
.iter()
|
||||
@@ -456,22 +472,36 @@ impl RuntimeGenerator {
|
||||
#[allow(dead_code, unused_imports, non_camel_case_types)]
|
||||
#[allow(clippy::all)]
|
||||
pub mod #mod_ident {
|
||||
// Preserve any Rust items that were previously defined in the adorned module
|
||||
// Preserve any Rust items that were previously defined in the adorned module.
|
||||
#( #rust_items ) *
|
||||
|
||||
// Make it easy to access the root via `root_mod` at different levels:
|
||||
use super::#mod_ident as root_mod;
|
||||
// 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,)* ];
|
||||
|
||||
#outer_event
|
||||
#( #modules )*
|
||||
#types_mod
|
||||
/// The statically generated runtime call type.
|
||||
pub type Call = #runtime_path::RuntimeCall;
|
||||
|
||||
/// The default error type returned when there is a runtime issue,
|
||||
/// exposed here for ease of use.
|
||||
/// The error type returned when there is a runtime issue.
|
||||
pub type DispatchError = #types_mod_ident::sp_runtime::DispatchError;
|
||||
|
||||
// Make the runtime event type easily accessible, and impl RootEvent to help decode into it.
|
||||
pub type Event = #runtime_path::RuntimeEvent;
|
||||
|
||||
impl #crate_path::events::RootEvent for Event {
|
||||
fn root_event(pallet_bytes: &[u8], pallet_name: &str, pallet_ty: u32, metadata: &#crate_path::Metadata) -> Result<Self, #crate_path::Error> {
|
||||
use #crate_path::metadata::DecodeWithMetadata;
|
||||
#( #root_event_if_arms )*
|
||||
Err(#crate_path::ext::scale_decode::Error::custom(format!("Pallet name '{}' not found in root Event enum", pallet_name)).into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constants() -> ConstantsApi {
|
||||
ConstantsApi
|
||||
}
|
||||
@@ -512,14 +542,17 @@ impl RuntimeGenerator {
|
||||
}
|
||||
|
||||
/// check whether the Client you are using is aligned with the statically generated codegen.
|
||||
pub fn validate_codegen<T: ::subxt::Config, C: ::subxt::client::OfflineClientT<T>>(client: &C) -> Result<(), ::subxt::error::MetadataError> {
|
||||
pub fn validate_codegen<T: #crate_path::Config, C: #crate_path::client::OfflineClientT<T>>(client: &C) -> Result<(), #crate_path::error::MetadataError> {
|
||||
let runtime_metadata_hash = client.metadata().metadata_hash(&PALLETS);
|
||||
if runtime_metadata_hash != [ #(#metadata_hash,)* ] {
|
||||
Err(::subxt::error::MetadataError::IncompatibleMetadata)
|
||||
Err(#crate_path::error::MetadataError::IncompatibleMetadata)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#( #modules )*
|
||||
#types_mod
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+28
-59
@@ -12,7 +12,6 @@ use frame_metadata::{
|
||||
StorageEntryMetadata,
|
||||
StorageEntryModifier,
|
||||
StorageEntryType,
|
||||
StorageHasher,
|
||||
};
|
||||
use heck::ToSnakeCase as _;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
@@ -84,31 +83,12 @@ fn generate_storage_entry_fns(
|
||||
crate_path: &CratePath,
|
||||
should_gen_docs: bool,
|
||||
) -> Result<TokenStream2, CodegenError> {
|
||||
let (fields, key_impl) = match storage_entry.ty {
|
||||
let (fields, key_impl) = match &storage_entry.ty {
|
||||
StorageEntryType::Plain(_) => (vec![], quote!(vec![])),
|
||||
StorageEntryType::Map {
|
||||
ref key,
|
||||
ref hashers,
|
||||
..
|
||||
} => {
|
||||
StorageEntryType::Map { key, .. } => {
|
||||
let key_ty = type_gen.resolve_type(key.id());
|
||||
let hashers = hashers
|
||||
.iter()
|
||||
.map(|hasher| {
|
||||
let hasher = match hasher {
|
||||
StorageHasher::Blake2_128 => "Blake2_128",
|
||||
StorageHasher::Blake2_256 => "Blake2_256",
|
||||
StorageHasher::Blake2_128Concat => "Blake2_128Concat",
|
||||
StorageHasher::Twox128 => "Twox128",
|
||||
StorageHasher::Twox256 => "Twox256",
|
||||
StorageHasher::Twox64Concat => "Twox64Concat",
|
||||
StorageHasher::Identity => "Identity",
|
||||
};
|
||||
let hasher = format_ident!("{}", hasher);
|
||||
quote!( #crate_path::storage::address::StorageHasher::#hasher )
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
match key_ty.type_def() {
|
||||
// An N-map; return each of the keys separately.
|
||||
TypeDef::Tuple(tuple) => {
|
||||
let fields = tuple
|
||||
.fields()
|
||||
@@ -121,46 +101,23 @@ fn generate_storage_entry_fns(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let key_impl = if hashers.len() == fields.len() {
|
||||
// If the number of hashers matches the number of fields, we're dealing with
|
||||
// something shaped like a StorageNMap, and each field should be hashed separately
|
||||
// according to the corresponding hasher.
|
||||
let keys = hashers
|
||||
.into_iter()
|
||||
.zip(&fields)
|
||||
.map(|(hasher, (field_name, _))| {
|
||||
quote!( #crate_path::storage::address::StorageMapKey::new(#field_name.borrow(), #hasher) )
|
||||
});
|
||||
quote! {
|
||||
vec![ #( #keys ),* ]
|
||||
}
|
||||
} else if hashers.len() == 1 {
|
||||
// If there is one hasher, then however many fields we have, we want to hash a
|
||||
// tuple of them using the one hasher we're told about. This corresponds to a
|
||||
// StorageMap.
|
||||
let hasher = hashers.get(0).expect("checked for 1 hasher");
|
||||
let items =
|
||||
fields.iter().map(|(field_name, _)| quote!( #field_name ));
|
||||
quote! {
|
||||
vec![ #crate_path::storage::address::StorageMapKey::new(&(#( #items.borrow() ),*), #hasher) ]
|
||||
}
|
||||
} else {
|
||||
return Err(CodegenError::MismatchHashers(
|
||||
hashers.len(),
|
||||
fields.len(),
|
||||
))
|
||||
let keys = fields
|
||||
.iter()
|
||||
.map(|(field_name, _)| {
|
||||
quote!( #crate_path::storage::address::StaticStorageMapKey::new(#field_name.borrow()) )
|
||||
});
|
||||
let key_impl = quote! {
|
||||
vec![ #( #keys ),* ]
|
||||
};
|
||||
|
||||
(fields, key_impl)
|
||||
}
|
||||
// A map with a single key; return the single key.
|
||||
_ => {
|
||||
let ty_path = type_gen.resolve_type_path(key.id());
|
||||
let fields = vec![(format_ident!("_0"), ty_path)];
|
||||
let Some(hasher) = hashers.get(0) else {
|
||||
return Err(CodegenError::MissingHasher)
|
||||
};
|
||||
let key_impl = quote! {
|
||||
vec![ #crate_path::storage::address::StorageMapKey::new(_0.borrow(), #hasher) ]
|
||||
vec![ #crate_path::storage::address::StaticStorageMapKey::new(_0.borrow()) ]
|
||||
};
|
||||
(fields, key_impl)
|
||||
}
|
||||
@@ -233,8 +190,14 @@ fn generate_storage_entry_fns(
|
||||
#docs
|
||||
pub fn #fn_name_root(
|
||||
&self,
|
||||
) -> #crate_path::storage::address::StaticStorageAddress::<#crate_path::metadata::DecodeStaticType<#storage_entry_value_ty>, (), #is_defaultable_type, #is_iterable_type> {
|
||||
#crate_path::storage::address::StaticStorageAddress::new(
|
||||
) -> #crate_path::storage::address::Address::<
|
||||
#crate_path::storage::address::StaticStorageMapKey,
|
||||
#storage_entry_value_ty,
|
||||
(),
|
||||
#is_defaultable_type,
|
||||
#is_iterable_type
|
||||
> {
|
||||
#crate_path::storage::address::Address::new_static(
|
||||
#pallet_name,
|
||||
#storage_name,
|
||||
Vec::new(),
|
||||
@@ -252,8 +215,14 @@ fn generate_storage_entry_fns(
|
||||
pub fn #fn_name(
|
||||
&self,
|
||||
#( #key_args, )*
|
||||
) -> #crate_path::storage::address::StaticStorageAddress::<#crate_path::metadata::DecodeStaticType<#storage_entry_value_ty>, #crate_path::storage::address::Yes, #is_defaultable_type, #is_iterable_type> {
|
||||
#crate_path::storage::address::StaticStorageAddress::new(
|
||||
) -> #crate_path::storage::address::Address::<
|
||||
#crate_path::storage::address::StaticStorageMapKey,
|
||||
#storage_entry_value_ty,
|
||||
#crate_path::storage::address::Yes,
|
||||
#is_defaultable_type,
|
||||
#is_iterable_type
|
||||
> {
|
||||
#crate_path::storage::address::Address::new_static(
|
||||
#pallet_name,
|
||||
#storage_name,
|
||||
#key_impl,
|
||||
|
||||
Reference in New Issue
Block a user