Parameterize AccountData (#409)

* Add custom config example

* Make account data storage item configurable

* Fix contracts

* Regenerate polkadot codegen

* Fmt

* Specify different type for `MyConfig::Index`

* Update comment in custom config example

* Assign concrete types for default AccountData impl

* Fmt

* Fix contracts tests

* Fmt

* Add comments

* Unlink doc comment trait (subxt not in scope)

* Fix missing nonce field error message

* Update codegen/src/api/mod.rs

Co-authored-by: David <dvdplm@gmail.com>

* Update examples/custom_config.rs

Co-authored-by: David <dvdplm@gmail.com>

* Rename Nonce assoc type to Index for consistency

* Add module level docs about codegen assumptions

Co-authored-by: David <dvdplm@gmail.com>
This commit is contained in:
Andrew Jones
2022-01-31 12:46:29 +00:00
committed by GitHub
parent 79b56612e4
commit f5d8a846ec
6 changed files with 666 additions and 146 deletions
+1 -1
View File
@@ -116,7 +116,7 @@ pub fn generate_calls(
where
T: ::subxt::Config,
X: ::subxt::SignedExtra<T>,
A: ::subxt::AccountData<T>,
A: ::subxt::AccountData,
{
pub fn new(client: &'a ::subxt::Client<T>) -> Self {
Self { client, marker: ::core::marker::PhantomData }
+105 -18
View File
@@ -14,6 +14,23 @@
// You should have received a copy of the GNU General Public License
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
//! Generate code for submitting extrinsics and query storage of a Substrate runtime.
//!
//! ## Note
//!
//! By default the codegen will search for the `System` pallet's `Account` storage item, which is
//! the conventional location where an account's index (aka nonce) is stored.
//!
//! If this `System::Account` storage item is discovered, then it is assumed that:
//!
//! 1. The type of the storage item is a `struct` (aka a composite type)
//! 2. There exists a field called `nonce` which contains the account index.
//!
//! These assumptions are based on the fact that the `frame_system::AccountInfo` type is the default
//! configured type, and that the vast majority of chain configurations will use this.
//!
//! If either of these conditions are not satisfied, the codegen will fail.
mod calls;
mod constants;
mod errors;
@@ -32,8 +49,10 @@ use crate::{
use codec::Decode;
use frame_metadata::{
v14::RuntimeMetadataV14,
PalletMetadata,
RuntimeMetadata,
RuntimeMetadataPrefixed,
StorageEntryType,
};
use heck::SnakeCase as _;
use proc_macro2::TokenStream as TokenStream2;
@@ -42,6 +61,7 @@ use quote::{
format_ident,
quote,
};
use scale_info::form::PortableForm;
use std::{
collections::HashMap,
fs,
@@ -242,6 +262,16 @@ impl RuntimeGenerator {
let error_type = error_details.type_def;
let error_fn = error_details.dispatch_error_impl_fn;
let default_account_data_ident = format_ident!("DefaultAccountData");
let default_account_data_impl = generate_default_account_data_impl(
&pallets_with_mod_names,
&default_account_data_ident,
&type_gen,
);
let type_parameter_default_impl = default_account_data_impl
.as_ref()
.map(|_| quote!( = #default_account_data_ident ));
quote! {
#[allow(dead_code, unused_imports, non_camel_case_types)]
pub mod #mod_ident {
@@ -249,10 +279,6 @@ impl RuntimeGenerator {
#( #modules )*
#types_mod
/// The default storage entry from which to fetch an account nonce, required for
/// constructing a transaction.
pub type DefaultAccountData = self::system::storage::Account;
/// The default error type returned when there is a runtime issue.
pub type DispatchError = self::runtime_types::sp_runtime::DispatchError;
@@ -262,34 +288,29 @@ impl RuntimeGenerator {
#error_fn
}
impl ::subxt::AccountData<::subxt::DefaultConfig> for DefaultAccountData {
fn nonce(result: &<Self as ::subxt::StorageEntry>::Value) -> <::subxt::DefaultConfig as ::subxt::Config>::Index {
result.nonce
}
fn storage_entry(account_id: <::subxt::DefaultConfig as ::subxt::Config>::AccountId) -> Self {
Self(account_id)
}
}
#default_account_data_impl
pub struct RuntimeApi<T: ::subxt::Config, X> {
pub struct RuntimeApi<T: ::subxt::Config, X, A #type_parameter_default_impl> {
pub client: ::subxt::Client<T>,
marker: ::core::marker::PhantomData<X>,
marker: ::core::marker::PhantomData<(X, A)>,
}
impl<T, X> ::core::convert::From<::subxt::Client<T>> for RuntimeApi<T, X>
impl<T, X, A> ::core::convert::From<::subxt::Client<T>> for RuntimeApi<T, X, A>
where
T: ::subxt::Config,
X: ::subxt::SignedExtra<T>,
A: ::subxt::AccountData,
{
fn from(client: ::subxt::Client<T>) -> Self {
Self { client, marker: ::core::marker::PhantomData }
}
}
impl<'a, T, X> RuntimeApi<T, X>
impl<'a, T, X, A> RuntimeApi<T, X, A>
where
T: ::subxt::Config,
X: ::subxt::SignedExtra<T>,
A: ::subxt::AccountData,
{
pub fn constants(&'a self) -> ConstantsApi {
ConstantsApi
@@ -299,7 +320,7 @@ impl RuntimeGenerator {
StorageApi { client: &self.client }
}
pub fn tx(&'a self) -> TransactionApi<'a, T, X, DefaultAccountData> {
pub fn tx(&'a self) -> TransactionApi<'a, T, X, A> {
TransactionApi { client: &self.client, marker: ::core::marker::PhantomData }
}
}
@@ -339,7 +360,7 @@ impl RuntimeGenerator {
where
T: ::subxt::Config,
X: ::subxt::SignedExtra<T>,
A: ::subxt::AccountData<T>,
A: ::subxt::AccountData,
{
#(
pub fn #pallets_with_calls(&self) -> #pallets_with_calls::calls::TransactionApi<'a, T, X, A> {
@@ -352,6 +373,72 @@ impl RuntimeGenerator {
}
}
/// Most chains require a valid account nonce as part of the extrinsic, so the default behaviour of
/// the client is to fetch the nonce for the current account.
///
/// The account index (aka nonce) is commonly stored in the `System` pallet's `Account` storage item.
/// This function attempts to find that storage item, and if it is present will implement the
/// `subxt::AccountData` trait for it. This allows the client to construct the appropriate
/// storage key from the account id, and then retrieve the `nonce` from the resulting storage item.
fn generate_default_account_data_impl(
pallets_with_mod_names: &[(&PalletMetadata<PortableForm>, syn::Ident)],
default_impl_name: &syn::Ident,
type_gen: &TypeGenerator,
) -> Option<TokenStream2> {
let storage = pallets_with_mod_names
.iter()
.find(|(pallet, _)| pallet.name == "System")
.and_then(|(pallet, _)| pallet.storage.as_ref())?;
let storage_entry = storage
.entries
.iter()
.find(|entry| entry.name == "Account")?;
// resolve the concrete types for `AccountId` (to build the key) and `Index` to extract the
// account index (nonce) value from the result.
let (account_id_ty, account_nonce_ty) =
if let StorageEntryType::Map { key, value, .. } = &storage_entry.ty {
let account_id_ty = type_gen.resolve_type_path(key.id(), &[]);
let account_data_ty = type_gen.resolve_type(value.id());
let nonce_field = if let scale_info::TypeDef::Composite(composite) =
account_data_ty.type_def()
{
composite
.fields()
.iter()
.find(|f| f.name() == Some(&"nonce".to_string()))?
} else {
abort_call_site!("Expected a `nonce` field in the account info struct")
};
let account_nonce_ty = type_gen.resolve_type_path(nonce_field.ty().id(), &[]);
(account_id_ty, account_nonce_ty)
} else {
abort_call_site!("System::Account should be a `StorageEntryType::Map`")
};
// this path to the storage entry depends on storage codegen.
let storage_entry_path = quote!(self::system::storage::Account);
Some(quote! {
/// The default storage entry from which to fetch an account nonce, required for
/// constructing a transaction.
pub enum #default_impl_name {}
impl ::subxt::AccountData for #default_impl_name {
type StorageEntry = #storage_entry_path;
type AccountId = #account_id_ty;
type Index = #account_nonce_ty;
fn nonce(result: &<Self::StorageEntry as ::subxt::StorageEntry>::Value) -> Self::Index {
result.nonce
}
fn storage_entry(account_id: Self::AccountId) -> Self::StorageEntry {
#storage_entry_path(account_id)
}
}
})
}
pub fn generate_structs_from_variants<'a, F>(
type_gen: &'a TypeGenerator,
type_id: u32,