mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-22 20:48:01 +00:00
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:
@@ -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
@@ -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,
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
|
||||
// This file is part of subxt.
|
||||
//
|
||||
// subxt is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// subxt is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{
|
||||
ClientBuilder,
|
||||
Config,
|
||||
DefaultConfig,
|
||||
DefaultExtra,
|
||||
PairSigner,
|
||||
};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
/// Custom [`Config`] impl where the default types for the target chain differ from the
|
||||
/// [`DefaultConfig`]
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct MyConfig;
|
||||
impl Config for MyConfig {
|
||||
// This is different from the default `u32`.
|
||||
//
|
||||
// *Note* that in this example it does differ from the actual `Index` type in the
|
||||
// polkadot runtime used, so some operations will fail. Normally when using a custom `Config`
|
||||
// impl types MUST match exactly those used in the actual runtime.
|
||||
type Index = u64;
|
||||
type BlockNumber = <DefaultConfig as Config>::BlockNumber;
|
||||
type Hash = <DefaultConfig as Config>::Hash;
|
||||
type Hashing = <DefaultConfig as Config>::Hashing;
|
||||
type AccountId = <DefaultConfig as Config>::AccountId;
|
||||
type Address = <DefaultConfig as Config>::Address;
|
||||
type Header = <DefaultConfig as Config>::Header;
|
||||
type Signature = <DefaultConfig as Config>::Signature;
|
||||
type Extrinsic = <DefaultConfig as Config>::Extrinsic;
|
||||
}
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let api = ClientBuilder::new()
|
||||
.build()
|
||||
.await?
|
||||
.to_runtime_api::<polkadot::RuntimeApi<MyConfig, DefaultExtra<MyConfig>>>();
|
||||
|
||||
let signer = PairSigner::new(AccountKeyring::Alice.pair());
|
||||
let dest = AccountKeyring::Bob.to_account_id().into();
|
||||
|
||||
let hash = api
|
||||
.tx()
|
||||
.balances()
|
||||
.transfer(dest, 10_000)
|
||||
.sign_and_submit(&signer)
|
||||
.await?;
|
||||
|
||||
println!("Balance transfer extrinsic submitted: {}", hash);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+10
-3
@@ -197,7 +197,7 @@ impl<'client, T, X, A, C, E> SubmittableExtrinsic<'client, T, X, A, C, E>
|
||||
where
|
||||
T: Config,
|
||||
X: SignedExtra<T>,
|
||||
A: AccountData<T>,
|
||||
A: AccountData,
|
||||
C: Call + Send + Sync,
|
||||
E: Decode,
|
||||
{
|
||||
@@ -221,6 +221,8 @@ where
|
||||
where
|
||||
<<X as SignedExtra<T>>::Extra as SignedExtension>::AdditionalSigned:
|
||||
Send + Sync + 'static,
|
||||
<A as AccountData>::AccountId: From<<T as Config>::AccountId>,
|
||||
<A as AccountData>::Index: Into<<T as Config>::Index>,
|
||||
{
|
||||
// Sign the call data to create our extrinsic.
|
||||
let extrinsic = self.create_signed(signer, Default::default()).await?;
|
||||
@@ -249,6 +251,8 @@ where
|
||||
where
|
||||
<<X as SignedExtra<T>>::Extra as SignedExtension>::AdditionalSigned:
|
||||
Send + Sync + 'static,
|
||||
<A as AccountData>::AccountId: From<<T as Config>::AccountId>,
|
||||
<A as AccountData>::Index: Into<<T as Config>::Index>,
|
||||
{
|
||||
let extrinsic = self.create_signed(signer, Default::default()).await?;
|
||||
self.client.rpc().submit_extrinsic(extrinsic).await
|
||||
@@ -263,17 +267,20 @@ where
|
||||
where
|
||||
<<X as SignedExtra<T>>::Extra as SignedExtension>::AdditionalSigned:
|
||||
Send + Sync + 'static,
|
||||
<A as AccountData>::AccountId: From<<T as Config>::AccountId>,
|
||||
<A as AccountData>::Index: Into<<T as Config>::Index>,
|
||||
{
|
||||
let account_nonce = if let Some(nonce) = signer.nonce() {
|
||||
nonce
|
||||
} else {
|
||||
let account_storage_entry = A::storage_entry(signer.account_id().clone());
|
||||
let account_storage_entry =
|
||||
A::storage_entry(signer.account_id().clone().into());
|
||||
let account_data = self
|
||||
.client
|
||||
.storage()
|
||||
.fetch_or_default(&account_storage_entry, None)
|
||||
.await?;
|
||||
A::nonce(&account_data)
|
||||
A::nonce(&account_data).into()
|
||||
};
|
||||
let call = self
|
||||
.client
|
||||
|
||||
+14
-6
@@ -104,12 +104,20 @@ impl Config for DefaultConfig {
|
||||
}
|
||||
|
||||
/// Trait to fetch data about an account.
|
||||
///
|
||||
/// Should be implemented on a type implementing `StorageEntry`,
|
||||
/// usually generated by the `subxt` macro.
|
||||
pub trait AccountData<T: Config>: StorageEntry {
|
||||
pub trait AccountData {
|
||||
/// The runtime storage entry from which the account data can be fetched.
|
||||
/// Usually generated by the `subxt` macro.
|
||||
type StorageEntry: StorageEntry;
|
||||
|
||||
/// The type of the account id to fetch the account data for.
|
||||
type AccountId;
|
||||
|
||||
/// The type of the account nonce returned from storage.
|
||||
type Index;
|
||||
|
||||
/// Create a new storage entry key from the account id.
|
||||
fn storage_entry(account_id: T::AccountId) -> Self;
|
||||
fn storage_entry(account_id: Self::AccountId) -> Self::StorageEntry;
|
||||
|
||||
/// Get the nonce from the storage entry value.
|
||||
fn nonce(result: &<Self as StorageEntry>::Value) -> T::Index;
|
||||
fn nonce(result: &<Self::StorageEntry as StorageEntry>::Value) -> Self::Index;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user