Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Double map and plain storage support, introduce macros #93

Merged
merged 14 commits into from
Apr 28, 2020
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[workspace]
members = [".", "proc-macro"]

[package]
name = "substrate-subxt"
version = "0.6.0"
Expand Down Expand Up @@ -35,7 +38,7 @@ sp-core = { version = "2.0.0-alpha.6", package = "sp-core" }
sp-transaction-pool = { version = "2.0.0-alpha.6", package = "sp-transaction-pool" }

[dev-dependencies]
async-std = "1.5.0"
async-std = { version = "1.5.0", features = ["attributes"] }
env_logger = "0.7"
wabt = "0.9"
frame-system = { version = "2.0.0-alpha.6", package = "frame-system" }
Expand Down
10 changes: 5 additions & 5 deletions examples/kusama_balance_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,17 @@ fn main() {

async fn transfer_balance() -> Result<sp_core::H256, Error> {
let signer = AccountKeyring::Alice.pair();
let dest = AccountKeyring::Bob.to_account_id();
let dest = AccountKeyring::Bob.to_account_id().into();

// note use of `KusamaRuntime`
substrate_subxt::ClientBuilder::<KusamaRuntime>::new()
.build()
.await?
.xt(signer, None)
.await?
.submit(balances::transfer::<KusamaRuntime>(
dest.clone().into(),
10_000,
))
.submit(balances::TransferCall {
to: &dest,
amount: 10_000,
})
.await
}
60 changes: 21 additions & 39 deletions examples/submit_and_watch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,49 +17,31 @@
use sp_keyring::AccountKeyring;
use substrate_subxt::{
balances,
system::System,
DefaultNodeRuntime as Runtime,
ExtrinsicSuccess,
};

type AccountId = <Runtime as System>::AccountId;
type Balance = <Runtime as balances::Balances>::Balance;
#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();

fn main() {
let result: Result<ExtrinsicSuccess<_>, Box<dyn std::error::Error + 'static>> =
async_std::task::block_on(async move {
env_logger::init();
let signer = AccountKeyring::Alice.pair();
let dest = AccountKeyring::Bob.to_account_id().into();

let signer = AccountKeyring::Alice.pair();

let dest = AccountKeyring::Bob.to_account_id();

let cli = substrate_subxt::ClientBuilder::<Runtime>::new()
.build()
.await?;
let xt = cli.xt(signer, None).await?;
let xt_result = xt
.watch()
.events_decoder(|decoder| {
// for any primitive event with no type size registered
decoder.register_type_size::<(u64, u64)>("IdentificationTuple")
})
.submit(balances::transfer::<Runtime>(dest.clone().into(), 10_000))
.await?;
Ok(xt_result)
});
match result {
Ok(extrinsic_success) => {
match extrinsic_success
.find_event::<(AccountId, AccountId, Balance)>("Balances", "Transfer")
{
Some(Ok((_from, _to, value))) => {
println!("Balance transfer success: value: {:?}", value)
}
Some(Err(err)) => println!("Failed to decode code hash: {}", err),
None => println!("Failed to find Balances::Transfer Event"),
}
}
Err(err) => println!("Error: {:?}", err),
let cli = substrate_subxt::ClientBuilder::<Runtime>::new()
.build()
.await?;
let xt = cli.xt(signer, None).await?;
let xt_result = xt
.watch()
.submit(balances::TransferCall {
to: &dest,
amount: 10_000,
})
.await?;
if let Some(event) = xt_result.find_event::<balances::TransferEvent<_>>()? {
println!("Balance transfer success: value: {:?}", event.amount);
} else {
println!("Failed to find Balances::Transfer Event");
}
Ok(())
}
33 changes: 33 additions & 0 deletions proc-macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "substrate-subxt-proc-macro"
version = "0.1.0"
authors = ["David Craven <david@craven.ch>"]
edition = "2018"
autotests = false

[lib]
proc-macro = true

[dependencies]
heck = "0.3.1"
proc-macro2 = "1.0.10"
proc-macro-crate = "0.1.4"
quote = "1.0.3"
syn = "1.0.17"
synstructure = "0.12.3"

[dev-dependencies]
async-std = { version = "1.5.0", features = ["attributes"] }
codec = { package = "parity-scale-codec", version = "1.2", default-features = false, features = ["derive", "full"] }
env_logger = "0.7.1"
frame-support = "2.0.0-alpha.6"
pretty_assertions = "0.6.1"
sp-core = "2.0.0-alpha.6"
sp-keyring = "2.0.0-alpha.6"
sp-runtime = "2.0.0-alpha.6"
substrate-subxt = { path = ".." }
trybuild = "1.0.25"

[[test]]
name = "balances"
path = "tests/balances.rs"
140 changes: 140 additions & 0 deletions proc-macro/src/call.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use crate::utils;
use heck::{
CamelCase,
SnakeCase,
};
use proc_macro2::TokenStream;
use quote::{
format_ident,
quote,
};
use synstructure::Structure;

pub fn call(s: Structure) -> TokenStream {
let subxt = utils::use_crate("substrate-subxt");
let codec = utils::use_crate("parity-scale-codec");
let sp_core = utils::use_crate("sp-core");
let sp_runtime = utils::use_crate("sp-runtime");
let ident = &s.ast().ident;
let generics = &s.ast().generics;
let params = utils::type_params(generics);
let module = utils::module_name(generics);
let with_module = format_ident!(
"with_{}",
utils::path_to_ident(module).to_string().to_snake_case()
);
let call_name = ident.to_string().trim_end_matches("Call").to_snake_case();
let call = format_ident!("{}", call_name);
let call_trait = format_ident!("{}CallExt", call_name.to_camel_case());
let bindings = utils::bindings(&s);
let fields = bindings.iter().map(|bi| {
let ident = bi.ast().ident.as_ref().unwrap();
quote!(#ident,)
});
let args = bindings.iter().map(|bi| {
let ident = bi.ast().ident.as_ref().unwrap();
let ty = &bi.ast().ty;
quote!(#ident: #ty,)
});
let args = quote!(#(#args)*);
let ret = quote!(#subxt::ExtrinsicSuccess<T>);

let expanded = quote! {
impl#generics #subxt::Call<T> for #ident<#(#params),*> {
const MODULE: &'static str = MODULE;
const FUNCTION: &'static str = #call_name;
fn events_decoder(
decoder: &mut #subxt::EventsDecoder<T>,
) -> Result<(), #subxt::EventsError> {
decoder.#with_module()?;
Ok(())
}
}

pub trait #call_trait<T: #module> {
fn #call<'a>(
self,
#args
) -> core::pin::Pin<Box<dyn core::future::Future<Output = Result<#ret, #subxt::Error>> + Send + 'a>>;
}

impl<T, P, S, E> #call_trait<T> for #subxt::EventsSubscriber<T, P, S, E>
where
T: #module + #subxt::system::System + Send + Sync,
P: #sp_core::Pair,
S: #sp_runtime::traits::Verify + #codec::Codec + From<P::Signature> + Send + 'static,
S::Signer: From<P::Public> + #sp_runtime::traits::IdentifyAccount<AccountId = T::AccountId>,
T::Address: From<T::AccountId>,
E: #subxt::SignedExtra<T> + #sp_runtime::traits::SignedExtension + 'static,
{
fn #call<'a>(
self,
#args
) -> core::pin::Pin<Box<dyn core::future::Future<Output = Result<#ret, #subxt::Error>> + Send + 'a>> {
Box::pin(self.submit(#ident { #(#fields)* }))
}
}
};

TokenStream::from(expanded)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_transfer_call() {
let input = quote! {
#[derive(Call, Encode)]
pub struct TransferCall<'a, T: Balances> {
pub to: &'a <T as System>::Address,
#[codec(compact)]
pub amount: T::Balance,
}
};
let expected = quote! {
impl<'a, T: Balances> substrate_subxt::Call<T> for TransferCall<'a, T> {
const MODULE: &'static str = MODULE;
const FUNCTION: &'static str = "transfer";
fn events_decoder(
decoder: &mut substrate_subxt::EventsDecoder<T>,
) -> Result<(), substrate_subxt::EventsError> {
decoder.with_balances()?;
Ok(())
}
}

pub trait TransferCallExt<T: Balances> {
fn transfer<'a>(
self,
to: &'a <T as System>::Address,
amount: T::Balance,
) -> core::pin::Pin<Box<dyn core::future::Future<Output = Result<substrate_subxt::ExtrinsicSuccess<T>, substrate_subxt::Error>> + Send + 'a>>;
}

impl<T, P, S, E> TransferCallExt<T> for substrate_subxt::EventsSubscriber<T, P, S, E>
where
T: Balances + substrate_subxt::system::System + Send + Sync,
P: sp_core::Pair,
S: sp_runtime::traits::Verify + codec::Codec + From<P::Signature> + Send + 'static,
S::Signer: From<P::Public> + sp_runtime::traits::IdentifyAccount<
AccountId = T::AccountId>,
T::Address: From<T::AccountId>,
E: substrate_subxt::SignedExtra<T> + sp_runtime::traits::SignedExtension + 'static,
{
fn transfer<'a>(
self,
to: &'a <T as System>::Address,
amount: T::Balance,
) -> core::pin::Pin<Box<dyn core::future::Future<Output = Result<substrate_subxt::ExtrinsicSuccess<T>, substrate_subxt::Error>> + Send + 'a>> {
Box::pin(self.submit(TransferCall { to, amount, }))
}
}
};
let derive_input = syn::parse2(input).unwrap();
let s = Structure::new(&derive_input);
let result = call(s);
utils::assert_proc_macro(result, expected);
}
}
78 changes: 78 additions & 0 deletions proc-macro/src/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use crate::utils;
use heck::{
CamelCase,
SnakeCase,
};
use proc_macro2::TokenStream;
use quote::{
format_ident,
quote,
};
use synstructure::Structure;

pub fn event(s: Structure) -> TokenStream {
let subxt = utils::use_crate("substrate-subxt");
let codec = utils::use_crate("parity-scale-codec");
let ident = &s.ast().ident;
let generics = &s.ast().generics;
let module = utils::module_name(generics);
let event_name = ident.to_string().trim_end_matches("Event").to_camel_case();
let event = format_ident!("{}", event_name.to_snake_case());
let event_trait = format_ident!("{}EventExt", event_name);

let expanded = quote! {
impl<T: #module> #subxt::Event<T> for #ident<T> {
const MODULE: &'static str = MODULE;
const EVENT: &'static str = #event_name;
}

pub trait #event_trait<T: #module> {
fn #event(&self) -> Result<Option<#ident<T>>, #codec::Error>;
}

impl<T: #module> #event_trait<T> for #subxt::ExtrinsicSuccess<T> {
fn #event(&self) -> Result<Option<#ident<T>>, #codec::Error> {
self.find_event()
}
}
};

TokenStream::from(expanded)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_transfer_event() {
let input = quote! {
#[derive(Debug, Decode, Eq, Event, PartialEq)]
pub struct TransferEvent<T: Balances> {
pub from: <T as System>::AccountId,
pub to: <T as System>::AccountId,
pub amount: T::Balance,
}
};
let expected = quote! {
impl<T: Balances> substrate_subxt::Event<T> for TransferEvent<T> {
const MODULE: &'static str = MODULE;
const EVENT: &'static str = "Transfer";
}

pub trait TransferEventExt<T: Balances> {
fn transfer(&self) -> Result<Option<TransferEvent<T>>, codec::Error>;
}

impl<T: Balances> TransferEventExt<T> for substrate_subxt::ExtrinsicSuccess<T> {
fn transfer(&self) -> Result<Option<TransferEvent<T>>, codec::Error> {
self.find_event()
}
}
};
let derive_input = syn::parse2(input).unwrap();
let s = Structure::new(&derive_input);
let result = event(s);
utils::assert_proc_macro(result, expected);
}
}
Loading