mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-29 02:07:56 +00:00
v0.50.0: Integrate frame-decode, redo storage APIs and break up Error. (#2100)
* WIP integrating new frame-decode and working out new storage APIS * WIP: first pass adding new storage things to subxt-core * Second pass over Address type and start impl in Subxt * WIP new storage APIs * WIP New storage APIs roughly completed, lots of errors still * Remove PlainorMap enum; plain and map values now use same struct to simplify usage * Begin 'fixing' errors * WIP splitting errors and tidying payload/address traits * Get subxt-core compiling * Small fixes in subxt-core and remove metadata mod * subxt-core: cargo check --all-targets passes * Fix test * WIP starting to update subxt from subxt-core changes * WIP splitting up subxt errors into smaller variants * WIP errors: add DispatchError errors * Port new Storage APIs to subxt-core * cargo check -p subxt passes * Quick-fix errors in subxt-cli (explore subcommand) * fmt * Finish fixing codegen up and start fixing examples * get Subxt examples compiling and bytes_at for constants * Add some arcs to limit lifetimes in subxt/subxt-core storage APIs * A little Arcing to allow more method chaining in Storage APIs, aligning with Subxt * Update codegen test * cargo check --all-targets passing * cargo check --features 'unstable-light-client' passing * clippy * Remove unused dep in subxt * use published frame-decode * fix wasm-example * Add new tx extension to fix daily tests * Remove unused subxt_core::dynamic::DecodedValue type * Update book to match changes * Update docs to fix more broken bits * Add missing docs * fmt * allow larger result errs for now * Add missing alloc imports in subxt-core * Fix doc tests and fix bug getting constant info * Fix V14 -> Metadata transform for storage & constants * Fix parachain example * Fix FFI example * BlockLength decodes t ostruct, not u128 * use fetch/iter shorthands rather than entry in most storage tests * Fix some integration tests * Fix Runtime codegen tests * Expose the dynamic custom_value selecter and use in a UI test * Update codegen metadata * Tidy CLI storage query and support (str,str) as a storage address * Add (str,str) as valid constant address too * Show string tuple in constants example * Via the magic of traits, avoid needing any clones of queries/addresses and accept references to them * clippy
This commit is contained in:
+97
-230
@@ -2,14 +2,11 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use heck::{ToSnakeCase as _, ToUpperCamelCase};
|
||||
use proc_macro2::{Ident, TokenStream as TokenStream2, TokenStream};
|
||||
use heck::ToSnakeCase as _;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use scale_info::TypeDef;
|
||||
use scale_typegen::TypeGenerator;
|
||||
use subxt_metadata::{
|
||||
PalletMetadata, StorageEntryMetadata, StorageEntryModifier, StorageEntryType, StorageHasher,
|
||||
};
|
||||
use subxt_metadata::{PalletMetadata, StorageEntryMetadata};
|
||||
|
||||
use super::CodegenError;
|
||||
|
||||
@@ -29,33 +26,34 @@ pub fn generate_storage(
|
||||
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_fns, alias_modules): (Vec<TokenStream2>, Vec<TokenStream2>) = storage
|
||||
let storage_entries = storage
|
||||
.entries()
|
||||
.iter()
|
||||
.map(|entry| generate_storage_entry_fns(type_gen, pallet, entry, crate_path))
|
||||
.collect::<Result<Vec<_>, CodegenError>>()?
|
||||
.into_iter()
|
||||
.unzip();
|
||||
.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 mod types {
|
||||
use super::#types_mod_ident;
|
||||
|
||||
#( #alias_modules )*
|
||||
}
|
||||
|
||||
pub struct StorageApi;
|
||||
|
||||
impl StorageApi {
|
||||
#( #storage_fns )*
|
||||
#( #storage_entry_methods )*
|
||||
}
|
||||
|
||||
#( #storage_entry_types )*
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -67,239 +65,108 @@ fn generate_storage_entry_fns(
|
||||
storage_entry: &StorageEntryMetadata,
|
||||
crate_path: &syn::Path,
|
||||
) -> Result<(TokenStream2, TokenStream2), CodegenError> {
|
||||
let snake_case_name = storage_entry.name().to_snake_case();
|
||||
let storage_entry_ty = storage_entry.entry_type().value_ty();
|
||||
let storage_entry_value_ty = type_gen
|
||||
.resolve_type_path(storage_entry_ty)
|
||||
.expect("storage type is in metadata; qed")
|
||||
.to_token_stream(type_gen.settings());
|
||||
|
||||
let alias_name = format_ident!("{}", storage_entry.name().to_upper_camel_case());
|
||||
let alias_module_name = format_ident!("{snake_case_name}");
|
||||
let alias_storage_path = quote!( types::#alias_module_name::#alias_name );
|
||||
|
||||
struct MapEntryKey {
|
||||
arg_name: Ident,
|
||||
alias_type_def: TokenStream,
|
||||
alias_type_path: TokenStream,
|
||||
hasher: StorageHasher,
|
||||
}
|
||||
|
||||
let map_entry_key = |idx, id, hasher| -> MapEntryKey {
|
||||
let arg_name: Ident = format_ident!("_{}", idx);
|
||||
let ty_path = type_gen
|
||||
.resolve_type_path(id)
|
||||
.expect("type is in metadata; qed");
|
||||
|
||||
let alias_name = format_ident!("Param{}", idx);
|
||||
let alias_type = ty_path.to_token_stream(type_gen.settings());
|
||||
|
||||
let alias_type_def = quote!( pub type #alias_name = #alias_type; );
|
||||
let alias_type_path = quote!( types::#alias_module_name::#alias_name );
|
||||
|
||||
MapEntryKey {
|
||||
arg_name,
|
||||
alias_type_def,
|
||||
alias_type_path,
|
||||
hasher,
|
||||
}
|
||||
};
|
||||
|
||||
let keys: Vec<MapEntryKey> = match storage_entry.entry_type() {
|
||||
StorageEntryType::Plain(_) => vec![],
|
||||
StorageEntryType::Map {
|
||||
key_ty, hashers, ..
|
||||
} => {
|
||||
if hashers.len() == 1 {
|
||||
// If there's exactly 1 hasher, then we have a plain StorageMap. We can't
|
||||
// break the key down (even if it's a tuple) because the hasher applies to
|
||||
// the whole key.
|
||||
vec![map_entry_key(0, *key_ty, hashers[0])]
|
||||
} else {
|
||||
// If there are multiple hashers, then we have a StorageDoubleMap or StorageNMap.
|
||||
// We expect the key type to be tuple, and we will return a MapEntryKey for each
|
||||
// key in the tuple.
|
||||
let hasher_count = hashers.len();
|
||||
let tuple = match &type_gen
|
||||
.resolve_type(*key_ty)
|
||||
.expect("key type should be present")
|
||||
.type_def
|
||||
{
|
||||
TypeDef::Tuple(tuple) => tuple,
|
||||
_ => {
|
||||
return Err(CodegenError::InvalidStorageHasherCount {
|
||||
storage_entry_name: storage_entry.name().to_owned(),
|
||||
key_count: 1,
|
||||
hasher_count,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// We should have the same number of hashers and keys.
|
||||
let key_count = tuple.fields.len();
|
||||
if hasher_count != key_count {
|
||||
return Err(CodegenError::InvalidStorageHasherCount {
|
||||
storage_entry_name: storage_entry.name().to_owned(),
|
||||
key_count,
|
||||
hasher_count,
|
||||
});
|
||||
}
|
||||
|
||||
// Collect them together.
|
||||
tuple
|
||||
.fields
|
||||
.iter()
|
||||
.zip(hashers)
|
||||
.enumerate()
|
||||
.map(|(idx, (field, hasher))| map_entry_key(idx, field.id, *hasher))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
};
|
||||
let types_mod_ident = type_gen.types_mod_ident();
|
||||
|
||||
let pallet_name = pallet.name();
|
||||
let storage_name = storage_entry.name();
|
||||
let Some(storage_hash) = pallet.storage_hash(storage_name) else {
|
||||
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_name.into(),
|
||||
storage_entry_name_str.into(),
|
||||
));
|
||||
};
|
||||
|
||||
let docs = storage_entry.docs();
|
||||
let docs = type_gen
|
||||
let docs: TokenStream2 = type_gen
|
||||
.settings()
|
||||
.should_gen_docs
|
||||
.then_some(quote! { #( #[doc = #docs ] )* })
|
||||
.unwrap_or_default();
|
||||
|
||||
let is_defaultable_type = match storage_entry.modifier() {
|
||||
StorageEntryModifier::Default => quote!(#crate_path::utils::Yes),
|
||||
StorageEntryModifier::Optional => quote!(()),
|
||||
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)
|
||||
};
|
||||
|
||||
// Note: putting `#crate_path::storage::address::StaticStorageKey` into this variable is necessary
|
||||
// to get the line width below a certain limit. If not done, rustfmt will refuse to format the following big expression.
|
||||
// for more information see [this post](https://users.rust-lang.org/t/rustfmt-silently-fails-to-work/75485/4).
|
||||
let static_storage_key: TokenStream = quote!(#crate_path::storage::address::StaticStorageKey);
|
||||
let all_fns = (0..=keys.len()).map(|n_keys| {
|
||||
let keys_slice = &keys[..n_keys];
|
||||
let (fn_name, is_fetchable, is_iterable) = if n_keys == keys.len() {
|
||||
let fn_name = format_ident!("{snake_case_name}");
|
||||
(fn_name, true, false)
|
||||
} else {
|
||||
let fn_name = if n_keys == 0 {
|
||||
format_ident!("{snake_case_name}_iter")
|
||||
} else {
|
||||
format_ident!("{snake_case_name}_iter{}", n_keys)
|
||||
};
|
||||
(fn_name, false, true)
|
||||
};
|
||||
let is_fetchable_type = is_fetchable
|
||||
.then_some(quote!(#crate_path::utils::Yes))
|
||||
.unwrap_or(quote!(()));
|
||||
let is_iterable_type = is_iterable
|
||||
.then_some(quote!(#crate_path::utils::Yes))
|
||||
.unwrap_or(quote!(()));
|
||||
|
||||
let (keys, keys_type) = match keys_slice.len() {
|
||||
0 => (quote!(()), quote!(())),
|
||||
1 => {
|
||||
let key = &keys_slice[0];
|
||||
if key.hasher.ends_with_key() {
|
||||
let arg = &key.arg_name;
|
||||
let keys = quote!(#static_storage_key::new(#arg));
|
||||
let path = &key.alias_type_path;
|
||||
let path = quote!(#static_storage_key<#path>);
|
||||
(keys, path)
|
||||
} else {
|
||||
(quote!(()), quote!(()))
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let keys_iter = keys_slice.iter().map(
|
||||
|MapEntryKey {
|
||||
arg_name, hasher, ..
|
||||
}| {
|
||||
if hasher.ends_with_key() {
|
||||
quote!( #static_storage_key::new(#arg_name) )
|
||||
} else {
|
||||
quote!(())
|
||||
}
|
||||
},
|
||||
);
|
||||
let keys = quote!( (#(#keys_iter,)*) );
|
||||
let paths_iter = keys_slice.iter().map(
|
||||
|MapEntryKey {
|
||||
alias_type_path,
|
||||
hasher,
|
||||
..
|
||||
}| {
|
||||
if hasher.ends_with_key() {
|
||||
quote!( #static_storage_key<#alias_type_path> )
|
||||
} else {
|
||||
quote!(())
|
||||
}
|
||||
},
|
||||
);
|
||||
let paths = quote!( (#(#paths_iter,)*) );
|
||||
(keys, paths)
|
||||
}
|
||||
};
|
||||
|
||||
let key_args = keys_slice.iter().map(
|
||||
|MapEntryKey {
|
||||
arg_name,
|
||||
alias_type_path,
|
||||
..
|
||||
}| quote!( #arg_name: #alias_type_path ),
|
||||
);
|
||||
|
||||
quote!(
|
||||
#docs
|
||||
pub fn #fn_name(
|
||||
&self,
|
||||
#(#key_args,)*
|
||||
) -> #crate_path::storage::address::StaticAddress::<
|
||||
#keys_type,
|
||||
#alias_storage_path,
|
||||
#is_fetchable_type,
|
||||
#is_defaultable_type,
|
||||
#is_iterable_type
|
||||
> {
|
||||
#crate_path::storage::address::StaticAddress::new_static(
|
||||
#pallet_name,
|
||||
#storage_name,
|
||||
#keys,
|
||||
[#(#storage_hash,)*]
|
||||
)
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
let alias_types = keys
|
||||
.iter()
|
||||
.map(|MapEntryKey { alias_type_def, .. }| alias_type_def);
|
||||
|
||||
let types_mod_ident = type_gen.types_mod_ident();
|
||||
// Generate type alias for the return type only, since
|
||||
// the keys of the storage entry are not explicitly named.
|
||||
let alias_module = quote! {
|
||||
pub mod #alias_module_name {
|
||||
let storage_entry_types = quote!(
|
||||
pub mod #storage_entry_snake_case_ident {
|
||||
use super::root_mod;
|
||||
use super::#types_mod_ident;
|
||||
|
||||
pub type #alias_name = #storage_entry_value_ty;
|
||||
#(#storage_key_type_aliases)*
|
||||
|
||||
#( #alias_types )*
|
||||
pub mod output {
|
||||
use super::#types_mod_ident;
|
||||
pub type Output = #storage_value_type_path;
|
||||
}
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
Ok((
|
||||
quote! {
|
||||
#( #all_fns )*
|
||||
},
|
||||
alias_module,
|
||||
))
|
||||
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)]
|
||||
|
||||
Reference in New Issue
Block a user