Skip to content

Commit

Permalink
Parameterize AccountData (paritytech#409)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* Update examples/custom_config.rs

Co-authored-by: David <[email protected]>

* Rename Nonce assoc type to Index for consistency

* Add module level docs about codegen assumptions

Co-authored-by: David <[email protected]>
  • Loading branch information
2 people authored and 0623forbidden committed Feb 15, 2022
1 parent 65b75d3 commit ab83d8a
Show file tree
Hide file tree
Showing 6 changed files with 664 additions and 144 deletions.
2 changes: 1 addition & 1 deletion codegen/src/api/calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
123 changes: 105 additions & 18 deletions codegen/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -42,6 +61,7 @@ use quote::{
format_ident,
quote,
};
use scale_info::form::PortableForm;
use std::{
collections::HashMap,
fs,
Expand Down Expand Up @@ -242,17 +262,23 @@ 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 {
#outer_event
#( #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;

Expand All @@ -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
Expand All @@ -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 }
}
}
Expand Down Expand Up @@ -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> {
Expand All @@ -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,
Expand Down
70 changes: 70 additions & 0 deletions examples/custom_config.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
13 changes: 10 additions & 3 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
{
Expand All @@ -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?;
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
20 changes: 14 additions & 6 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Loading

0 comments on commit ab83d8a

Please sign in to comment.