mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-23 02:37:59 +00:00
Rework Subxt API to support offline and dynamic transactions (#593)
* WIP API changes * debug impls * Get main crate compiling with first round of changes * Some tidy up * Add WithExtrinsicParams, and have SubstrateConfig + PolkadotConfig, not DefaultConfig * move transaction into extrinsic folder * Add runtime updates back to OnlineClient * rework to be 'client first' to fit better with storage + events * add support for events to Client * tidy dupe trait bound * Wire storage into client, but need to remove static reliance * various tidy up and start stripping codegen to remove bits we dont need now * First pass updating calls and constants codegen * WIP storage client updates * First pass migrated runtime storage over to new format * pass over codegen to generate StorageAddresses and throw other stuff out * don't need a Call trait any more * shuffle things around a bit * Various proc_macro fixes to get 'cargo check' working * organise what's exposed from subxt * Get first example working; balance_transfer_with_params * get balance_transfer example compiling * get concurrent_storage_requests.rs example compiling * get fetch_all_accounts example compiling * get a bunch more of the examples compiling * almost get final example working; type mismatch to look into * wee tweaks * move StorageAddress to separate file * pass Defaultable/Iterable info to StorageAddress in codegen * fix storage validation ne, and partial run through example code * Remove static iteration and strip a generic param from everything * fix doc tests in subxt crate * update test utils and start fixing frame tests * fix frame staking tests * fix the rest of the test compile issues, Borrow on storage values * cargo fmt * remove extra logging during tests * Appease clippy and no more need for into_iter on events * cargo fmt * fix dryRun tests by waiting for blocks * wait for blocks instead of sleeping or other test hacks * cargo fmt * Fix doc links * Traitify StorageAddress * remove out-of-date doc comments * optimise decoding storage a little * cleanup tx stuff, trait for TxPayload, remove Err type param and decode at runtime * clippy fixes * fix doc links * fix doc example * constant address trait for consistency * fix a typo and remove EncodeWithMetadata stuff * Put EventDetails behind a proper interface and allow decoding into top level event, too * fix docs * tweak StorageAddress docs * re-export StorageAddress at root for consistency * fix clippy things * Add support for dynamic values * fix double encoding of storage map key after refactor * clippy fix * Fixes and add a dynamic usage example (needs new scale_value release) * bump scale_value version * cargo fmt * Tweak event bits * cargo fmt * Add a test and bump scale-value to 0.4.0 to support this * remove unnecessary vec from dynamic example * Various typo/grammar fixes Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> * Address PR nits * Undo accidental rename in changelog * Small PR nits/tidyups * fix tests; codegen change against latest substrate * tweak storage address util names * move error decoding to DecodeError and expose * impl some basic traits on the extrinsic param builder Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
This commit is contained in:
+43
-80
@@ -22,31 +22,8 @@ use quote::{
|
||||
};
|
||||
use scale_info::form::PortableForm;
|
||||
|
||||
/// Generate calls from the provided pallet's metadata.
|
||||
///
|
||||
/// The function creates a new module named `calls` under the pallet's module.
|
||||
/// ```ignore
|
||||
/// pub mod PalletName {
|
||||
/// pub mod calls {
|
||||
/// ...
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The function generates the calls as rust structs that implement the `subxt::Call` trait
|
||||
/// to uniquely identify the call's identity when creating the extrinsic.
|
||||
///
|
||||
/// ```ignore
|
||||
/// pub struct CallName {
|
||||
/// pub call_param: type,
|
||||
/// }
|
||||
/// impl ::subxt::Call for CallName {
|
||||
/// ...
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Calls are extracted from the API and wrapped into the generated `TransactionApi` of
|
||||
/// each module.
|
||||
/// Generate calls from the provided pallet's metadata. Each call returns a `StaticTxPayload`
|
||||
/// that can be passed to the subxt client to submit/sign/encode.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
@@ -76,35 +53,42 @@ pub fn generate_calls(
|
||||
let (call_structs, call_fns): (Vec<_>, Vec<_>) = struct_defs
|
||||
.iter_mut()
|
||||
.map(|(variant_name, struct_def)| {
|
||||
let (call_fn_args, call_args): (Vec<_>, Vec<_>) =
|
||||
match struct_def.fields {
|
||||
CompositeDefFields::Named(ref named_fields) => {
|
||||
named_fields
|
||||
.iter()
|
||||
.map(|(name, field)| {
|
||||
let fn_arg_type = &field.type_path;
|
||||
let call_arg = if field.is_boxed() {
|
||||
quote! { #name: ::std::boxed::Box::new(#name) }
|
||||
} else {
|
||||
quote! { #name }
|
||||
};
|
||||
(quote!( #name: #fn_arg_type ), call_arg)
|
||||
})
|
||||
.unzip()
|
||||
}
|
||||
CompositeDefFields::NoFields => Default::default(),
|
||||
CompositeDefFields::Unnamed(_) =>
|
||||
abort_call_site!(
|
||||
"Call variant for type {} must have all named fields",
|
||||
call.ty.id()
|
||||
)
|
||||
};
|
||||
let (call_fn_args, call_args): (Vec<_>, Vec<_>) = match struct_def.fields {
|
||||
CompositeDefFields::Named(ref named_fields) => {
|
||||
named_fields
|
||||
.iter()
|
||||
.map(|(name, field)| {
|
||||
let fn_arg_type = &field.type_path;
|
||||
let call_arg = if field.is_boxed() {
|
||||
quote! { #name: ::std::boxed::Box::new(#name) }
|
||||
} else {
|
||||
quote! { #name }
|
||||
};
|
||||
(quote!( #name: #fn_arg_type ), call_arg)
|
||||
})
|
||||
.unzip()
|
||||
}
|
||||
CompositeDefFields::NoFields => Default::default(),
|
||||
CompositeDefFields::Unnamed(_) => {
|
||||
abort_call_site!(
|
||||
"Call variant for type {} must have all named fields",
|
||||
call.ty.id()
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let pallet_name = &pallet.name;
|
||||
let call_name = &variant_name;
|
||||
let struct_name = &struct_def.name;
|
||||
let call_hash = subxt_metadata::get_call_hash(metadata, pallet_name, call_name)
|
||||
.unwrap_or_else(|_| abort_call_site!("Metadata information for the call {}_{} could not be found", pallet_name, call_name));
|
||||
let call_hash =
|
||||
subxt_metadata::get_call_hash(metadata, pallet_name, call_name)
|
||||
.unwrap_or_else(|_| {
|
||||
abort_call_site!(
|
||||
"Metadata information for the call {}_{} could not be found",
|
||||
pallet_name,
|
||||
call_name
|
||||
)
|
||||
});
|
||||
|
||||
let fn_name = format_ident!("{}", variant_name.to_snake_case());
|
||||
// Propagate the documentation just to `TransactionApi` methods, while
|
||||
@@ -113,29 +97,19 @@ pub fn generate_calls(
|
||||
// The call structure's documentation was stripped above.
|
||||
let call_struct = quote! {
|
||||
#struct_def
|
||||
|
||||
impl ::subxt::Call for #struct_name {
|
||||
const PALLET: &'static str = #pallet_name;
|
||||
const FUNCTION: &'static str = #call_name;
|
||||
}
|
||||
};
|
||||
let client_fn = quote! {
|
||||
#docs
|
||||
pub fn #fn_name(
|
||||
&self,
|
||||
#( #call_fn_args, )*
|
||||
) -> Result<::subxt::SubmittableExtrinsic<'a, T, X, #struct_name, DispatchError, root_mod::Event>, ::subxt::BasicError> {
|
||||
let runtime_call_hash = {
|
||||
let locked_metadata = self.client.metadata();
|
||||
let metadata = locked_metadata.read();
|
||||
metadata.call_hash::<#struct_name>()?
|
||||
};
|
||||
if runtime_call_hash == [#(#call_hash,)*] {
|
||||
let call = #struct_name { #( #call_args, )* };
|
||||
Ok(::subxt::SubmittableExtrinsic::new(self.client, call))
|
||||
} else {
|
||||
Err(::subxt::MetadataError::IncompatibleMetadata.into())
|
||||
}
|
||||
) -> ::subxt::tx::StaticTxPayload<#struct_name> {
|
||||
::subxt::tx::StaticTxPayload::new(
|
||||
#pallet_name,
|
||||
#call_name,
|
||||
#struct_name { #( #call_args, )* },
|
||||
[#(#call_hash,)*]
|
||||
)
|
||||
}
|
||||
};
|
||||
(call_struct, client_fn)
|
||||
@@ -155,20 +129,9 @@ pub fn generate_calls(
|
||||
|
||||
#( #call_structs )*
|
||||
|
||||
pub struct TransactionApi<'a, T: ::subxt::Config, X> {
|
||||
client: &'a ::subxt::Client<T>,
|
||||
marker: ::core::marker::PhantomData<X>,
|
||||
}
|
||||
|
||||
impl<'a, T, X> TransactionApi<'a, T, X>
|
||||
where
|
||||
T: ::subxt::Config,
|
||||
X: ::subxt::extrinsic::ExtrinsicParams<T>,
|
||||
{
|
||||
pub fn new(client: &'a ::subxt::Client<T>) -> Self {
|
||||
Self { client, marker: ::core::marker::PhantomData }
|
||||
}
|
||||
pub struct TransactionApi;
|
||||
|
||||
impl TransactionApi {
|
||||
#( #call_fns )*
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,17 +63,12 @@ pub fn generate_constants(
|
||||
|
||||
quote! {
|
||||
#( #[doc = #docs ] )*
|
||||
pub fn #fn_name(&self) -> ::core::result::Result<#return_ty, ::subxt::BasicError> {
|
||||
let locked_metadata = self.client.metadata();
|
||||
let metadata = locked_metadata.read();
|
||||
if metadata.constant_hash(#pallet_name, #constant_name)? == [#(#constant_hash,)*] {
|
||||
let pallet = metadata.pallet(#pallet_name)?;
|
||||
let constant = pallet.constant(#constant_name)?;
|
||||
let value = ::subxt::codec::Decode::decode(&mut &constant.value[..])?;
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(::subxt::MetadataError::IncompatibleMetadata.into())
|
||||
}
|
||||
pub fn #fn_name(&self) -> ::subxt::constants::StaticConstantAddress<::subxt::metadata::DecodeStaticType<#return_ty>> {
|
||||
::subxt::constants::StaticConstantAddress::new(
|
||||
#pallet_name,
|
||||
#constant_name,
|
||||
[#(#constant_hash,)*]
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -82,15 +77,9 @@ pub fn generate_constants(
|
||||
pub mod constants {
|
||||
use super::#types_mod_ident;
|
||||
|
||||
pub struct ConstantsApi<'a, T: ::subxt::Config> {
|
||||
client: &'a ::subxt::Client<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: ::subxt::Config> ConstantsApi<'a, T> {
|
||||
pub fn new(client: &'a ::subxt::Client<T>) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
pub struct ConstantsApi;
|
||||
|
||||
impl ConstantsApi {
|
||||
#(#constant_fns)*
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use frame_metadata::v14::RuntimeMetadataV14;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use proc_macro_error::abort_call_site;
|
||||
use quote::quote;
|
||||
use scale_info::{
|
||||
form::PortableForm,
|
||||
Field,
|
||||
TypeDef,
|
||||
TypeDefPrimitive,
|
||||
};
|
||||
|
||||
/// Different substrate versions will have a different `DispatchError::Module`.
|
||||
/// The following cases are ordered by versions.
|
||||
enum ModuleErrorType {
|
||||
/// Case 1: `DispatchError::Module { index: u8, error: u8 }`
|
||||
///
|
||||
/// This is the first supported `DispatchError::Module` format.
|
||||
NamedField,
|
||||
/// Case 2: `DispatchError::Module ( sp_runtime::ModuleError { index: u8, error: u8 } )`
|
||||
///
|
||||
/// Substrate introduced `sp_runtime::ModuleError`, while keeping the error `u8`.
|
||||
LegacyError,
|
||||
/// Case 3: `DispatchError::Module ( sp_runtime::ModuleError { index: u8, error: [u8; 4] } )`
|
||||
///
|
||||
/// The substrate error evolved into `[u8; 4]`.
|
||||
ArrayError,
|
||||
}
|
||||
|
||||
impl quote::ToTokens for ModuleErrorType {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let trait_fn_body = match self {
|
||||
ModuleErrorType::NamedField => {
|
||||
quote! {
|
||||
if let &Self::Module { index, error } = self {
|
||||
Some(::subxt::ModuleErrorData { pallet_index: index, error: [error, 0, 0, 0] })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
ModuleErrorType::LegacyError => {
|
||||
quote! {
|
||||
if let Self::Module (module_error) = self {
|
||||
Some(::subxt::ModuleErrorData { pallet_index: module_error.index, error: [module_error.error, 0, 0, 0] })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
ModuleErrorType::ArrayError => {
|
||||
quote! {
|
||||
if let Self::Module (module_error) = self {
|
||||
Some(::subxt::ModuleErrorData { pallet_index: module_error.index, error: module_error.error })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tokens.extend(trait_fn_body);
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the `ModuleError` type for the `ModuleErrorType::LegacyError` and
|
||||
/// `ModuleErrorType::ErrorArray` cases.
|
||||
fn module_error_type(
|
||||
module_field: &Field<PortableForm>,
|
||||
metadata: &RuntimeMetadataV14,
|
||||
) -> ModuleErrorType {
|
||||
// Fields are named.
|
||||
if module_field.name().is_some() {
|
||||
return ModuleErrorType::NamedField
|
||||
}
|
||||
|
||||
// Get the `sp_runtime::ModuleError` structure.
|
||||
let module_err = metadata
|
||||
.types
|
||||
.resolve(module_field.ty().id())
|
||||
.unwrap_or_else(|| {
|
||||
abort_call_site!("sp_runtime::ModuleError type expected in metadata")
|
||||
});
|
||||
|
||||
let error_type_def = match module_err.type_def() {
|
||||
TypeDef::Composite(composite) => composite,
|
||||
_ => abort_call_site!("sp_runtime::ModuleError type should be a composite type"),
|
||||
};
|
||||
|
||||
// Get the error field from the `sp_runtime::ModuleError` structure.
|
||||
let error_field = error_type_def
|
||||
.fields()
|
||||
.iter()
|
||||
.find(|field| field.name() == Some(&"error".to_string()))
|
||||
.unwrap_or_else(|| {
|
||||
abort_call_site!("sp_runtime::ModuleError expected to contain error field")
|
||||
});
|
||||
|
||||
// Resolve the error type from the metadata.
|
||||
let error_field_ty = metadata
|
||||
.types
|
||||
.resolve(error_field.ty().id())
|
||||
.unwrap_or_else(|| {
|
||||
abort_call_site!("sp_runtime::ModuleError::error type expected in metadata")
|
||||
});
|
||||
|
||||
match error_field_ty.type_def() {
|
||||
// Check for legacy error type.
|
||||
TypeDef::Primitive(TypeDefPrimitive::U8) => ModuleErrorType::LegacyError,
|
||||
TypeDef::Array(array) => {
|
||||
// Check new error type of len 4 and type u8.
|
||||
if array.len() != 4 {
|
||||
abort_call_site!("sp_runtime::ModuleError::error array length is not 4");
|
||||
}
|
||||
|
||||
let array_ty = metadata
|
||||
.types
|
||||
.resolve(array.type_param().id())
|
||||
.unwrap_or_else(|| {
|
||||
abort_call_site!(
|
||||
"sp_runtime::ModuleError::error array type expected in metadata"
|
||||
)
|
||||
});
|
||||
|
||||
if let TypeDef::Primitive(TypeDefPrimitive::U8) = array_ty.type_def() {
|
||||
ModuleErrorType::ArrayError
|
||||
} else {
|
||||
abort_call_site!(
|
||||
"sp_runtime::ModuleError::error array type expected to be u8"
|
||||
)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
abort_call_site!(
|
||||
"sp_runtime::ModuleError::error array type or primitive expected"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The aim of this is to implement the `::subxt::HasModuleError` trait for
|
||||
/// the generated `DispatchError`, so that we can obtain the module error details,
|
||||
/// if applicable, from it.
|
||||
pub fn generate_has_module_error_impl(
|
||||
metadata: &RuntimeMetadataV14,
|
||||
types_mod_ident: &syn::Ident,
|
||||
) -> TokenStream2 {
|
||||
let dispatch_error = metadata
|
||||
.types
|
||||
.types()
|
||||
.iter()
|
||||
.find(|&ty| ty.ty().path().segments() == ["sp_runtime", "DispatchError"])
|
||||
.unwrap_or_else(|| {
|
||||
abort_call_site!("sp_runtime::DispatchError type expected in metadata")
|
||||
})
|
||||
.ty()
|
||||
.type_def();
|
||||
|
||||
// Get the `DispatchError::Module` variant (either struct or named fields).
|
||||
let module_variant = match dispatch_error {
|
||||
TypeDef::Variant(variant) => {
|
||||
variant
|
||||
.variants()
|
||||
.iter()
|
||||
.find(|variant| variant.name() == "Module")
|
||||
.unwrap_or_else(|| {
|
||||
abort_call_site!("DispatchError::Module variant expected in metadata")
|
||||
})
|
||||
}
|
||||
_ => abort_call_site!("DispatchError expected to contain variant in metadata"),
|
||||
};
|
||||
|
||||
let module_field = module_variant.fields().get(0).unwrap_or_else(|| {
|
||||
abort_call_site!("DispatchError::Module expected to contain 1 or more fields")
|
||||
});
|
||||
|
||||
let error_type = module_error_type(module_field, metadata);
|
||||
|
||||
quote! {
|
||||
impl ::subxt::HasModuleError for #types_mod_ident::sp_runtime::DispatchError {
|
||||
fn module_error_data(&self) -> Option<::subxt::ModuleErrorData> {
|
||||
#error_type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use scale_info::form::PortableForm;
|
||||
/// Generate events from the provided pallet metadata.
|
||||
///
|
||||
/// The function creates a new module named `events` under the pallet's module.
|
||||
///
|
||||
/// ```ignore
|
||||
/// pub mod PalletName {
|
||||
/// pub mod events {
|
||||
@@ -19,14 +20,14 @@ use scale_info::form::PortableForm;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The function generates the events as rust structs that implement the `subxt::Event` trait
|
||||
/// The function generates the events as rust structs that implement the `subxt::event::StaticEvent` trait
|
||||
/// to uniquely identify the event's identity when creating the extrinsic.
|
||||
///
|
||||
/// ```ignore
|
||||
/// pub struct EventName {
|
||||
/// pub event_param: type,
|
||||
/// }
|
||||
/// impl ::subxt::Event for EventName {
|
||||
/// impl ::subxt::events::StaticEvent for EventName {
|
||||
/// ...
|
||||
/// }
|
||||
/// ```
|
||||
@@ -62,7 +63,7 @@ pub fn generate_events(
|
||||
quote! {
|
||||
#struct_def
|
||||
|
||||
impl ::subxt::Event for #event_struct {
|
||||
impl ::subxt::events::StaticEvent for #event_struct {
|
||||
const PALLET: &'static str = #pallet_name;
|
||||
const EVENT: &'static str = #event_name;
|
||||
}
|
||||
|
||||
+37
-111
@@ -6,7 +6,6 @@
|
||||
|
||||
mod calls;
|
||||
mod constants;
|
||||
mod errors;
|
||||
mod events;
|
||||
mod storage;
|
||||
|
||||
@@ -110,33 +109,33 @@ impl RuntimeGenerator {
|
||||
let mut type_substitutes = [
|
||||
(
|
||||
"bitvec::order::Lsb0",
|
||||
parse_quote!(::subxt::bitvec::order::Lsb0),
|
||||
parse_quote!(::subxt::ext::bitvec::order::Lsb0),
|
||||
),
|
||||
(
|
||||
"bitvec::order::Msb0",
|
||||
parse_quote!(::subxt::bitvec::order::Msb0),
|
||||
parse_quote!(::subxt::ext::bitvec::order::Msb0),
|
||||
),
|
||||
(
|
||||
"sp_core::crypto::AccountId32",
|
||||
parse_quote!(::subxt::sp_core::crypto::AccountId32),
|
||||
parse_quote!(::subxt::ext::sp_core::crypto::AccountId32),
|
||||
),
|
||||
(
|
||||
"primitive_types::H256",
|
||||
parse_quote!(::subxt::sp_core::H256),
|
||||
parse_quote!(::subxt::ext::sp_core::H256),
|
||||
),
|
||||
(
|
||||
"sp_runtime::multiaddress::MultiAddress",
|
||||
parse_quote!(::subxt::sp_runtime::MultiAddress),
|
||||
parse_quote!(::subxt::ext::sp_runtime::MultiAddress),
|
||||
),
|
||||
(
|
||||
"frame_support::traits::misc::WrapperKeepOpaque",
|
||||
parse_quote!(::subxt::WrapperKeepOpaque),
|
||||
parse_quote!(::subxt::utils::WrapperKeepOpaque),
|
||||
),
|
||||
// BTreeMap and BTreeSet impose an `Ord` constraint on their key types. This
|
||||
// can cause an issue with generated code that doesn't impl `Ord` by default.
|
||||
// Decoding them to Vec by default (KeyedVec is just an alias for Vec with
|
||||
// suitable type params) avoids these issues.
|
||||
("BTreeMap", parse_quote!(::subxt::KeyedVec)),
|
||||
("BTreeMap", parse_quote!(::subxt::utils::KeyedVec)),
|
||||
("BTreeSet", parse_quote!(::std::vec::Vec)),
|
||||
]
|
||||
.iter()
|
||||
@@ -256,9 +255,6 @@ impl RuntimeGenerator {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let has_module_error_impl =
|
||||
errors::generate_has_module_error_impl(&self.metadata, types_mod_ident);
|
||||
|
||||
quote! {
|
||||
#[allow(dead_code, unused_imports, non_camel_case_types)]
|
||||
pub mod #mod_ident {
|
||||
@@ -271,128 +267,58 @@ impl RuntimeGenerator {
|
||||
#( #modules )*
|
||||
#types_mod
|
||||
|
||||
/// The default error type returned when there is a runtime issue.
|
||||
/// The default error type returned when there is a runtime issue,
|
||||
/// exposed here for ease of use.
|
||||
pub type DispatchError = #types_mod_ident::sp_runtime::DispatchError;
|
||||
// Impl HasModuleError on DispatchError so we can pluck out module error details.
|
||||
#has_module_error_impl
|
||||
|
||||
pub struct RuntimeApi<T: ::subxt::Config, X> {
|
||||
pub client: ::subxt::Client<T>,
|
||||
marker: ::core::marker::PhantomData<X>,
|
||||
pub fn constants() -> ConstantsApi {
|
||||
ConstantsApi
|
||||
}
|
||||
|
||||
impl<T: ::subxt::Config, X> Clone for RuntimeApi<T, X> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { client: self.client.clone(), marker: ::core::marker::PhantomData }
|
||||
}
|
||||
pub fn storage() -> StorageApi {
|
||||
StorageApi
|
||||
}
|
||||
|
||||
impl<T, X> ::core::convert::From<::subxt::Client<T>> for RuntimeApi<T, X>
|
||||
where
|
||||
T: ::subxt::Config,
|
||||
X: ::subxt::extrinsic::ExtrinsicParams<T>
|
||||
{
|
||||
fn from(client: ::subxt::Client<T>) -> Self {
|
||||
Self { client, marker: ::core::marker::PhantomData }
|
||||
}
|
||||
pub fn tx() -> TransactionApi {
|
||||
TransactionApi
|
||||
}
|
||||
|
||||
impl<'a, T, X> RuntimeApi<T, X>
|
||||
where
|
||||
T: ::subxt::Config,
|
||||
X: ::subxt::extrinsic::ExtrinsicParams<T>,
|
||||
{
|
||||
pub fn validate_metadata(&'a self) -> Result<(), ::subxt::MetadataError> {
|
||||
let runtime_metadata_hash = {
|
||||
let locked_metadata = self.client.metadata();
|
||||
let metadata = locked_metadata.read();
|
||||
metadata.metadata_hash(&PALLETS)
|
||||
};
|
||||
if runtime_metadata_hash != [ #(#metadata_hash,)* ] {
|
||||
Err(::subxt::MetadataError::IncompatibleMetadata)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constants(&'a self) -> ConstantsApi<'a, T> {
|
||||
ConstantsApi { client: &self.client }
|
||||
}
|
||||
|
||||
pub fn storage(&'a self) -> StorageApi<'a, T> {
|
||||
StorageApi { client: &self.client }
|
||||
}
|
||||
|
||||
pub fn tx(&'a self) -> TransactionApi<'a, T, X> {
|
||||
TransactionApi { client: &self.client, marker: ::core::marker::PhantomData }
|
||||
}
|
||||
|
||||
pub fn events(&'a self) -> EventsApi<'a, T> {
|
||||
EventsApi { client: &self.client }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventsApi<'a, T: ::subxt::Config> {
|
||||
client: &'a ::subxt::Client<T>,
|
||||
}
|
||||
|
||||
impl <'a, T: ::subxt::Config> EventsApi<'a, T> {
|
||||
pub async fn at(&self, block_hash: T::Hash) -> Result<::subxt::events::Events<T, Event>, ::subxt::BasicError> {
|
||||
::subxt::events::at::<T, Event>(self.client, block_hash).await
|
||||
}
|
||||
|
||||
pub async fn subscribe(&self) -> Result<::subxt::events::EventSubscription<'a, ::subxt::events::EventSub<T::Header>, T, Event>, ::subxt::BasicError> {
|
||||
::subxt::events::subscribe::<T, Event>(self.client).await
|
||||
}
|
||||
|
||||
pub async fn subscribe_finalized(&self) -> Result<::subxt::events::EventSubscription<'a, ::subxt::events::FinalizedEventSub<'a, T::Header>, T, Event>, ::subxt::BasicError> {
|
||||
::subxt::events::subscribe_finalized::<T, Event>(self.client).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConstantsApi<'a, T: ::subxt::Config> {
|
||||
client: &'a ::subxt::Client<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: ::subxt::Config> ConstantsApi<'a, T> {
|
||||
pub struct ConstantsApi;
|
||||
impl ConstantsApi {
|
||||
#(
|
||||
pub fn #pallets_with_constants(&self) -> #pallets_with_constants::constants::ConstantsApi<'a, T> {
|
||||
#pallets_with_constants::constants::ConstantsApi::new(self.client)
|
||||
pub fn #pallets_with_constants(&self) -> #pallets_with_constants::constants::ConstantsApi {
|
||||
#pallets_with_constants::constants::ConstantsApi
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
pub struct StorageApi<'a, T: ::subxt::Config> {
|
||||
client: &'a ::subxt::Client<T>,
|
||||
}
|
||||
|
||||
impl<'a, T> StorageApi<'a, T>
|
||||
where
|
||||
T: ::subxt::Config,
|
||||
{
|
||||
pub struct StorageApi;
|
||||
impl StorageApi {
|
||||
#(
|
||||
pub fn #pallets_with_storage(&self) -> #pallets_with_storage::storage::StorageApi<'a, T> {
|
||||
#pallets_with_storage::storage::StorageApi::new(self.client)
|
||||
pub fn #pallets_with_storage(&self) -> #pallets_with_storage::storage::StorageApi {
|
||||
#pallets_with_storage::storage::StorageApi
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
pub struct TransactionApi<'a, T: ::subxt::Config, X> {
|
||||
client: &'a ::subxt::Client<T>,
|
||||
marker: ::core::marker::PhantomData<X>,
|
||||
}
|
||||
|
||||
impl<'a, T, X> TransactionApi<'a, T, X>
|
||||
where
|
||||
T: ::subxt::Config,
|
||||
X: ::subxt::extrinsic::ExtrinsicParams<T>,
|
||||
{
|
||||
pub struct TransactionApi;
|
||||
impl TransactionApi {
|
||||
#(
|
||||
pub fn #pallets_with_calls(&self) -> #pallets_with_calls::calls::TransactionApi<'a, T, X> {
|
||||
#pallets_with_calls::calls::TransactionApi::new(self.client)
|
||||
pub fn #pallets_with_calls(&self) -> #pallets_with_calls::calls::TransactionApi {
|
||||
#pallets_with_calls::calls::TransactionApi
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
let runtime_metadata_hash = client.metadata().metadata_hash(&PALLETS);
|
||||
if runtime_metadata_hash != [ #(#metadata_hash,)* ] {
|
||||
Err(::subxt::error::MetadataError::IncompatibleMetadata)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+77
-211
@@ -23,32 +23,8 @@ use scale_info::{
|
||||
TypeDef,
|
||||
};
|
||||
|
||||
/// Generate storage from the provided pallet's metadata.
|
||||
///
|
||||
/// The function creates a new module named `storage` under the pallet's module.
|
||||
///
|
||||
/// ```ignore
|
||||
/// pub mod PalletName {
|
||||
/// pub mod storage {
|
||||
/// ...
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The function generates the storage as rust structs that implement the `subxt::StorageEntry`
|
||||
/// trait to uniquely identify the storage's identity when creating the extrinsic.
|
||||
///
|
||||
/// ```ignore
|
||||
/// pub struct StorageName {
|
||||
/// pub storage_param: type,
|
||||
/// }
|
||||
/// impl ::subxt::StorageEntry for StorageName {
|
||||
/// ...
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Storages are extracted from the API and wrapped into the generated `StorageApi` of
|
||||
/// each module.
|
||||
/// 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
|
||||
///
|
||||
@@ -68,27 +44,19 @@ pub fn generate_storage(
|
||||
return quote!()
|
||||
};
|
||||
|
||||
let (storage_structs, storage_fns): (Vec<_>, Vec<_>) = storage
|
||||
let storage_fns: Vec<_> = storage
|
||||
.entries
|
||||
.iter()
|
||||
.map(|entry| generate_storage_entry_fns(metadata, type_gen, pallet, entry))
|
||||
.unzip();
|
||||
.collect();
|
||||
|
||||
quote! {
|
||||
pub mod storage {
|
||||
use super::#types_mod_ident;
|
||||
|
||||
#( #storage_structs )*
|
||||
|
||||
pub struct StorageApi<'a, T: ::subxt::Config> {
|
||||
client: &'a ::subxt::Client<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: ::subxt::Config> StorageApi<'a, T> {
|
||||
pub fn new(client: &'a ::subxt::Client<T>) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
pub struct StorageApi;
|
||||
|
||||
impl StorageApi {
|
||||
#( #storage_fns )*
|
||||
}
|
||||
}
|
||||
@@ -100,16 +68,9 @@ fn generate_storage_entry_fns(
|
||||
type_gen: &TypeGenerator,
|
||||
pallet: &PalletMetadata<PortableForm>,
|
||||
storage_entry: &StorageEntryMetadata<PortableForm>,
|
||||
) -> (TokenStream2, TokenStream2) {
|
||||
let entry_struct_ident = format_ident!("{}", storage_entry.name);
|
||||
let (fields, entry_struct, constructor, key_impl, should_ref) = match storage_entry.ty
|
||||
{
|
||||
StorageEntryType::Plain(_) => {
|
||||
let entry_struct = quote!( pub struct #entry_struct_ident; );
|
||||
let constructor = quote!( #entry_struct_ident );
|
||||
let key_impl = quote!(::subxt::StorageEntryKey::Plain);
|
||||
(vec![], entry_struct, constructor, key_impl, false)
|
||||
}
|
||||
) -> TokenStream2 {
|
||||
let (fields, key_impl) = match storage_entry.ty {
|
||||
StorageEntryType::Plain(_) => (vec![], quote!(vec![])),
|
||||
StorageEntryType::Map {
|
||||
ref key,
|
||||
ref hashers,
|
||||
@@ -129,7 +90,7 @@ fn generate_storage_entry_fns(
|
||||
StorageHasher::Identity => "Identity",
|
||||
};
|
||||
let hasher = format_ident!("{}", hasher);
|
||||
quote!( ::subxt::StorageHasher::#hasher )
|
||||
quote!( ::subxt::storage::address::StorageHasher::#hasher )
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
match key_ty.type_def() {
|
||||
@@ -145,60 +106,28 @@ fn generate_storage_entry_fns(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let field_names = fields.iter().map(|(n, _)| n);
|
||||
let field_types = fields.iter().map(|(_, t)| {
|
||||
// If the field type is `::std::vec::Vec<T>` obtain the type parameter and
|
||||
// surround with slice brackets. Otherwise, utilize the field_type as is.
|
||||
match t.vec_type_param() {
|
||||
Some(ty) => quote!([#ty]),
|
||||
None => quote!(#t),
|
||||
}
|
||||
});
|
||||
// There cannot be a reference without a parameter.
|
||||
let should_ref = !fields.is_empty();
|
||||
let (entry_struct, constructor) = if should_ref {
|
||||
(
|
||||
quote! {
|
||||
pub struct #entry_struct_ident <'a>( #( pub &'a #field_types ),* );
|
||||
},
|
||||
quote!( #entry_struct_ident( #( #field_names ),* ) ),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
quote!( pub struct #entry_struct_ident; ),
|
||||
quote!( #entry_struct_ident ),
|
||||
)
|
||||
};
|
||||
|
||||
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()
|
||||
.enumerate()
|
||||
.map(|(field_idx, hasher)| {
|
||||
let index = syn::Index::from(field_idx);
|
||||
quote!( ::subxt::StorageMapKey::new(&self.#index, #hasher) )
|
||||
.zip(&fields)
|
||||
.map(|(hasher, (field_name, _))| {
|
||||
quote!( ::subxt::storage::address::StorageMapKey::new(#field_name.borrow(), #hasher) )
|
||||
});
|
||||
quote! {
|
||||
::subxt::StorageEntryKey::Map(
|
||||
vec![ #( #keys ),* ]
|
||||
)
|
||||
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 = (0..fields.len()).map(|field_idx| {
|
||||
let index = syn::Index::from(field_idx);
|
||||
quote!( &self.#index )
|
||||
});
|
||||
let items =
|
||||
fields.iter().map(|(field_name, _)| quote!( #field_name ));
|
||||
quote! {
|
||||
::subxt::StorageEntryKey::Map(
|
||||
vec![ ::subxt::StorageMapKey::new(&(#( #items ),*), #hasher) ]
|
||||
)
|
||||
vec![ ::subxt::storage::address::StorageMapKey::new(&(#( #items.borrow() ),*), #hasher) ]
|
||||
}
|
||||
} else {
|
||||
// If we hit this condition, we don't know how to handle the number of hashes vs fields
|
||||
@@ -210,33 +139,18 @@ fn generate_storage_entry_fns(
|
||||
)
|
||||
};
|
||||
|
||||
(fields, entry_struct, constructor, key_impl, should_ref)
|
||||
(fields, key_impl)
|
||||
}
|
||||
_ => {
|
||||
let (lifetime_param, lifetime_ref) = (quote!(<'a>), quote!(&'a));
|
||||
|
||||
let ty_path = type_gen.resolve_type_path(key.id(), &[]);
|
||||
let fields = vec![(format_ident!("_0"), ty_path.clone())];
|
||||
|
||||
// `ty_path` can be `std::vec::Vec<T>`. In such cases, the entry struct
|
||||
// should contain a slice reference.
|
||||
let ty_slice = match ty_path.vec_type_param() {
|
||||
Some(ty) => quote!([#ty]),
|
||||
None => quote!(#ty_path),
|
||||
};
|
||||
let entry_struct = quote! {
|
||||
pub struct #entry_struct_ident #lifetime_param( pub #lifetime_ref #ty_slice );
|
||||
};
|
||||
let constructor = quote!( #entry_struct_ident(_0) );
|
||||
let fields = vec![(format_ident!("_0"), ty_path)];
|
||||
let hasher = hashers.get(0).unwrap_or_else(|| {
|
||||
abort_call_site!("No hasher found for single key")
|
||||
});
|
||||
let key_impl = quote! {
|
||||
::subxt::StorageEntryKey::Map(
|
||||
vec![ ::subxt::StorageMapKey::new(&self.0, #hasher) ]
|
||||
)
|
||||
vec![ ::subxt::storage::address::StorageMapKey::new(_0.borrow(), #hasher) ]
|
||||
};
|
||||
(fields, entry_struct, constructor, key_impl, true)
|
||||
(fields, key_impl)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,133 +169,85 @@ fn generate_storage_entry_fns(
|
||||
});
|
||||
|
||||
let fn_name = format_ident!("{}", storage_entry.name.to_snake_case());
|
||||
let fn_name_iter = format_ident!("{}_iter", fn_name);
|
||||
let storage_entry_ty = match storage_entry.ty {
|
||||
StorageEntryType::Plain(ref ty) => ty,
|
||||
StorageEntryType::Map { ref value, .. } => value,
|
||||
};
|
||||
let storage_entry_value_ty = type_gen.resolve_type_path(storage_entry_ty.id(), &[]);
|
||||
let (return_ty, fetch) = match storage_entry.modifier {
|
||||
StorageEntryModifier::Default => {
|
||||
(quote!( #storage_entry_value_ty ), quote!(fetch_or_default))
|
||||
}
|
||||
StorageEntryModifier::Optional => {
|
||||
(
|
||||
quote!( ::core::option::Option<#storage_entry_value_ty> ),
|
||||
quote!(fetch),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let storage_entry_impl = quote! (
|
||||
const PALLET: &'static str = #pallet_name;
|
||||
const STORAGE: &'static str = #storage_name;
|
||||
type Value = #storage_entry_value_ty;
|
||||
fn key(&self) -> ::subxt::StorageEntryKey {
|
||||
#key_impl
|
||||
}
|
||||
);
|
||||
|
||||
let anon_lifetime = match should_ref {
|
||||
true => quote!(<'_>),
|
||||
false => quote!(),
|
||||
};
|
||||
let storage_entry_type = quote! {
|
||||
#entry_struct
|
||||
impl ::subxt::StorageEntry for #entry_struct_ident #anon_lifetime {
|
||||
#storage_entry_impl
|
||||
}
|
||||
};
|
||||
|
||||
let docs = &storage_entry.docs;
|
||||
let docs_token = quote! { #( #[doc = #docs ] )* };
|
||||
|
||||
let lifetime_param = match should_ref {
|
||||
true => quote!(<'a>),
|
||||
false => quote!(),
|
||||
let key_args = fields.iter().map(|(field_name, field_type)| {
|
||||
// The field type is translated from `std::vec::Vec<T>` to `[T]`. We apply
|
||||
// AsRef to all types, so this just makes it a little more ergonomic.
|
||||
//
|
||||
// TODO [jsdw]: Support mappings like `String -> str` too for better borrow
|
||||
// ergonomics.
|
||||
let field_ty = match field_type.vec_type_param() {
|
||||
Some(ty) => quote!([#ty]),
|
||||
_ => quote!(#field_type),
|
||||
};
|
||||
quote!( #field_name: impl ::std::borrow::Borrow<#field_ty> )
|
||||
});
|
||||
|
||||
let is_map_type = matches!(storage_entry.ty, StorageEntryType::Map { .. });
|
||||
|
||||
// Is the entry iterable?
|
||||
let is_iterable_type = if is_map_type {
|
||||
quote!(::subxt::storage::address::Yes)
|
||||
} else {
|
||||
quote!(())
|
||||
};
|
||||
let client_iter_fn = if matches!(storage_entry.ty, StorageEntryType::Map { .. }) {
|
||||
|
||||
let has_default_value = match storage_entry.modifier {
|
||||
StorageEntryModifier::Default => true,
|
||||
StorageEntryModifier::Optional => false,
|
||||
};
|
||||
|
||||
// Does the entry have a default value?
|
||||
let is_defaultable_type = if has_default_value {
|
||||
quote!(::subxt::storage::address::Yes)
|
||||
} else {
|
||||
quote!(())
|
||||
};
|
||||
|
||||
// If the item is a map, we want a way to access the root entry to do things like iterate over it,
|
||||
// so expose a function to create this entry, too:
|
||||
let root_entry_fn = if is_map_type {
|
||||
let fn_name_root = format_ident!("{}_root", fn_name);
|
||||
quote! (
|
||||
#docs_token
|
||||
pub fn #fn_name_iter(
|
||||
pub fn #fn_name_root(
|
||||
&self,
|
||||
block_hash: ::core::option::Option<T::Hash>,
|
||||
) -> impl ::core::future::Future<
|
||||
Output = ::core::result::Result<::subxt::KeyIter<'a, T, #entry_struct_ident #lifetime_param>, ::subxt::BasicError>
|
||||
> + 'a {
|
||||
// Instead of an async fn which borrows all of self,
|
||||
// we make sure that the returned future only borrows
|
||||
// client, which allows you to chain calls a little better.
|
||||
let client = self.client;
|
||||
async move {
|
||||
let runtime_storage_hash = {
|
||||
let locked_metadata = client.metadata();
|
||||
let metadata = locked_metadata.read();
|
||||
match metadata.storage_hash::<#entry_struct_ident>() {
|
||||
Ok(hash) => hash,
|
||||
Err(e) => return Err(e.into())
|
||||
}
|
||||
};
|
||||
if runtime_storage_hash == [#(#storage_hash,)*] {
|
||||
client.storage().iter(block_hash).await
|
||||
} else {
|
||||
Err(::subxt::MetadataError::IncompatibleMetadata.into())
|
||||
}
|
||||
}
|
||||
) -> ::subxt::storage::address::StaticStorageAddress::<::subxt::metadata::DecodeStaticType<#storage_entry_value_ty>, (), #is_defaultable_type, #is_iterable_type> {
|
||||
::subxt::storage::address::StaticStorageAddress::new(
|
||||
#pallet_name,
|
||||
#storage_name,
|
||||
Vec::new(),
|
||||
[#(#storage_hash,)*]
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let key_args_ref = match should_ref {
|
||||
true => quote!(&'a),
|
||||
false => quote!(),
|
||||
};
|
||||
let key_args = fields.iter().map(|(field_name, field_type)| {
|
||||
// The field type is translated from `std::vec::Vec<T>` to `[T]`, if the
|
||||
// interface should generate a reference. In such cases, the vector ultimately is
|
||||
// a slice.
|
||||
let field_ty = match field_type.vec_type_param() {
|
||||
Some(ty) if should_ref => quote!([#ty]),
|
||||
_ => quote!(#field_type),
|
||||
};
|
||||
quote!( #field_name: #key_args_ref #field_ty )
|
||||
});
|
||||
|
||||
let client_fns = quote! {
|
||||
quote! {
|
||||
// Access a specific value from a storage entry
|
||||
#docs_token
|
||||
pub fn #fn_name(
|
||||
&self,
|
||||
#( #key_args, )*
|
||||
block_hash: ::core::option::Option<T::Hash>,
|
||||
) -> impl ::core::future::Future<
|
||||
Output = ::core::result::Result<#return_ty, ::subxt::BasicError>
|
||||
> + 'a {
|
||||
// Instead of an async fn which borrows all of self,
|
||||
// we make sure that the returned future only borrows
|
||||
// client, which allows you to chain calls a little better.
|
||||
let client = self.client;
|
||||
async move {
|
||||
let runtime_storage_hash = {
|
||||
let locked_metadata = client.metadata();
|
||||
let metadata = locked_metadata.read();
|
||||
match metadata.storage_hash::<#entry_struct_ident>() {
|
||||
Ok(hash) => hash,
|
||||
Err(e) => return Err(e.into())
|
||||
}
|
||||
};
|
||||
if runtime_storage_hash == [#(#storage_hash,)*] {
|
||||
let entry = #constructor;
|
||||
client.storage().#fetch(&entry, block_hash).await
|
||||
} else {
|
||||
Err(::subxt::MetadataError::IncompatibleMetadata.into())
|
||||
}
|
||||
}
|
||||
) -> ::subxt::storage::address::StaticStorageAddress::<::subxt::metadata::DecodeStaticType<#storage_entry_value_ty>, ::subxt::storage::address::Yes, #is_defaultable_type, #is_iterable_type> {
|
||||
::subxt::storage::address::StaticStorageAddress::new(
|
||||
#pallet_name,
|
||||
#storage_name,
|
||||
#key_impl,
|
||||
[#(#storage_hash,)*]
|
||||
)
|
||||
}
|
||||
|
||||
#client_iter_fn
|
||||
};
|
||||
|
||||
(storage_entry_type, client_fns)
|
||||
#root_entry_fn
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user