diff --git a/Cargo.toml b/Cargo.toml index 8b2620ab7d..382913c676 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,8 +35,9 @@ pallet-indices = { version = "2.0.0-alpha.7", package = "pallet-indices" } hex = "0.4.0" sp-rpc = { version = "2.0.0-alpha.7", package = "sp-rpc" } sp-core = { version = "2.0.0-alpha.7", package = "sp-core" } -sp-transaction-pool = { version = "2.0.0-alpha.7", package = "sp-transaction-pool" } sc-rpc-api = { version = "0.8.0-alpha.7", package = "sc-rpc-api" } +sp-transaction-pool = { version = "2.0.0-alpha.7", package = "sp-transaction-pool" } +substrate-subxt-proc-macro = { path = "proc-macro" } [dev-dependencies] async-std = { version = "1.5.0", features = ["attributes"] } diff --git a/examples/fetch_remote.rs b/examples/fetch_remote.rs index 64dbac1097..e2c0af06f1 100644 --- a/examples/fetch_remote.rs +++ b/examples/fetch_remote.rs @@ -15,31 +15,28 @@ // along with substrate-subxt. If not, see . use substrate_subxt::{ - system::System, - Error, + ClientBuilder, KusamaRuntime, }; -fn main() { - async_std::task::block_on(async move { - env_logger::init(); +#[async_std::main] +async fn main() -> Result<(), Box> { + env_logger::init(); - let block_hash = fetch_block_hash(1).await; - match block_hash { - Ok(Some(hash)) => println!("Block hash for block number 1: {}", hash), - Ok(None) => println!("Block number 1 not found."), - Err(_) => eprintln!("Failed to fetch block hash"), - } - }); -} - -async fn fetch_block_hash( - block_number: u32, -) -> Result::Hash>, Error> { - substrate_subxt::ClientBuilder::::new() + let client = ClientBuilder::::new() .set_url("wss://kusama-rpc.polkadot.io") .build() - .await? - .block_hash(Some(block_number.into())) - .await + .await?; + + let block_number = 1; + + let block_hash = client.block_hash(Some(block_number.into())).await?; + + if let Some(hash) = block_hash { + println!("Block hash for block number {}: {}", block_number, hash); + } else { + println!("Block number {} not found.", block_number); + } + + Ok(()) } diff --git a/examples/kusama_balance_transfer.rs b/examples/kusama_balance_transfer.rs index 78076b7b87..646edf4e7f 100644 --- a/examples/kusama_balance_transfer.rs +++ b/examples/kusama_balance_transfer.rs @@ -16,36 +16,27 @@ use sp_keyring::AccountKeyring; use substrate_subxt::{ - balances, - Error, + balances::*, + ClientBuilder, KusamaRuntime, }; -fn main() { - async_std::task::block_on(async move { - env_logger::init(); +#[async_std::main] +async fn main() -> Result<(), Box> { + env_logger::init(); - let xt_result = transfer_balance().await; - match xt_result { - Ok(hash) => println!("Balance transfer extrinsic submitted: {}", hash), - Err(_) => eprintln!("Balance transfer extrinisic failed"), - } - }); -} - -async fn transfer_balance() -> Result { let signer = AccountKeyring::Alice.pair(); let dest = AccountKeyring::Bob.to_account_id().into(); - // note use of `KusamaRuntime` - substrate_subxt::ClientBuilder::::new() - .build() - .await? + let client = ClientBuilder::::new().build().await?; + + let hash = client .xt(signer, None) .await? - .submit(balances::TransferCall { - to: &dest, - amount: 10_000, - }) - .await + .transfer(&dest, 10_000) + .await?; + + println!("Balance transfer extrinsic submitted: {}", hash); + + Ok(()) } diff --git a/examples/submit_and_watch.rs b/examples/submit_and_watch.rs index b587c97f00..ede981bc29 100644 --- a/examples/submit_and_watch.rs +++ b/examples/submit_and_watch.rs @@ -16,8 +16,9 @@ use sp_keyring::AccountKeyring; use substrate_subxt::{ - balances, - DefaultNodeRuntime as Runtime, + balances::*, + ClientBuilder, + DefaultNodeRuntime, }; #[async_std::main] @@ -27,18 +28,15 @@ async fn main() -> Result<(), Box> { let signer = AccountKeyring::Alice.pair(); let dest = AccountKeyring::Bob.to_account_id().into(); - let cli = substrate_subxt::ClientBuilder::::new() - .build() - .await?; - let xt = cli.xt(signer, None).await?; - let xt_result = xt + let client = ClientBuilder::::new().build().await?; + let result = client + .xt(signer, None) + .await? .watch() - .submit(balances::TransferCall { - to: &dest, - amount: 10_000, - }) + .transfer(&dest, 10_000) .await?; - if let Some(event) = xt_result.find_event::>()? { + + if let Some(event) = result.transfer()? { println!("Balance transfer success: value: {:?}", event.amount); } else { println!("Failed to find Balances::Transfer Event"); diff --git a/proc-macro/Cargo.toml b/proc-macro/Cargo.toml index 54e2666672..eba9fb63e8 100644 --- a/proc-macro/Cargo.toml +++ b/proc-macro/Cargo.toml @@ -18,13 +18,10 @@ 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"] } +codec = { package = "parity-scale-codec", version = "1.2.0", features = ["derive"] } env_logger = "0.7.1" -frame-support = "2.0.0-alpha.7" pretty_assertions = "0.6.1" -sp-core = "2.0.0-alpha.7" sp-keyring = "2.0.0-alpha.7" -sp-runtime = "2.0.0-alpha.7" substrate-subxt = { path = ".." } trybuild = "1.0.25" diff --git a/proc-macro/src/call.rs b/proc-macro/src/call.rs index 7c44772aaf..9ccb419b87 100644 --- a/proc-macro/src/call.rs +++ b/proc-macro/src/call.rs @@ -28,9 +28,6 @@ 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); @@ -39,23 +36,35 @@ pub fn call(s: Structure) -> TokenStream { "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 call_name = utils::ident_to_name(ident, "Call").to_snake_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); + let fields = utils::fields(&bindings); + let marker = utils::marker_field(&fields).unwrap_or_else(|| format_ident!("_")); + let filtered_fields = utils::filter_fields(&fields, &marker); + let args = utils::fields_to_args(&filtered_fields); + let build_struct = utils::build_struct(ident, &fields); + let xt_builder = generate_trait( + &module, + &call_name, + "XtBuilder", + quote!(&'a self), + quote!(T::Hash), + &args, + &build_struct, + &marker, + ); + let events_subscriber = generate_trait( + &module, + &call_name, + "EventsSubscriber", + quote!(self), + quote!(#subxt::ExtrinsicSuccess), + &args, + &build_struct, + &marker, + ); - let expanded = quote! { + quote! { impl#generics #subxt::Call for #ident<#(#params),*> { const MODULE: &'static str = MODULE; const FUNCTION: &'static str = #call_name; @@ -67,32 +76,55 @@ pub fn call(s: Structure) -> TokenStream { } } + #xt_builder + + #events_subscriber + } +} + +pub fn generate_trait( + module: &syn::Path, + call: &str, + ty: &str, + me: TokenStream, + ret: TokenStream, + args: &TokenStream, + build_struct: &TokenStream, + marker: &syn::Ident, +) -> TokenStream { + let subxt = utils::use_crate("substrate-subxt"); + let codec = utils::use_crate("parity-scale-codec"); + let call_trait = format_ident!("{}Call{}", call.to_camel_case(), ty); + let call = format_ident!("{}", call); + let ty = format_ident!("{}", ty); + quote! { + /// Call extension trait. pub trait #call_trait { + /// Create and submit the extrinsic. fn #call<'a>( - self, + #me, #args ) -> core::pin::Pin> + Send + 'a>>; } - impl #call_trait for #subxt::EventsSubscriber + impl #call_trait for #subxt::#ty where - T: #module + #subxt::system::System + Send + Sync, - P: #sp_core::Pair, - S: #sp_runtime::traits::Verify + #codec::Codec + From + Send + 'static, - S::Signer: From + #sp_runtime::traits::IdentifyAccount, + T: #module + #subxt::system::System + Send + Sync + 'static, + P: #subxt::sp_core::Pair, + S: #subxt::sp_runtime::traits::Verify + #codec::Codec + From + Send + 'static, + S::Signer: From + #subxt::sp_runtime::traits::IdentifyAccount, T::Address: From, - E: #subxt::SignedExtra + #sp_runtime::traits::SignedExtension + 'static, + E: #subxt::SignedExtra + #subxt::sp_runtime::traits::SignedExtension + 'static, { fn #call<'a>( - self, + #me, #args ) -> core::pin::Pin> + Send + 'a>> { - Box::pin(self.submit(#ident { #(#fields)* })) + let #marker = core::marker::PhantomData::; + Box::pin(self.submit(#build_struct)) } } - }; - - TokenStream::from(expanded) + } } #[cfg(test)] @@ -121,7 +153,39 @@ mod tests { } } - pub trait TransferCallExt { + /// Call extension trait. + pub trait TransferCallXtBuilder { + /// Create and submit the extrinsic. + fn transfer<'a>( + &'a self, + to: &'a ::Address, + amount: T::Balance, + ) -> core::pin::Pin> + Send + 'a>>; + } + + impl TransferCallXtBuilder for substrate_subxt::XtBuilder + where + T: Balances + substrate_subxt::system::System + Send + Sync + 'static, + P: substrate_subxt::sp_core::Pair, + S: substrate_subxt::sp_runtime::traits::Verify + codec::Codec + From + Send + 'static, + S::Signer: From + substrate_subxt::sp_runtime::traits::IdentifyAccount< + AccountId = T::AccountId>, + T::Address: From, + E: substrate_subxt::SignedExtra + substrate_subxt::sp_runtime::traits::SignedExtension + 'static, + { + fn transfer<'a>( + &'a self, + to: &'a ::Address, + amount: T::Balance, + ) -> core::pin::Pin> + Send + 'a>> { + let _ = core::marker::PhantomData::; + Box::pin(self.submit(TransferCall { to, amount, })) + } + } + + /// Call extension trait. + pub trait TransferCallEventsSubscriber { + /// Create and submit the extrinsic. fn transfer<'a>( self, to: &'a ::Address, @@ -129,21 +193,22 @@ mod tests { ) -> core::pin::Pin, substrate_subxt::Error>> + Send + 'a>>; } - impl TransferCallExt for substrate_subxt::EventsSubscriber + impl TransferCallEventsSubscriber for substrate_subxt::EventsSubscriber where - T: Balances + substrate_subxt::system::System + Send + Sync, - P: sp_core::Pair, - S: sp_runtime::traits::Verify + codec::Codec + From + Send + 'static, - S::Signer: From + sp_runtime::traits::IdentifyAccount< + T: Balances + substrate_subxt::system::System + Send + Sync + 'static, + P: substrate_subxt::sp_core::Pair, + S: substrate_subxt::sp_runtime::traits::Verify + codec::Codec + From + Send + 'static, + S::Signer: From + substrate_subxt::sp_runtime::traits::IdentifyAccount< AccountId = T::AccountId>, T::Address: From, - E: substrate_subxt::SignedExtra + sp_runtime::traits::SignedExtension + 'static, + E: substrate_subxt::SignedExtra + substrate_subxt::sp_runtime::traits::SignedExtension + 'static, { fn transfer<'a>( self, to: &'a ::Address, amount: T::Balance, ) -> core::pin::Pin, substrate_subxt::Error>> + Send + 'a>> { + let _ = core::marker::PhantomData::; Box::pin(self.submit(TransferCall { to, amount, })) } } diff --git a/proc-macro/src/event.rs b/proc-macro/src/event.rs index ce0c8abb15..a57df0d15f 100644 --- a/proc-macro/src/event.rs +++ b/proc-macro/src/event.rs @@ -32,7 +32,7 @@ pub fn event(s: Structure) -> TokenStream { 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_name = utils::ident_to_name(ident, "Event").to_camel_case(); let event = format_ident!("{}", event_name.to_snake_case()); let event_trait = format_ident!("{}EventExt", event_name); @@ -42,7 +42,9 @@ pub fn event(s: Structure) -> TokenStream { const EVENT: &'static str = #event_name; } + /// Event extension trait. pub trait #event_trait { + /// Retrieves the event. fn #event(&self) -> Result>, #codec::Error>; } @@ -76,7 +78,9 @@ mod tests { const EVENT: &'static str = "Transfer"; } + /// Event extension trait. pub trait TransferEventExt { + /// Retrieves the event. fn transfer(&self) -> Result>, codec::Error>; } diff --git a/proc-macro/src/module.rs b/proc-macro/src/module.rs index 69bee65f3a..55e0a01a56 100644 --- a/proc-macro/src/module.rs +++ b/proc-macro/src/module.rs @@ -21,6 +21,43 @@ use quote::{ format_ident, quote, }; +use syn::parse::{ + Parse, + ParseStream, +}; + +mod kw { + use syn::custom_keyword; + + custom_keyword!(ignore); +} + +#[derive(Debug)] +enum ModuleAttr { + Ignore(kw::ignore), +} + +impl Parse for ModuleAttr { + fn parse(input: ParseStream) -> syn::Result { + Ok(Self::Ignore(input.parse()?)) + } +} + +type ModuleAttrs = utils::Attrs; + +fn ignore(attrs: &[syn::Attribute]) -> bool { + for attr in attrs { + if let Some(ident) = attr.path.get_ident() { + if ident.to_string() == "module" { + let attrs: ModuleAttrs = syn::parse2(attr.tokens.clone()).unwrap(); + if !attrs.attrs.is_empty() { + return true + } + } + } + } + false +} fn events_decoder_trait_name(module: &syn::Ident) -> syn::Ident { format_ident!("{}EventsDecoder", module.to_string()) @@ -30,8 +67,12 @@ fn with_module_ident(module: &syn::Ident) -> syn::Ident { format_ident!("with_{}", module.to_string().to_snake_case()) } -pub fn module(_args: TokenStream, input: TokenStream) -> TokenStream { - let input: syn::ItemTrait = syn::parse2(input).unwrap(); +pub fn module(_args: TokenStream, tokens: TokenStream) -> TokenStream { + let input: Result = syn::parse2(tokens.clone()); + if input.is_err() { + return tokens + } + let input = input.unwrap(); let subxt = utils::use_crate("substrate-subxt"); let module = &input.ident; @@ -52,6 +93,9 @@ pub fn module(_args: TokenStream, input: TokenStream) -> TokenStream { }); let types = input.items.iter().filter_map(|item| { if let syn::TraitItem::Type(ty) = item { + if ignore(&ty.attrs) { + return None + } let ident = &ty.ident; let ident_str = ident.to_string(); Some(quote! { @@ -67,7 +111,9 @@ pub fn module(_args: TokenStream, input: TokenStream) -> TokenStream { const MODULE: &str = #module_name; + /// `EventsDecoder` extension trait. pub trait #module_events_decoder { + /// Registers this modules types. fn #with_module(&mut self) -> Result<(), #subxt::EventsError>; } @@ -118,7 +164,9 @@ mod tests { const MODULE: &str = "Balances"; + /// `EventsDecoder` extension trait. pub trait BalancesEventsDecoder { + /// Registers this modules types. fn with_balances(&mut self) -> Result<(), substrate_subxt::EventsError>; } diff --git a/proc-macro/src/store.rs b/proc-macro/src/store.rs index d5444f5357..cb5e2cc79e 100644 --- a/proc-macro/src/store.rs +++ b/proc-macro/src/store.rs @@ -15,75 +15,63 @@ // along with substrate-subxt. If not, see . use crate::utils; -use heck::SnakeCase; -use proc_macro2::{ - TokenStream, - TokenTree, +use heck::{ + CamelCase, + SnakeCase, }; +use proc_macro2::TokenStream; use quote::{ format_ident, quote, }; -use syn::{ - parse::{ - Parse, - ParseStream, - }, - Token, +use syn::parse::{ + Parse, + ParseStream, }; use synstructure::Structure; -struct Returns { - returns: syn::Ident, - _eq: Token![=], - ty: syn::Type, +mod kw { + use syn::custom_keyword; + + custom_keyword!(returns); +} + +#[derive(Debug)] +enum StoreAttr { + Returns(utils::Attr), } -impl Parse for Returns { +impl Parse for StoreAttr { fn parse(input: ParseStream) -> syn::Result { - Ok(Returns { - returns: input.parse()?, - _eq: input.parse()?, - ty: input.parse()?, - }) + Ok(Self::Returns(input.parse()?)) } } +type StoreAttrs = utils::Attrs; + fn parse_returns_attr(attr: &syn::Attribute) -> Option { - if let TokenTree::Group(group) = attr.tokens.clone().into_iter().next().unwrap() { - if let Ok(Returns { returns, ty, .. }) = syn::parse2(group.stream()) { - if returns.to_string() == "returns" { - return Some(ty) - } - } - } - None + let attrs: StoreAttrs = syn::parse2(attr.tokens.clone()).unwrap(); + attrs.attrs.into_iter().next().map(|attr| { + let StoreAttr::Returns(attr) = attr; + attr.value + }) } pub fn store(s: Structure) -> TokenStream { let subxt = utils::use_crate("substrate-subxt"); - let sp_core = utils::use_crate("sp-core"); let ident = &s.ast().ident; let generics = &s.ast().generics; let params = utils::type_params(generics); let module = utils::module_name(generics); - let store_name = ident.to_string().trim_end_matches("Store").to_string(); + let store_name = utils::ident_to_name(ident, "Store").to_camel_case(); let store = format_ident!("{}", store_name.to_snake_case()); let store_trait = format_ident!("{}StoreExt", store_name); let bindings = utils::bindings(&s); - let fields = bindings - .iter() - .enumerate() - .map(|(i, bi)| { - ( - bi.ast() - .ident - .clone() - .unwrap_or_else(|| format_ident!("key{}", i)), - bi.ast().ty.clone(), - ) - }) - .collect::>(); + let fields = utils::fields(&bindings); + let marker = utils::marker_field(&fields).unwrap_or_else(|| format_ident!("_")); + let filtered_fields = utils::filter_fields(&fields, &marker); + let args = utils::fields_to_args(&filtered_fields); + let build_struct = utils::build_struct(ident, &fields); let ret = bindings .iter() .filter_map(|bi| bi.ast().attrs.iter().filter_map(parse_returns_attr).next()) @@ -91,19 +79,16 @@ pub fn store(s: Structure) -> TokenStream { .expect("#[store(returns = ..)] needs to be specified."); let store_ty = format_ident!( "{}", - match fields.len() { + match filtered_fields.len() { 0 => "plain", 1 => "map", 2 => "double_map", _ => panic!("invalid number of arguments"), } ); - let args = fields.iter().map(|(field, ty)| quote!(#field: #ty,)); - let args = quote!(#(#args)*); - let keys = fields.iter().map(|(field, _)| quote!(&self.#field,)); - let keys = quote!(#(#keys)*); - let fields = fields.iter().map(|(field, _)| quote!(#field,)); - let fields = quote!(#(#fields)*); + let keys = filtered_fields + .iter() + .map(|(field, _)| quote!(&self.#field)); quote! { impl#generics #subxt::Store for #ident<#(#params),*> { @@ -113,20 +98,22 @@ pub fn store(s: Structure) -> TokenStream { fn key( &self, metadata: &#subxt::Metadata, - ) -> Result<#sp_core::storage::StorageKey, #subxt::MetadataError> { + ) -> Result<#subxt::sp_core::storage::StorageKey, #subxt::MetadataError> { Ok(metadata .module(Self::MODULE)? .storage(Self::FIELD)? .#store_ty()? - .key(#keys)) + .key(#(#keys,)*)) } } + /// Store extension trait. pub trait #store_trait { + /// Retrive the store element. fn #store<'a>( &'a self, #args - ) -> core::pin::Pin, #subxt::Error>> + Send + 'a>>; + ) -> core::pin::Pin> + Send + 'a>>; } impl #store_trait for #subxt::Client @@ -138,8 +125,9 @@ pub fn store(s: Structure) -> TokenStream { fn #store<'a>( &'a self, #args - ) -> core::pin::Pin, #subxt::Error>> + Send + 'a>> { - Box::pin(self.fetch(#ident { #fields }, None)) + ) -> core::pin::Pin> + Send + 'a>> { + let #marker = core::marker::PhantomData::; + Box::pin(self.fetch(#build_struct, None)) } } } @@ -166,7 +154,7 @@ mod tests { fn key( &self, metadata: &substrate_subxt::Metadata, - ) -> Result { + ) -> Result { Ok(metadata .module(Self::MODULE)? .storage(Self::FIELD)? @@ -175,11 +163,13 @@ mod tests { } } + /// Store extension trait. pub trait AccountStoreExt { + /// Retrive the store element. fn account<'a>( &'a self, account_id: &'a ::AccountId, - ) -> core::pin::Pin >, substrate_subxt::Error>> + Send + 'a>>; + ) -> core::pin::Pin, substrate_subxt::Error>> + Send + 'a>>; } impl AccountStoreExt for substrate_subxt::Client @@ -191,8 +181,9 @@ mod tests { fn account<'a>( &'a self, account_id: &'a ::AccountId, - ) -> core::pin::Pin >, substrate_subxt::Error>> + Send + 'a>> + ) -> core::pin::Pin, substrate_subxt::Error>> + Send + 'a>> { + let _ = core::marker::PhantomData::; Box::pin(self.fetch(AccountStore { account_id, }, None)) } } diff --git a/proc-macro/src/test.rs b/proc-macro/src/test.rs index b3c2886001..5a8f7a29d1 100644 --- a/proc-macro/src/test.rs +++ b/proc-macro/src/test.rs @@ -36,6 +36,7 @@ mod kw { custom_keyword!(account); custom_keyword!(signature); custom_keyword!(extra); + custom_keyword!(prelude); custom_keyword!(step); custom_keyword!(state); custom_keyword!(call); @@ -86,6 +87,7 @@ enum TestItem { Signature(Item), Extra(Item), State(Item), + Prelude(Item), Step(Item), } @@ -103,6 +105,8 @@ impl Parse for TestItem { Ok(TestItem::Extra(input.parse()?)) } else if input.peek(kw::state) { Ok(TestItem::State(input.parse()?)) + } else if input.peek(kw::prelude) { + Ok(TestItem::Prelude(input.parse()?)) } else { Ok(TestItem::Step(input.parse()?)) } @@ -116,7 +120,7 @@ enum StepItem { State(Item), Call(Item), Event(Item), - Assert(Item), + Assert(Item), } impl Parse for StepItem { @@ -143,6 +147,7 @@ struct Test { signature: syn::Type, extra: syn::Type, state: Option, + prelude: Option, steps: Vec, } @@ -154,6 +159,7 @@ impl From for Test { let mut signature = None; let mut extra = None; let mut state = None; + let mut prelude = None; let mut steps = vec![]; for test_item in test.items { match test_item { @@ -175,28 +181,29 @@ impl From for Test { TestItem::State(item) => { state = Some(item.value.into()); } + TestItem::Prelude(item) => { + prelude = Some(item.value); + } TestItem::Step(item) => { steps.push(item.value.into()); } } } - let runtime = runtime.unwrap_or_else(|| { - let subxt = utils::use_crate("substrate-subxt"); - syn::parse2(quote!(#subxt::DefaultNodeRuntime)).unwrap() - }); + let subxt = utils::use_crate("substrate-subxt"); + let runtime = runtime + .unwrap_or_else(|| syn::parse2(quote!(#subxt::DefaultNodeRuntime)).unwrap()); Self { name: name.expect("No name specified"), account: account.unwrap_or_else(|| format_ident!("Alice")), signature: signature.unwrap_or_else(|| { - let sp_runtime = utils::use_crate("sp-runtime"); - syn::parse2(quote!(#sp_runtime::MultiSignature)).unwrap() + syn::parse2(quote!(#subxt::sp_runtime::MultiSignature)).unwrap() }), extra: extra.unwrap_or_else(|| { - let subxt = utils::use_crate("substrate-subxt"); syn::parse2(quote!(#subxt::DefaultExtra<#runtime>)).unwrap() }), runtime, state, + prelude, steps, } } @@ -204,9 +211,10 @@ impl From for Test { impl Test { fn into_tokens(self) -> TokenStream { - let env_logger = utils::use_crate("env_logger"); - let sp_keyring = utils::use_crate("sp-keyring"); let subxt = utils::use_crate("substrate-subxt"); + let sp_keyring = utils::use_crate("sp-keyring"); + let env_logger = utils::opt_crate("env_logger") + .map(|env_logger| quote!(#env_logger::try_init().ok();)); let Test { name, runtime, @@ -214,8 +222,10 @@ impl Test { signature, extra, state, + prelude, steps, } = self; + let prelude = prelude.map(|block| block.stmts).unwrap_or_default(); let step = steps .into_iter() .map(|step| step.into_tokens(&account, state.as_ref())); @@ -223,7 +233,7 @@ impl Test { #[async_std::test] #[ignore] async fn #name() { - #env_logger::try_init().ok(); + #env_logger let client = #subxt::ClientBuilder::<#runtime, #signature, #extra>::new() .build().await.unwrap(); #[allow(unused)] @@ -239,6 +249,8 @@ impl Test { #[allow(unused)] let ferdie = #sp_keyring::AccountKeyring::Ferdie.to_account_id(); + #(#prelude)* + #({ #step })* @@ -252,7 +264,7 @@ struct Step { call: syn::Expr, event_name: Vec, event: Vec, - assert: syn::Expr, + assert: Option, } impl From for Step { @@ -286,7 +298,7 @@ impl From for Step { call: call.expect("Step requires a call."), event_name, event, - assert: assert.expect("Step requires assert."), + assert, } } } @@ -305,27 +317,51 @@ impl Step { event, assert, } = self; - let state = state + let (pre, post) = state .as_ref() - .unwrap_or_else(|| test_state.expect("No state for step")); - let State { - state_name, - state, - state_param, - } = state; + .or(test_state) + .map(|state| { + let State { + state_name, + state, + state_param, + } = state; + let state_struct = quote! { + struct State<#(#state_param),*> { + #(#state_name: #state_param,)* + } + }; + let build_struct = quote! { + #( + let #state_name = client.fetch(#state, None).await.unwrap(); + )* + State { #(#state_name),* } + }; + let pre = quote! { + #state_struct + let pre = { + #build_struct + }; + }; + let post = quote! { + let post = { + #build_struct + }; + }; + (pre, post) + }) + .unwrap_or_default(); + let expect_event = event_name.iter().map(|event| { + format!( + "failed to find event {}", + utils::path_to_ident(event).to_string() + ) + }); + let assert = assert.map(|block| block.stmts).unwrap_or_default(); quote! { let xt = client.xt(#sp_keyring::AccountKeyring::#account.pair(), None).await.unwrap(); - struct State<#(#state_param),*> { - #(#state_name: #state_param,)* - } - - let pre = { - #( - let #state_name = client.fetch(#state, None).await.unwrap().unwrap(); - )* - State { #(#state_name),* } - }; + #pre #[allow(unused)] let result = xt @@ -335,20 +371,13 @@ impl Step { .unwrap(); #( - assert_eq!( - result.find_event::<#event_name<_>>().unwrap(), - Some(#event) - ); + let event = result.find_event::<#event_name<_>>().unwrap().expect(#expect_event); + assert_eq!(event, #event); )* - let post = { - #( - let #state_name = client.fetch(#state, None).await.unwrap().unwrap(); - )* - State { #(#state_name),* } - }; + #post - #assert + #(#assert)* } } } @@ -429,7 +458,7 @@ mod tests { env_logger::try_init().ok(); let client = substrate_subxt::ClientBuilder::< KusamaRuntime, - sp_runtime::MultiSignature, + substrate_subxt::sp_runtime::MultiSignature, substrate_subxt::DefaultExtra >::new().build().await.unwrap(); #[allow(unused)] @@ -457,12 +486,10 @@ mod tests { let alice = client .fetch(AccountStore { account_id: &alice }, None) .await - .unwrap() .unwrap(); let bob = client .fetch(AccountStore { account_id: &bob }, None) .await - .unwrap() .unwrap(); State { alice, bob } }; @@ -477,33 +504,32 @@ mod tests { .await .unwrap(); + let event = result.find_event::>() + .unwrap() + .expect("failed to find event TransferEvent"); assert_eq!( - result.find_event::>().unwrap(), - Some(TransferEvent { + event, + TransferEvent { from: alice.clone(), to: bob.clone(), amount: 10_000, - }) + } ); let post = { let alice = client .fetch(AccountStore { account_id: &alice }, None) .await - .unwrap() .unwrap(); let bob = client .fetch(AccountStore { account_id: &bob }, None) .await - .unwrap() .unwrap(); State { alice, bob } }; - { - assert_eq!(pre.alice.free, post.alice.free - 10_000); - assert_eq!(pre.bob.free, post.bob.free + 10_000); - } + assert_eq!(pre.alice.free, post.alice.free - 10_000); + assert_eq!(pre.bob.free, post.bob.free + 10_000); } } }; diff --git a/proc-macro/src/utils.rs b/proc-macro/src/utils.rs index b8c0ce361e..8d74cde62a 100644 --- a/proc-macro/src/utils.rs +++ b/proc-macro/src/utils.rs @@ -18,15 +18,30 @@ use proc_macro2::{ Span, TokenStream, }; -use quote::quote; +use quote::{ + format_ident, + quote, +}; +use syn::{ + parse::{ + Parse, + ParseStream, + }, + punctuated::Punctuated, +}; use synstructure::{ BindingInfo, Structure, }; pub fn use_crate(name: &str) -> syn::Ident { - let krate = proc_macro_crate::crate_name(name).unwrap(); - syn::Ident::new(&krate, Span::call_site()) + opt_crate(name).unwrap_or_else(|| syn::Ident::new("crate", Span::call_site())) +} + +pub fn opt_crate(name: &str) -> Option { + proc_macro_crate::crate_name(name) + .ok() + .map(|krate| syn::Ident::new(&krate, Span::call_site())) } pub fn bindings<'a>(s: &'a Structure) -> Vec<&'a BindingInfo<'a>> { @@ -39,6 +54,71 @@ pub fn bindings<'a>(s: &'a Structure) -> Vec<&'a BindingInfo<'a>> { bindings } +type Field = (syn::Ident, syn::Type); + +pub fn fields<'a>(bindings: &'a [&'a BindingInfo<'a>]) -> Vec { + bindings + .iter() + .enumerate() + .map(|(i, bi)| { + ( + bi.ast() + .ident + .clone() + .unwrap_or_else(|| format_ident!("key{}", i)), + bi.ast().ty.clone(), + ) + }) + .collect() +} + +pub fn marker_field<'a>(fields: &'a [Field]) -> Option { + fields + .iter() + .filter_map(|(field, ty)| { + if quote!(#ty).to_string() == quote!(PhantomData).to_string() { + Some(field) + } else { + None + } + }) + .next() + .cloned() +} + +pub fn filter_fields<'a>(fields: &'a [Field], field: &'a syn::Ident) -> Vec { + fields + .iter() + .filter_map(|(field2, ty)| { + if field2 != field { + Some((field2.clone(), ty.clone())) + } else { + None + } + }) + .collect() +} + +pub fn fields_to_args<'a>(fields: &'a [Field]) -> TokenStream { + let args = fields.iter().map(|(field, ty)| quote!(#field: #ty,)); + quote!(#(#args)*) +} + +pub fn build_struct<'a>(ident: &'a syn::Ident, fields: &'a [Field]) -> TokenStream { + let fields = fields.iter().map(|(field, _)| field); + quote!(#ident { #(#fields,)* }) +} + +pub fn ident_to_name(ident: &syn::Ident, ty: &str) -> String { + let name = ident.to_string(); + let name = name.trim_end_matches(ty); + if name.is_empty() { + ty.to_string() + } else { + name.to_string() + } +} + pub fn module_name(generics: &syn::Generics) -> &syn::Path { generics .params @@ -87,6 +167,39 @@ pub fn type_params(generics: &syn::Generics) -> Vec { .collect() } +#[derive(Debug)] +pub struct Attrs { + pub paren: syn::token::Paren, + pub attrs: Punctuated, +} + +impl Parse for Attrs { + fn parse(input: ParseStream) -> syn::Result { + let content; + Ok(Self { + paren: syn::parenthesized!(content in input), + attrs: content.parse_terminated(A::parse)?, + }) + } +} + +#[derive(Debug)] +pub struct Attr { + pub key: K, + pub eq: syn::token::Eq, + pub value: V, +} + +impl Parse for Attr { + fn parse(input: ParseStream) -> syn::Result { + Ok(Self { + key: input.parse()?, + eq: input.parse()?, + value: input.parse()?, + }) + } +} + #[cfg(test)] pub(crate) fn assert_proc_macro( result: proc_macro2::TokenStream, diff --git a/proc-macro/tests/balances.rs b/proc-macro/tests/balances.rs index 3eb3321fe1..a600277c16 100644 --- a/proc-macro/tests/balances.rs +++ b/proc-macro/tests/balances.rs @@ -14,47 +14,35 @@ // You should have received a copy of the GNU General Public License // along with substrate-subxt. If not, see . +#[macro_use] +extern crate substrate_subxt; + use codec::{ + Codec, Decode, Encode, }; -use frame_support::Parameter; use sp_keyring::AccountKeyring; -use sp_runtime::traits::{ - AtLeast32Bit, - MaybeSerialize, - Member, -}; use std::fmt::Debug; use substrate_subxt::{ - system::System, + sp_runtime::traits::{ + AtLeast32Bit, + MaybeSerialize, + Member, + }, + system::{ + System, + SystemEventsDecoder, + }, ClientBuilder, KusamaRuntime, }; -use substrate_subxt_proc_macro::{ - module, - subxt_test, - Call, - Event, - Store, -}; - -pub trait SystemEventsDecoder { - fn with_system(&mut self) -> Result<(), substrate_subxt::EventsError>; -} - -impl SystemEventsDecoder for substrate_subxt::EventsDecoder { - fn with_system(&mut self) -> Result<(), substrate_subxt::EventsError> { - Ok(()) - } -} #[module] pub trait Balances: System { - type Balance: Parameter - + Member + type Balance: Member + AtLeast32Bit - + codec::Codec + + Codec + Default + Copy + MaybeSerialize @@ -127,13 +115,15 @@ async fn transfer_balance_example() -> Result<(), Box> { let alice = AccountKeyring::Alice.to_account_id(); let bob = AccountKeyring::Bob.to_account_id(); - let alice_account = client.account(&alice).await?.unwrap_or_default(); - let bob_account = client.account(&bob).await?.unwrap_or_default(); + let alice_account = client.account(&alice).await?; + let bob_account = client.account(&bob).await?; let pre = (alice_account, bob_account); - let result = client - .xt(AccountKeyring::Alice.pair(), None) - .await? + let builder = client.xt(AccountKeyring::Alice.pair(), None).await?; + + let _hash = builder.transfer(&bob.clone().into(), 10_000).await?; + + let result = builder .watch() .transfer(&bob.clone().into(), 10_000) .await?; @@ -147,8 +137,8 @@ async fn transfer_balance_example() -> Result<(), Box> { }) ); - let alice_account = client.account(&alice).await?.unwrap_or_default(); - let bob_account = client.account(&bob).await?.unwrap_or_default(); + let alice_account = client.account(&alice).await?; + let bob_account = client.account(&bob).await?; let post = (alice_account, bob_account); assert_eq!(pre.0.free, post.0.free - 10_000); diff --git a/src/events.rs b/src/events.rs index c2cfb10df0..801641d2a6 100644 --- a/src/events.rs +++ b/src/events.rs @@ -14,6 +14,15 @@ // You should have received a copy of the GNU General Public License // along with substrate-subxt. If not, see . +use codec::{ + Codec, + Compact, + Decode, + Encode, + Error as CodecError, + Input, + Output, +}; use std::{ collections::{ HashMap, @@ -25,16 +34,7 @@ use std::{ Send, }, }; - -use codec::{ - Codec, - Compact, - Decode, - Encode, - Error as CodecError, - Input, - Output, -}; +use thiserror::Error; use crate::{ metadata::{ @@ -66,7 +66,7 @@ pub struct RawEvent { } /// Events error. -#[derive(Debug, thiserror::Error)] +#[derive(Debug, Error)] pub enum EventsError { /// Codec error. #[error("Scale codec error: {0:?}")] @@ -80,6 +80,7 @@ pub enum EventsError { } /// Event decoder. +#[derive(Debug)] pub struct EventsDecoder { metadata: Metadata, type_sizes: HashMap, @@ -95,10 +96,6 @@ impl TryFrom for EventsDecoder { type_sizes: HashMap::new(), marker: PhantomData, }; - // REMOVE when https://github.com/paritytech/substrate-subxt/pull/102 is merged - // Balance type will be registered by the proc macro. - decoder.register_type_size::("Balance")?; - // register default event arg type sizes for dynamic decoding of events decoder.register_type_size::("bool")?; decoder.register_type_size::("ReferendumIndex")?; @@ -123,11 +120,6 @@ impl TryFrom for EventsDecoder { } impl EventsDecoder { - /// Register system types. - pub fn with_system(&mut self) -> Result<(), EventsError> { - Ok(()) - } - /// Register a type. pub fn register_type_size(&mut self, name: &str) -> Result where diff --git a/src/extrinsic.rs b/src/extrinsic.rs index 80aad5b8bd..8dedf70386 100644 --- a/src/extrinsic.rs +++ b/src/extrinsic.rs @@ -14,14 +14,15 @@ // You should have received a copy of the GNU General Public License // along with substrate-subxt. If not, see . -use std::marker::PhantomData; - use codec::{ Codec, Decode, Encode, }; - +use core::{ + fmt::Debug, + marker::PhantomData, +}; use sp_core::Pair; use sp_runtime::{ generic::{ @@ -52,7 +53,7 @@ use crate::frame::{ /// This is modified from the substrate version to allow passing in of the version, which is /// returned via `additional_signed()`. #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] -pub struct CheckVersion( +pub struct CheckVersion( pub PhantomData, /// Local version to be used for `AdditionalSigned` #[codec(skip)] @@ -61,7 +62,7 @@ pub struct CheckVersion( impl SignedExtension for CheckVersion where - T: System + Send + Sync, + T: System + Clone + Debug + Eq + Send + Sync, { const IDENTIFIER: &'static str = "CheckVersion"; type AccountId = u64; @@ -82,7 +83,7 @@ where /// This is modified from the substrate version to allow passing in of the genesis hash, which is /// returned via `additional_signed()`. #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] -pub struct CheckGenesis( +pub struct CheckGenesis( pub PhantomData, /// Local genesis hash to be used for `AdditionalSigned` #[codec(skip)] @@ -91,7 +92,7 @@ pub struct CheckGenesis( impl SignedExtension for CheckGenesis where - T: System + Send + Sync, + T: System + Clone + Debug + Eq + Send + Sync, { const IDENTIFIER: &'static str = "CheckGenesis"; type AccountId = u64; @@ -113,7 +114,7 @@ where /// returned via `additional_signed()`. It assumes therefore `Era::Immortal` (The transaction is /// valid forever) #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] -pub struct CheckEra( +pub struct CheckEra( /// The default structure for the Extra encoding pub (Era, PhantomData), /// Local genesis hash to be used for `AdditionalSigned` @@ -123,7 +124,7 @@ pub struct CheckEra( impl SignedExtension for CheckEra where - T: System + Send + Sync, + T: System + Clone + Debug + Eq + Send + Sync, { const IDENTIFIER: &'static str = "CheckEra"; type AccountId = u64; @@ -139,11 +140,11 @@ where /// Nonce check and increment to give replay protection for transactions. #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] -pub struct CheckNonce(#[codec(compact)] pub T::Index); +pub struct CheckNonce(#[codec(compact)] pub T::Index); impl SignedExtension for CheckNonce where - T: System + Send + Sync, + T: System + Clone + Debug + Eq + Send + Sync, { const IDENTIFIER: &'static str = "CheckNonce"; type AccountId = u64; @@ -159,11 +160,11 @@ where /// Resource limit check. #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] -pub struct CheckWeight(pub PhantomData); +pub struct CheckWeight(pub PhantomData); impl SignedExtension for CheckWeight where - T: System + Send + Sync, + T: System + Clone + Debug + Eq + Send + Sync, { const IDENTIFIER: &'static str = "CheckWeight"; type AccountId = u64; @@ -184,7 +185,7 @@ pub struct ChargeTransactionPayment(#[codec(compact)] pub T::Balanc impl SignedExtension for ChargeTransactionPayment where - T: Balances + Send + Sync, + T: Balances + Clone + Debug + Eq + Send + Sync, { const IDENTIFIER: &'static str = "ChargeTransactionPayment"; type AccountId = u64; @@ -200,11 +201,11 @@ where /// Checks if a transaction would exhausts the block gas limit. #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] -pub struct CheckBlockGasLimit(pub PhantomData); +pub struct CheckBlockGasLimit(pub PhantomData); impl SignedExtension for CheckBlockGasLimit where - T: System + Send + Sync, + T: System + Clone + Debug + Eq + Send + Sync, { const IDENTIFIER: &'static str = "CheckBlockGasLimit"; type AccountId = u64; @@ -238,7 +239,9 @@ pub struct DefaultExtra { genesis_hash: T::Hash, } -impl SignedExtra for DefaultExtra { +impl SignedExtra + for DefaultExtra +{ type Extra = ( CheckVersion, CheckGenesis, @@ -270,7 +273,9 @@ impl SignedExtra for DefaultExtra { } } -impl SignedExtension for DefaultExtra { +impl SignedExtension + for DefaultExtra +{ const IDENTIFIER: &'static str = "DefaultExtra"; type AccountId = T::AccountId; type Call = (); diff --git a/src/frame/balances.rs b/src/frame/balances.rs index 373a500491..02e41d9342 100644 --- a/src/frame/balances.rs +++ b/src/frame/balances.rs @@ -16,24 +16,16 @@ //! Implements support for the pallet_balances module. -use crate::{ - frame::{ - system::System, - Call, - Event, - Store, - }, - metadata::{ - Metadata, - MetadataError, - }, +use crate::frame::system::{ + System, + SystemEventsDecoder, }; use codec::{ Decode, Encode, }; +use core::marker::PhantomData; use frame_support::Parameter; -use sp_core::storage::StorageKey; use sp_runtime::traits::{ AtLeast32Bit, MaybeSerialize, @@ -41,9 +33,8 @@ use sp_runtime::traits::{ }; use std::fmt::Debug; -const MODULE: &str = "Balances"; - /// The subset of the `pallet_balances::Trait` that a client must implement. +#[module] pub trait Balances: System { /// The balance of an account. type Balance: Parameter @@ -58,7 +49,7 @@ pub trait Balances: System { } /// All balance information for an account. -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, Default)] +#[derive(Clone, Debug, Eq, PartialEq, Default, Decode, Encode)] pub struct AccountData { /// Non-reserved part of the balance. There may still be restrictions on this, but it is the /// total pool what may in principle be transferred, reserved and used for tipping. @@ -82,21 +73,11 @@ pub struct AccountData { } /// The total issuance of the balances module. -#[derive(Encode)] -pub struct TotalIssuance(pub core::marker::PhantomData); - -impl Store for TotalIssuance { - const MODULE: &'static str = MODULE; - const FIELD: &'static str = "TotalIssuance"; - type Returns = T::Balance; - - fn key(&self, metadata: &Metadata) -> Result { - Ok(metadata - .module(Self::MODULE)? - .storage(Self::FIELD)? - .plain()? - .key()) - } +#[derive(Clone, Debug, Eq, PartialEq, Store, Encode)] +pub struct TotalIssuanceStore { + #[store(returns = T::Balance)] + /// Runtime marker. + pub _runtime: PhantomData, } /// Transfer some liquid free balance to another account. @@ -105,7 +86,7 @@ impl Store for TotalIssuance { /// It will decrease the total issuance of the system by the `TransferFee`. /// If the sender's account is below the existential deposit as a result /// of the transfer, the account will be reaped. -#[derive(Encode)] +#[derive(Clone, Debug, PartialEq, Call, Encode)] pub struct TransferCall<'a, T: Balances> { /// Destination of the transfer. pub to: &'a ::Address, @@ -114,13 +95,8 @@ pub struct TransferCall<'a, T: Balances> { pub amount: T::Balance, } -impl<'a, T: Balances> Call for TransferCall<'a, T> { - const MODULE: &'static str = MODULE; - const FUNCTION: &'static str = "transfer"; -} - /// Transfer event. -#[derive(Debug, Decode, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] pub struct TransferEvent { /// Account balance was transfered from. pub from: ::AccountId, @@ -130,7 +106,57 @@ pub struct TransferEvent { pub amount: T::Balance, } -impl Event for TransferEvent { - const MODULE: &'static str = MODULE; - const EVENT: &'static str = "transfer"; +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + system::{ + AccountStore, + AccountStoreExt, + }, + tests::test_client, + }; + use sp_keyring::AccountKeyring; + + subxt_test!({ + name: test_transfer, + step: { + state: { + alice: AccountStore { account_id: &alice }, + bob: AccountStore { account_id: &bob }, + }, + call: TransferCall { + to: &bob.clone().into(), + amount: 10_000, + }, + event: TransferEvent { + from: alice.clone(), + to: bob.clone(), + amount: 10_000, + }, + assert: { + assert!(pre.alice.data.free - 10_000 >= post.alice.data.free); + assert_eq!(pre.bob.data.free + 10_000, post.bob.data.free); + }, + }, + }); + + #[async_std::test] + #[ignore] // requires locally running substrate node + async fn test_state_total_issuance() { + env_logger::try_init().ok(); + let client = test_client().await; + let total_issuance = client.total_issuance().await.unwrap(); + assert_ne!(total_issuance, 0); + } + + #[async_std::test] + #[ignore] // requires locally running substrate node + async fn test_state_read_free_balance() { + env_logger::try_init().ok(); + let client = test_client().await; + let account = AccountKeyring::Alice.to_account_id(); + let info = client.account(&account).await.unwrap(); + assert_ne!(info.data.free, 0); + } } diff --git a/src/frame/contracts.rs b/src/frame/contracts.rs index 820ee811de..0a0bc1da34 100644 --- a/src/frame/contracts.rs +++ b/src/frame/contracts.rs @@ -17,39 +17,40 @@ //! Implements support for the pallet_contracts module. use crate::frame::{ - balances::Balances, - system::System, - Call, - Event, + balances::{ + Balances, + BalancesEventsDecoder, + }, + system::{ + System, + SystemEventsDecoder, + }, }; use codec::{ Decode, Encode, }; - -const MODULE: &str = "Contracts"; +use core::marker::PhantomData; /// Gas units are chosen to be represented by u64 so that gas metering /// instructions can operate on them efficiently. pub type Gas = u64; /// The subset of the `pallet_contracts::Trait` that a client must implement. +#[module] pub trait Contracts: System + Balances {} /// Stores the given binary Wasm code into the chain's storage and returns /// its `codehash`. /// You can instantiate contracts only with stored code. -#[derive(Debug, Encode)] -pub struct PutCodeCall<'a> { +#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] +pub struct PutCodeCall<'a, T: Contracts> { + /// Runtime marker. + pub _runtime: PhantomData, /// Wasm blob. pub code: &'a [u8], } -impl<'a, T: Contracts> Call for PutCodeCall<'a> { - const MODULE: &'static str = MODULE; - const FUNCTION: &'static str = "put_code"; -} - /// Creates a new contract from the `codehash` generated by `put_code`, /// optionally transferring some balance. /// @@ -63,7 +64,7 @@ impl<'a, T: Contracts> Call for PutCodeCall<'a> { /// of the account. That code will be invoked upon any call received by /// this account. /// - The contract is initialized. -#[derive(Debug, Encode)] +#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] pub struct InstantiateCall<'a, T: Contracts> { /// Initial balance transfered to the contract. #[codec(compact)] @@ -77,11 +78,6 @@ pub struct InstantiateCall<'a, T: Contracts> { pub data: &'a [u8], } -impl<'a, T: Contracts> Call for InstantiateCall<'a, T> { - const MODULE: &'static str = MODULE; - const FUNCTION: &'static str = "instantiate"; -} - /// Makes a call to an account, optionally transferring some balance. /// /// * If the account is a smart-contract account, the associated code will @@ -90,7 +86,7 @@ impl<'a, T: Contracts> Call for InstantiateCall<'a, T> { /// * If no account exists and the call value is not less than /// `existential_deposit`, a regular account will be created and any value /// will be transferred. -#[derive(Debug, Encode)] +#[derive(Clone, Debug, PartialEq, Call, Encode)] pub struct CallCall<'a, T: Contracts> { /// Address of the contract. pub dest: &'a ::Address, @@ -103,133 +99,61 @@ pub struct CallCall<'a, T: Contracts> { pub data: &'a [u8], } -impl<'a, T: Contracts> Call for CallCall<'a, T> { - const MODULE: &'static str = MODULE; - const FUNCTION: &'static str = "call"; -} - /// Code stored event. -#[derive(Debug, Decode)] +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] pub struct CodeStoredEvent { /// Code hash of the contract. pub code_hash: T::Hash, } -impl Event for CodeStoredEvent { - const MODULE: &'static str = MODULE; - const EVENT: &'static str = "CodeStored"; -} - /// Instantiated event. -#[derive(Debug, Decode)] -pub struct InstantiatedEvent( - pub ::AccountId, - pub ::AccountId, -); - -impl Event for InstantiatedEvent { - const MODULE: &'static str = MODULE; - const EVENT: &'static str = "Instantiated"; +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] +pub struct InstantiatedEvent { + /// Caller that instantiated the contract. + pub caller: ::AccountId, + /// The address of the contract. + pub contract: ::AccountId, } #[cfg(test)] mod tests { - use codec::Codec; - use sp_core::Pair; - use sp_keyring::AccountKeyring; - use sp_runtime::traits::{ - IdentifyAccount, - Verify, - }; - use super::*; - use crate::{ - tests::test_client, - Client, - Error, - }; - async fn put_code(client: &Client, signer: P) -> Result - where - T: Contracts + Send + Sync, - T::Address: From, - P: Pair, - P::Signature: Codec, - S: Verify + Codec + From + 'static, - S::Signer: From + IdentifyAccount, - { - const CONTRACT: &str = r#" + subxt_test!({ + name: test_put_code_and_instantiate, + prelude: { + const CONTRACT: &str = r#" (module (func (export "call")) (func (export "deploy")) ) "#; - let wasm = wabt::wat2wasm(CONTRACT).expect("invalid wabt"); - - let xt = client.xt(signer, None).await?; - - let result = xt.watch().submit(PutCodeCall { code: &wasm }).await?; - let code_hash = result - .find_event::>()? - .ok_or(Error::Other("Failed to find CodeStored event".into()))? - .code_hash; - - Ok(code_hash) - } - - #[test] - #[ignore] // requires locally running substrate node - fn tx_put_code() { - env_logger::try_init().ok(); - let code_hash_result: Result<_, Error> = async_std::task::block_on(async move { - let signer = AccountKeyring::Alice.pair(); - let client = test_client().await; - let code_hash = put_code(&client, signer).await?; - Ok(code_hash) - }); - - assert!( - code_hash_result.is_ok(), - format!( - "Error calling put_code and receiving CodeStored Event: {:?}", - code_hash_result - ) - ); - } - - #[test] - #[ignore] // requires locally running substrate node - fn tx_instantiate() { - env_logger::try_init().ok(); - let result: Result<_, Error> = async_std::task::block_on(async move { - let signer = AccountKeyring::Bob.pair(); - let client = test_client().await; - - let code_hash = put_code(&client, signer.clone()).await?; - - log::info!("Code hash: {:?}", code_hash); - - let xt = client.xt(signer, None).await?; - let result = xt - .watch() - .submit(InstantiateCall { - endowment: 100_000_000_000_000, - gas_limit: 500_000_000, - code_hash: &code_hash, - data: &[], - }) - .await?; - let event = result - .find_event::>()? - .ok_or(Error::Other("Failed to find Instantiated event".into()))?; - Ok(event) - }); - - log::info!("Instantiate result: {:?}", result); - - assert!( - result.is_ok(), - format!("Error instantiating contract: {:?}", result) - ); - } + let wasm = wabt::wat2wasm(CONTRACT).expect("invalid wabt"); + let code_hash; + }, + step: { + call: PutCodeCall { + _runtime: PhantomData, + code: &wasm, + }, + event: CodeStoredEvent { + code_hash: { + code_hash = event.code_hash.clone(); + event.code_hash.clone() + }, + }, + }, + step: { + call: InstantiateCall { + endowment: 100_000_000_000_000, + gas_limit: 500_000_000, + code_hash: &code_hash, + data: &[], + }, + event: InstantiatedEvent { + caller: alice.clone(), + contract: event.contract.clone(), + }, + }, + }); } diff --git a/src/frame/mod.rs b/src/frame/mod.rs index 3fca355ee9..7244b8c35f 100644 --- a/src/frame/mod.rs +++ b/src/frame/mod.rs @@ -47,14 +47,11 @@ pub trait Store: Encode { /// Returns the `StorageKey`. fn key(&self, metadata: &Metadata) -> Result; /// Returns the default value. - fn default( - &self, - metadata: &Metadata, - ) -> Result, MetadataError> { + fn default(&self, metadata: &Metadata) -> Result { Ok(metadata .module(Self::MODULE)? .storage(Self::FIELD)? - .default()) + .default()?) } } diff --git a/src/frame/system.rs b/src/frame/system.rs index 89ab461ab0..6734d47322 100644 --- a/src/frame/system.rs +++ b/src/frame/system.rs @@ -21,9 +21,12 @@ use codec::{ Decode, Encode, }; -use frame_support::Parameter; +use core::marker::PhantomData; +use frame_support::{ + weights::DispatchInfo, + Parameter, +}; use serde::de::DeserializeOwned; -use sp_core::storage::StorageKey; use sp_runtime::{ traits::{ AtLeast32Bit, @@ -39,23 +42,13 @@ use sp_runtime::{ Member, SimpleBitOps, }, - RuntimeDebug, + DispatchError, }; use std::fmt::Debug; -use crate::{ - frame::{ - Call, - Store, - }, - metadata::{ - Metadata, - MetadataError, - }, -}; - /// The subset of the `frame::Trait` that a client must implement. -pub trait System: 'static + Eq + Clone + Debug { +#[module] +pub trait System { /// Account index (aka nonce) type. This stores the number of previous /// transactions associated with a sender account. type Index: Parameter @@ -98,6 +91,7 @@ pub trait System: 'static + Eq + Clone + Debug { + AsMut<[u8]>; /// The hashing system (algorithm) being used in the runtime (e.g. Blake2). + #[module(ignore)] type Hashing: Hash; /// The user account identifier type for the runtime. @@ -110,14 +104,17 @@ pub trait System: 'static + Eq + Clone + Debug { + Default; /// The address type. This instead of `::Source`. + #[module(ignore)] type Address: Codec + Clone + PartialEq + Debug + Send + Sync; /// The block header. + #[module(ignore)] type Header: Parameter + Header + DeserializeOwned; /// Extrinsic type within blocks. + #[module(ignore)] type Extrinsic: Parameter + Member + Extrinsic + Debug + MaybeSerializeDeserialize; /// Data to be associated with an account (other than nonce/transaction counter, which this @@ -129,7 +126,7 @@ pub trait System: 'static + Eq + Clone + Debug { pub type RefCount = u8; /// Information of an account. -#[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode)] +#[derive(Clone, Debug, Eq, PartialEq, Default, Decode, Encode)] pub struct AccountInfo { /// The number of transactions this account has sent. pub nonce: T::Index, @@ -141,44 +138,30 @@ pub struct AccountInfo { pub data: T::AccountData, } -const MODULE: &str = "System"; - /// Account field of the `System` module. -#[derive(Encode)] -pub struct AccountStore<'a, T: System>(pub &'a T::AccountId); - -impl<'a, T: System> Store for AccountStore<'a, T> { - const MODULE: &'static str = MODULE; - const FIELD: &'static str = "Account"; - type Returns = AccountInfo; - - fn key(&self, metadata: &Metadata) -> Result { - Ok(metadata - .module(Self::MODULE)? - .storage(Self::FIELD)? - .map()? - .key(self.0)) - } +#[derive(Clone, Debug, Eq, PartialEq, Store, Encode)] +pub struct AccountStore<'a, T: System> { + #[store(returns = AccountInfo)] + /// Account to retrieve the `AccountInfo` for. + pub account_id: &'a T::AccountId, } /// Arguments for updating the runtime code -#[derive(Encode)] -pub struct SetCodeCall<'a>(pub &'a Vec); - -impl<'a, T: System> Call for SetCodeCall<'a> { - const MODULE: &'static str = MODULE; - const FUNCTION: &'static str = "set_code"; +#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] +pub struct SetCodeCall<'a, T: System> { + /// Runtime marker. + pub _runtime: PhantomData, + /// Runtime wasm blob. + pub code: &'a [u8], } -use frame_support::weights::DispatchInfo; - /// Event for the System module. -#[derive(Clone, Debug, codec::Decode)] +#[derive(Clone, Debug, Eq, PartialEq, Decode)] pub enum SystemEvent { /// An extrinsic completed successfully. ExtrinsicSuccess(DispatchInfo), /// An extrinsic failed. - ExtrinsicFailed(sp_runtime::DispatchError, DispatchInfo), + ExtrinsicFailed(DispatchError, DispatchInfo), /// `:code` was updated. CodeUpdated, /// A new account was created. @@ -188,7 +171,7 @@ pub enum SystemEvent { } /// A phase of a block's execution. -#[derive(codec::Decode)] +#[derive(Clone, Debug, Eq, PartialEq, Decode)] pub enum Phase { /// Applying an extrinsic. ApplyExtrinsic(u32), diff --git a/src/lib.rs b/src/lib.rs index 2a62cb8225..1c6bc8bc5e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,6 +39,12 @@ )] #![allow(clippy::type_complexity)] +#[macro_use] +extern crate substrate_subxt_proc_macro; + +pub use sp_core; +pub use sp_runtime; + use std::{ convert::TryFrom, marker::PhantomData, @@ -98,12 +104,13 @@ pub use crate::{ ExtrinsicSuccess, }, runtimes::*, + substrate_subxt_proc_macro::*, }; use crate::{ frame::{ balances::Balances, system::{ - AccountStore, + AccountStoreExt, Phase, System, SystemEvent, @@ -161,8 +168,7 @@ impl ClientBuilder { jsonrpsee::http_client(url) } }; - let rpc = Rpc::new(client).await?; - + let rpc = Rpc::new(client); let (metadata, genesis_hash, runtime_version) = future::join3( rpc.metadata(), rpc.genesis_hash(), @@ -211,11 +217,11 @@ impl Client { &self, store: F, hash: Option, - ) -> Result, Error> { + ) -> Result { let key = store.key(&self.metadata)?; let value = self.rpc.storage::(key, hash).await?; if let Some(v) = value { - Ok(Some(v)) + Ok(v) } else { Ok(store.default(&self.metadata)?) } @@ -324,7 +330,7 @@ impl Client { impl Client where - T: System + Balances + Send + Sync, + T: System + Balances + Send + Sync + 'static, S: 'static, E: SignedExtra + SignedExtension + 'static, { @@ -334,11 +340,7 @@ where account_id: &::AccountId, call: C, ) -> Result>::Extra>, Error> { - let account_nonce = self - .fetch(AccountStore(account_id), None) - .await? - .unwrap() - .nonce; + let account_nonce = self.account(account_id).await?.nonce; let version = self.runtime_version.spec_version; let genesis_hash = self.genesis_hash; let call = self @@ -365,12 +367,7 @@ where let account_id = S::Signer::from(signer.public()).into_account(); let nonce = match nonce { Some(nonce) => nonce, - None => { - self.fetch(AccountStore(&account_id), None) - .await? - .unwrap() - .nonce - } + None => self.account(&account_id).await?.nonce, }; let genesis_hash = self.genesis_hash; @@ -419,7 +416,7 @@ impl XtBuilder { } } -impl XtBuilder +impl XtBuilder where P: Pair, S: Verify + Codec + From, @@ -499,7 +496,7 @@ impl EventsSubscriber { } } -impl EventsSubscriber +impl EventsSubscriber where P: Pair, S: Verify + Codec + From, @@ -522,7 +519,7 @@ where /// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of /// the transaction payload -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Encoded(pub Vec); impl codec::Encode for Encoded { @@ -610,29 +607,6 @@ mod tests { .unwrap(); } - #[async_std::test] - #[ignore] // requires locally running substrate node - async fn test_state_total_issuance() { - let client = test_client().await; - client - .fetch(balances::TotalIssuance(Default::default()), None) - .await - .unwrap() - .unwrap(); - } - - #[async_std::test] - #[ignore] // requires locally running substrate node - async fn test_state_read_free_balance() { - let client = test_client().await; - let account = AccountKeyring::Alice.to_account_id(); - client - .fetch(AccountStore(&account), None) - .await - .unwrap() - .unwrap(); - } - #[async_std::test] #[ignore] // requires locally running substrate node async fn test_chain_subscribe_blocks() { diff --git a/src/metadata.rs b/src/metadata.rs index ae0b67bf56..80fcbe60aa 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -24,6 +24,7 @@ use std::{ use codec::{ Decode, Encode, + Error as CodecError, }; use frame_metadata::{ @@ -63,6 +64,9 @@ pub enum MetadataError { /// Storage type does not match requested type. #[error("Storage type error")] StorageTypeError, + /// Default error. + #[error("Failed to decode default: {0}")] + DefaultError(CodecError), } /// Runtime metadata. @@ -218,9 +222,9 @@ impl StorageMetadata { bytes } - pub fn default(&self) -> Option { - // substrate handles the default different for A => B vs A => Option - Decode::decode(&mut &self.default[..]).ok() + pub fn default(&self) -> Result { + Decode::decode(&mut &self.default[..]) + .map_err(|err| MetadataError::DefaultError(err)) } pub fn hash(hasher: &StorageHasher, bytes: &[u8]) -> Vec { diff --git a/src/rpc.rs b/src/rpc.rs index 76f7a8aec2..b6bef3f34f 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -19,13 +19,16 @@ // Related: https://github.com/paritytech/substrate-subxt/issues/66 #![allow(irrefutable_let_patterns)] -use std::convert::TryInto; - use codec::{ Decode, Encode, Error as CodecError, }; +use core::{ + convert::TryInto, + marker::PhantomData, +}; +use frame_metadata::RuntimeMetadataPrefixed; use jsonrpsee::{ client::Subscription, common::{ @@ -34,10 +37,7 @@ use jsonrpsee::{ }, Client, }; - use num_traits::bounds::Bounded; - -use frame_metadata::RuntimeMetadataPrefixed; use sc_rpc_api::state::ReadProof; use serde::Serialize; use sp_core::{ @@ -62,7 +62,6 @@ use sp_runtime::{ }; use sp_transaction_pool::TransactionStatus; use sp_version::RuntimeVersion; -use std::marker::PhantomData; use crate::{ error::Error, @@ -110,18 +109,26 @@ where } /// Client for substrate rpc interfaces -#[derive(Clone)] pub struct Rpc { client: Client, - marker: std::marker::PhantomData, + marker: PhantomData, +} + +impl Clone for Rpc { + fn clone(&self) -> Self { + Self { + client: self.client.clone(), + marker: PhantomData, + } + } } impl Rpc { - pub async fn new(client: Client) -> Result { - Ok(Rpc { + pub fn new(client: Client) -> Self { + Self { client, marker: PhantomData, - }) + } } /// Fetch a storage key