diff --git a/Cargo.toml b/Cargo.toml index 8448c19840b..43efe44e922 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,6 @@ +[workspace] +members = [".", "proc-macro"] + [package] name = "substrate-subxt" version = "0.6.0" @@ -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" } diff --git a/examples/kusama_balance_transfer.rs b/examples/kusama_balance_transfer.rs index 5255244365e..b0ea53b17c7 100644 --- a/examples/kusama_balance_transfer.rs +++ b/examples/kusama_balance_transfer.rs @@ -35,7 +35,7 @@ fn main() { async fn transfer_balance() -> Result { 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::::new() @@ -43,9 +43,9 @@ async fn transfer_balance() -> Result { .await? .xt(signer, None) .await? - .submit(balances::transfer::( - dest.clone().into(), - 10_000, - )) + .submit(balances::TransferCall { + to: &dest, + amount: 10_000, + }) .await } diff --git a/examples/submit_and_watch.rs b/examples/submit_and_watch.rs index 09d3a77d817..1bc2f476cc9 100644 --- a/examples/submit_and_watch.rs +++ b/examples/submit_and_watch.rs @@ -17,49 +17,31 @@ use sp_keyring::AccountKeyring; use substrate_subxt::{ balances, - system::System, DefaultNodeRuntime as Runtime, - ExtrinsicSuccess, }; -type AccountId = ::AccountId; -type Balance = ::Balance; +#[async_std::main] +async fn main() -> Result<(), Box> { + env_logger::init(); -fn main() { - let result: Result, Box> = - 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::::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::(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::::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::>()? { + println!("Balance transfer success: value: {:?}", event.amount); + } else { + println!("Failed to find Balances::Transfer Event"); } + Ok(()) } diff --git a/proc-macro/Cargo.toml b/proc-macro/Cargo.toml new file mode 100644 index 00000000000..ccb78d3b848 --- /dev/null +++ b/proc-macro/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "substrate-subxt-proc-macro" +version = "0.1.0" +authors = ["David Craven "] +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" diff --git a/proc-macro/src/call.rs b/proc-macro/src/call.rs new file mode 100644 index 00000000000..a01b2e5444d --- /dev/null +++ b/proc-macro/src/call.rs @@ -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); + + let expanded = quote! { + impl#generics #subxt::Call for #ident<#(#params),*> { + const MODULE: &'static str = MODULE; + const FUNCTION: &'static str = #call_name; + fn events_decoder( + decoder: &mut #subxt::EventsDecoder, + ) -> Result<(), #subxt::EventsError> { + decoder.#with_module()?; + Ok(()) + } + } + + pub trait #call_trait { + fn #call<'a>( + self, + #args + ) -> core::pin::Pin> + Send + 'a>>; + } + + impl #call_trait for #subxt::EventsSubscriber + 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::Address: From, + E: #subxt::SignedExtra + #sp_runtime::traits::SignedExtension + 'static, + { + fn #call<'a>( + self, + #args + ) -> core::pin::Pin> + 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 ::Address, + #[codec(compact)] + pub amount: T::Balance, + } + }; + let expected = quote! { + impl<'a, T: Balances> substrate_subxt::Call for TransferCall<'a, T> { + const MODULE: &'static str = MODULE; + const FUNCTION: &'static str = "transfer"; + fn events_decoder( + decoder: &mut substrate_subxt::EventsDecoder, + ) -> Result<(), substrate_subxt::EventsError> { + decoder.with_balances()?; + Ok(()) + } + } + + pub trait TransferCallExt { + fn transfer<'a>( + self, + to: &'a ::Address, + amount: T::Balance, + ) -> core::pin::Pin, substrate_subxt::Error>> + Send + 'a>>; + } + + impl TransferCallExt 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< + AccountId = T::AccountId>, + T::Address: From, + E: substrate_subxt::SignedExtra + sp_runtime::traits::SignedExtension + 'static, + { + fn transfer<'a>( + self, + to: &'a ::Address, + amount: T::Balance, + ) -> core::pin::Pin, 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); + } +} diff --git a/proc-macro/src/event.rs b/proc-macro/src/event.rs new file mode 100644 index 00000000000..59f64622fda --- /dev/null +++ b/proc-macro/src/event.rs @@ -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 #subxt::Event for #ident { + const MODULE: &'static str = MODULE; + const EVENT: &'static str = #event_name; + } + + pub trait #event_trait { + fn #event(&self) -> Result>, #codec::Error>; + } + + impl #event_trait for #subxt::ExtrinsicSuccess { + fn #event(&self) -> Result>, #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 { + pub from: ::AccountId, + pub to: ::AccountId, + pub amount: T::Balance, + } + }; + let expected = quote! { + impl substrate_subxt::Event for TransferEvent { + const MODULE: &'static str = MODULE; + const EVENT: &'static str = "Transfer"; + } + + pub trait TransferEventExt { + fn transfer(&self) -> Result>, codec::Error>; + } + + impl TransferEventExt for substrate_subxt::ExtrinsicSuccess { + fn transfer(&self) -> Result>, 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); + } +} diff --git a/proc-macro/src/lib.rs b/proc-macro/src/lib.rs new file mode 100644 index 00000000000..ccecbe1c62a --- /dev/null +++ b/proc-macro/src/lib.rs @@ -0,0 +1,39 @@ +extern crate proc_macro; + +mod call; +mod event; +mod module; +mod store; +mod test; +mod utils; + +use proc_macro::TokenStream; +use synstructure::{ + decl_derive, + Structure, +}; + +#[proc_macro_attribute] +pub fn module(args: TokenStream, input: TokenStream) -> TokenStream { + module::module(args.into(), input.into()).into() +} + +decl_derive!([Call] => call); +fn call(s: Structure) -> TokenStream { + call::call(s).into() +} + +decl_derive!([Event] => event); +fn event(s: Structure) -> TokenStream { + event::event(s).into() +} + +decl_derive!([Store, attributes(store)] => store); +fn store(s: Structure) -> TokenStream { + store::store(s).into() +} + +#[proc_macro] +pub fn subxt_test(input: TokenStream) -> TokenStream { + test::test(input.into()).into() +} diff --git a/proc-macro/src/module.rs b/proc-macro/src/module.rs new file mode 100644 index 00000000000..3327b368c42 --- /dev/null +++ b/proc-macro/src/module.rs @@ -0,0 +1,123 @@ +use crate::utils; +use heck::SnakeCase; +use proc_macro2::TokenStream; +use quote::{ + format_ident, + quote, +}; + +fn events_decoder_trait_name(module: &syn::Ident) -> syn::Ident { + format_ident!("{}EventsDecoder", module.to_string()) +} + +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(); + + let subxt = utils::use_crate("substrate-subxt"); + let module = &input.ident; + let module_name = module.to_string(); + let module_events_decoder = events_decoder_trait_name(module); + let with_module = with_module_ident(module); + + let bounds = input.supertraits.iter().filter_map(|bound| { + if let syn::TypeParamBound::Trait(syn::TraitBound { path, .. }) = bound { + let module = utils::path_to_ident(path); + let with_module = with_module_ident(module); + Some(quote! { + self.#with_module()?; + }) + } else { + None + } + }); + let types = input.items.iter().filter_map(|item| { + if let syn::TraitItem::Type(ty) = item { + let ident = &ty.ident; + let ident_str = ident.to_string(); + Some(quote! { + self.register_type_size::(#ident_str)?; + }) + } else { + None + } + }); + + quote! { + #input + + const MODULE: &str = #module_name; + + pub trait #module_events_decoder { + fn #with_module(&mut self) -> Result<(), #subxt::EventsError>; + } + + impl #module_events_decoder for + #subxt::EventsDecoder + { + fn #with_module(&mut self) -> Result<(), #subxt::EventsError> { + #(#bounds)* + #(#types)* + Ok(()) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_balance_module() { + let attr = quote!(#[module]); + let input = quote! { + pub trait Balances: System { + type Balance: frame_support::Parameter + + sp_runtime::traits::Member + + sp_runtime::traits::AtLeast32Bit + + codec::Codec + + Default + + Copy + + sp_runtime::traits::MaybeSerialize + + std::fmt::Debug + + From<::BlockNumber>; + } + }; + let expected = quote! { + pub trait Balances: System { + type Balance: frame_support::Parameter + + sp_runtime::traits::Member + + sp_runtime::traits::AtLeast32Bit + + codec::Codec + + Default + + Copy + + sp_runtime::traits::MaybeSerialize + + std::fmt::Debug + + From< ::BlockNumber>; + } + + const MODULE: &str = "Balances"; + + pub trait BalancesEventsDecoder { + fn with_balances(&mut self) -> Result<(), substrate_subxt::EventsError>; + } + + impl BalancesEventsDecoder for + substrate_subxt::EventsDecoder + { + fn with_balances(&mut self) -> Result<(), substrate_subxt::EventsError> { + self.with_system()?; + self.register_type_size::("Balance")?; + Ok(()) + } + } + }; + + let result = module(attr, input); + utils::assert_proc_macro(result, expected); + } +} diff --git a/proc-macro/src/store.rs b/proc-macro/src/store.rs new file mode 100644 index 00000000000..865573d0d07 --- /dev/null +++ b/proc-macro/src/store.rs @@ -0,0 +1,189 @@ +use crate::utils; +use heck::SnakeCase; +use proc_macro2::{ + TokenStream, + TokenTree, +}; +use quote::{ + format_ident, + quote, +}; +use syn::{ + parse::{ + Parse, + ParseStream, + }, + Token, +}; +use synstructure::Structure; + +struct Returns { + returns: syn::Ident, + _eq: Token![=], + ty: syn::Type, +} + +impl Parse for Returns { + fn parse(input: ParseStream) -> syn::Result { + Ok(Returns { + returns: input.parse()?, + _eq: input.parse()?, + ty: input.parse()?, + }) + } +} + +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 +} + +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 = 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 ret = bindings + .iter() + .filter_map(|bi| bi.ast().attrs.iter().filter_map(parse_returns_attr).next()) + .next() + .expect("#[store(returns = ..)] needs to be specified."); + let store_ty = format_ident!( + "{}", + match 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)*); + + quote! { + impl#generics #subxt::Store for #ident<#(#params),*> { + const MODULE: &'static str = MODULE; + const FIELD: &'static str = #store_name; + type Returns = #ret; + fn key( + &self, + metadata: &#subxt::Metadata, + ) -> Result<#sp_core::storage::StorageKey, #subxt::MetadataError> { + Ok(metadata + .module(Self::MODULE)? + .storage(Self::FIELD)? + .#store_ty()? + .key(#keys)) + } + } + + pub trait #store_trait { + fn #store<'a>( + &'a self, + #args + ) -> core::pin::Pin, #subxt::Error>> + Send + 'a>>; + } + + impl #store_trait for #subxt::Client + where + T: #module + Send + Sync, + S: 'static, + E: Send + Sync + 'static, + { + fn #store<'a>( + &'a self, + #args + ) -> core::pin::Pin, #subxt::Error>> + Send + 'a>> { + Box::pin(self.fetch(#ident { #fields }, None)) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_account_store() { + let input = quote! { + #[derive(Encode, Store)] + pub struct AccountStore<'a, T: Balances> { + #[store(returns = AccountData)] + account_id: &'a ::AccountId, + } + }; + let expected = quote! { + impl<'a, T: Balances> substrate_subxt::Store for AccountStore<'a, T> { + const MODULE: &'static str = MODULE; + const FIELD: &'static str = "Account"; + type Returns = AccountData; + fn key( + &self, + metadata: &substrate_subxt::Metadata, + ) -> Result { + Ok(metadata + .module(Self::MODULE)? + .storage(Self::FIELD)? + .map()? + .key(&self.account_id,)) + } + } + + pub trait AccountStoreExt { + fn account<'a>( + &'a self, + account_id: &'a ::AccountId, + ) -> core::pin::Pin >, substrate_subxt::Error>> + Send + 'a>>; + } + + impl AccountStoreExt for substrate_subxt::Client + where + T: Balances + Send + Sync, + S: 'static, + E: Send + Sync + 'static, + { + fn account<'a>( + &'a self, + account_id: &'a ::AccountId, + ) -> core::pin::Pin >, substrate_subxt::Error>> + Send + 'a>> + { + Box::pin(self.fetch(AccountStore { account_id, }, None)) + } + } + }; + let derive_input = syn::parse2(input).unwrap(); + let s = Structure::new(&derive_input); + let result = store(s); + utils::assert_proc_macro(result, expected); + } +} diff --git a/proc-macro/src/test.rs b/proc-macro/src/test.rs new file mode 100644 index 00000000000..3d270587d9d --- /dev/null +++ b/proc-macro/src/test.rs @@ -0,0 +1,497 @@ +use crate::utils; +use proc_macro2::TokenStream; +use quote::{ + format_ident, + quote, +}; +use syn::{ + parse::{ + Parse, + ParseStream, + }, + punctuated::Punctuated, +}; + +mod kw { + use syn::custom_keyword; + + custom_keyword!(name); + custom_keyword!(runtime); + custom_keyword!(account); + custom_keyword!(signature); + custom_keyword!(extra); + custom_keyword!(step); + custom_keyword!(state); + custom_keyword!(call); + custom_keyword!(event); + custom_keyword!(assert); +} + +#[derive(Debug)] +struct Item { + key: K, + colon: syn::token::Colon, + value: V, +} + +impl Parse for Item { + fn parse(input: ParseStream) -> syn::Result { + Ok(Self { + key: input.parse()?, + colon: input.parse()?, + value: input.parse()?, + }) + } +} + +#[derive(Debug)] +struct Items { + brace: syn::token::Brace, + items: Punctuated, +} + +impl Parse for Items { + fn parse(input: ParseStream) -> syn::Result { + let content; + Ok(Self { + brace: syn::braced!(content in input), + items: content.parse_terminated(I::parse)?, + }) + } +} + +type ItemTest = Items; + +#[derive(Debug)] +enum TestItem { + Name(Item), + Runtime(Item), + Account(Item), + Signature(Item), + Extra(Item), + State(Item), + Step(Item), +} + +impl Parse for TestItem { + fn parse(input: ParseStream) -> syn::Result { + if input.peek(kw::name) { + Ok(TestItem::Name(input.parse()?)) + } else if input.peek(kw::runtime) { + Ok(TestItem::Runtime(input.parse()?)) + } else if input.peek(kw::account) { + Ok(TestItem::Account(input.parse()?)) + } else if input.peek(kw::signature) { + Ok(TestItem::Signature(input.parse()?)) + } else if input.peek(kw::extra) { + Ok(TestItem::Extra(input.parse()?)) + } else if input.peek(kw::state) { + Ok(TestItem::State(input.parse()?)) + } else { + Ok(TestItem::Step(input.parse()?)) + } + } +} + +type ItemStep = Items; + +#[derive(Debug)] +enum StepItem { + State(Item), + Call(Item), + Event(Item), + Assert(Item), +} + +impl Parse for StepItem { + fn parse(input: ParseStream) -> syn::Result { + if input.peek(kw::state) { + Ok(StepItem::State(input.parse()?)) + } else if input.peek(kw::call) { + Ok(StepItem::Call(input.parse()?)) + } else if input.peek(kw::event) { + Ok(StepItem::Event(input.parse()?)) + } else { + Ok(StepItem::Assert(input.parse()?)) + } + } +} + +type ItemState = Items; +type StateItem = Item; + +struct Test { + name: syn::Ident, + runtime: syn::Type, + account: syn::Ident, + signature: syn::Type, + extra: syn::Type, + state: Option, + steps: Vec, +} + +impl From for Test { + fn from(test: ItemTest) -> Self { + let mut name = None; + let mut runtime = None; + let mut account = None; + let mut signature = None; + let mut extra = None; + let mut state = None; + let mut steps = vec![]; + for test_item in test.items { + match test_item { + TestItem::Name(item) => { + name = Some(item.value); + } + TestItem::Runtime(item) => { + runtime = Some(item.value); + } + TestItem::Account(item) => { + account = Some(item.value); + } + TestItem::Signature(item) => { + signature = Some(item.value); + } + TestItem::Extra(item) => { + extra = Some(item.value); + } + TestItem::State(item) => { + state = Some(item.value.into()); + } + 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() + }); + 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() + }), + extra: extra.unwrap_or_else(|| { + let subxt = utils::use_crate("substrate-subxt"); + syn::parse2(quote!(#subxt::DefaultExtra<#runtime>)).unwrap() + }), + runtime, + state, + steps, + } + } +} + +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 Test { + name, + runtime, + account, + signature, + extra, + state, + steps, + } = self; + let step = steps + .into_iter() + .map(|step| step.into_tokens(&account, state.as_ref())); + quote! { + #[async_std::test] + #[ignore] + async fn #name() { + #env_logger::try_init().ok(); + let client = #subxt::ClientBuilder::<#runtime, #signature, #extra>::new() + .build().await.unwrap(); + #[allow(unused)] + let alice = #sp_keyring::AccountKeyring::Alice.to_account_id(); + #[allow(unused)] + let bob = #sp_keyring::AccountKeyring::Bob.to_account_id(); + #[allow(unused)] + let charlie = #sp_keyring::AccountKeyring::Charlie.to_account_id(); + #[allow(unused)] + let dave = #sp_keyring::AccountKeyring::Dave.to_account_id(); + #[allow(unused)] + let eve = #sp_keyring::AccountKeyring::Eve.to_account_id(); + #[allow(unused)] + let ferdie = #sp_keyring::AccountKeyring::Ferdie.to_account_id(); + + #({ + #step + })* + } + } + } +} + +struct Step { + state: Option, + call: syn::Expr, + event_name: Vec, + event: Vec, + assert: syn::Expr, +} + +impl From for Step { + fn from(step: ItemStep) -> Self { + let mut state = None; + let mut call = None; + let mut event_name = vec![]; + let mut event = vec![]; + let mut assert = None; + + for step_item in step.items { + match step_item { + StepItem::State(item) => { + state = Some(item.value.into()); + } + StepItem::Call(item) => { + call = Some(item.value); + } + StepItem::Event(item) => { + event_name.push(struct_name(&item.value)); + event.push(item.value); + } + StepItem::Assert(item) => { + assert = Some(item.value); + } + } + } + + Self { + state, + call: call.expect("Step requires a call."), + event_name, + event, + assert: assert.expect("Step requires assert."), + } + } +} + +impl Step { + fn into_tokens( + self, + account: &syn::Ident, + test_state: Option<&State>, + ) -> TokenStream { + let sp_keyring = utils::use_crate("sp-keyring"); + let Step { + state, + call, + event_name, + event, + assert, + } = self; + let state = state + .as_ref() + .unwrap_or_else(|| test_state.expect("No state for step")); + let State { + state_name, + state, + state_param, + } = state; + 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),* } + }; + + #[allow(unused)] + let result = xt + .watch() + .submit(#call) + .await + .unwrap(); + + #( + assert_eq!( + result.find_event::<#event_name<_>>().unwrap(), + Some(#event) + ); + )* + + let post = { + #( + let #state_name = client.fetch(#state, None).await.unwrap().unwrap(); + )* + State { #(#state_name),* } + }; + + #assert + } + } +} + +struct State { + state_name: Vec, + state: Vec, + state_param: Vec, +} + +impl From for State { + fn from(item_state: ItemState) -> Self { + let mut state_name = vec![]; + let mut state = vec![]; + for item in item_state.items { + state_name.push(item.key); + state.push(item.value); + } + let state_param = (b'A'..b'Z') + .map(|c| format_ident!("{}", (c as char).to_string())) + .take(state_name.len()) + .collect::>(); + Self { + state_name, + state, + state_param, + } + } +} + +fn struct_name(expr: &syn::Expr) -> syn::Path { + if let syn::Expr::Struct(syn::ExprStruct { path, .. }) = expr { + return path.clone() + } else { + panic!("not a struct"); + } +} + +pub fn test(input: TokenStream) -> TokenStream { + let item_test: ItemTest = syn::parse2(input).unwrap(); + Test::from(item_test).into_tokens() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn transfer_test_case() { + let input = quote! {{ + name: test_transfer_balance, + runtime: KusamaRuntime, + account: Alice, + step: { + state: { + alice: AccountStore { account_id: &alice }, + bob: AccountStore { account_id: &bob }, + }, + call: TransferCall { + to: &bob, + amount: 10_000, + }, + event: TransferEvent { + from: alice.clone(), + to: bob.clone(), + amount: 10_000, + }, + assert: { + assert_eq!(pre.alice.free, post.alice.free - 10_000); + assert_eq!(pre.bob.free, post.bob.free + 10_000); + }, + }, + }}; + let expected = quote! { + #[async_std::test] + #[ignore] + async fn test_transfer_balance() { + env_logger::try_init().ok(); + let client = substrate_subxt::ClientBuilder::< + KusamaRuntime, + sp_runtime::MultiSignature, + substrate_subxt::DefaultExtra + >::new().build().await.unwrap(); + #[allow(unused)] + let alice = sp_keyring::AccountKeyring::Alice.to_account_id(); + #[allow(unused)] + let bob = sp_keyring::AccountKeyring::Bob.to_account_id(); + #[allow(unused)] + let charlie = sp_keyring::AccountKeyring::Charlie.to_account_id(); + #[allow(unused)] + let dave = sp_keyring::AccountKeyring::Dave.to_account_id(); + #[allow(unused)] + let eve = sp_keyring::AccountKeyring::Eve.to_account_id(); + #[allow(unused)] + let ferdie = sp_keyring::AccountKeyring::Ferdie.to_account_id(); + + { + let xt = client.xt(sp_keyring::AccountKeyring::Alice.pair(), None).await.unwrap(); + + struct State { + alice: A, + bob: B, + } + + let pre = { + 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 } + }; + + #[allow(unused)] + let result = xt + .watch() + .submit(TransferCall { + to: &bob, + amount: 10_000, + }) + .await + .unwrap(); + + assert_eq!( + result.find_event::>().unwrap(), + Some(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); + } + } + } + }; + let result = test(input); + utils::assert_proc_macro(result, expected); + } +} diff --git a/proc-macro/src/utils.rs b/proc-macro/src/utils.rs new file mode 100644 index 00000000000..3e3aaea6d58 --- /dev/null +++ b/proc-macro/src/utils.rs @@ -0,0 +1,82 @@ +use proc_macro2::{ + Span, + TokenStream, +}; +use quote::quote; +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()) +} + +pub fn bindings<'a>(s: &'a Structure) -> Vec<&'a BindingInfo<'a>> { + let mut bindings = vec![]; + for variant in s.variants() { + for binding in variant.bindings() { + bindings.push(binding); + } + } + bindings +} + +pub fn module_name(generics: &syn::Generics) -> &syn::Path { + generics + .params + .iter() + .filter_map(|p| { + if let syn::GenericParam::Type(p) = p { + p.bounds + .iter() + .filter_map(|b| { + if let syn::TypeParamBound::Trait(t) = b { + Some(&t.path) + } else { + None + } + }) + .next() + } else { + None + } + }) + .next() + .unwrap() +} + +pub fn path_to_ident(path: &syn::Path) -> &syn::Ident { + &path.segments.iter().last().unwrap().ident +} + +pub fn type_params(generics: &syn::Generics) -> Vec { + generics + .params + .iter() + .filter_map(|g| { + match g { + syn::GenericParam::Type(p) => { + let ident = &p.ident; + Some(quote!(#ident)) + } + syn::GenericParam::Lifetime(p) => { + let lifetime = &p.lifetime; + Some(quote!(#lifetime)) + } + syn::GenericParam::Const(_) => None, + } + }) + .collect() +} + +#[cfg(test)] +pub(crate) fn assert_proc_macro( + result: proc_macro2::TokenStream, + expected: proc_macro2::TokenStream, +) { + let result = result.to_string(); + let expected = expected.to_string(); + pretty_assertions::assert_eq!(result, expected); +} diff --git a/proc-macro/tests/balances.rs b/proc-macro/tests/balances.rs new file mode 100644 index 00000000000..b5b6f0b30ce --- /dev/null +++ b/proc-macro/tests/balances.rs @@ -0,0 +1,141 @@ +use 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, + 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 + + AtLeast32Bit + + codec::Codec + + Default + + Copy + + MaybeSerialize + + Debug + + From<::BlockNumber>; +} + +#[derive(Clone, Decode, Default)] +pub struct AccountData { + pub free: Balance, + pub reserved: Balance, + pub misc_frozen: Balance, + pub fee_frozen: Balance, +} + +#[derive(Encode, Store)] +pub struct AccountStore<'a, T: Balances> { + #[store(returns = AccountData)] + pub account_id: &'a ::AccountId, +} + +#[derive(Call, Encode)] +pub struct TransferCall<'a, T: Balances> { + pub to: &'a ::Address, + #[codec(compact)] + pub amount: T::Balance, +} + +#[derive(Debug, Decode, Eq, Event, PartialEq)] +pub struct TransferEvent { + pub from: ::AccountId, + pub to: ::AccountId, + pub amount: T::Balance, +} + +impl Balances for KusamaRuntime { + type Balance = u128; +} + +subxt_test!({ + name: transfer_test_case, + runtime: KusamaRuntime, + account: Alice, + step: { + state: { + alice: AccountStore { account_id: &alice }, + bob: AccountStore { account_id: &bob }, + }, + call: TransferCall { + to: &bob, + amount: 10_000, + }, + event: TransferEvent { + from: alice.clone(), + to: bob.clone(), + amount: 10_000, + }, + assert: { + assert_eq!(pre.alice.free, post.alice.free - 10_000); + assert_eq!(pre.bob.free, post.bob.free + 10_000); + }, + }, +}); + +#[async_std::test] +#[ignore] +async fn transfer_balance_example() -> Result<(), Box> { + env_logger::init(); + let client = ClientBuilder::::new().build().await?; + 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 pre = (alice_account, bob_account); + + let result = client + .xt(AccountKeyring::Alice.pair(), None) + .await? + .watch() + .transfer(&bob.clone().into(), 10_000) + .await?; + + assert_eq!( + result.transfer()?, + Some(TransferEvent { + from: alice.clone(), + to: bob.clone(), + amount: 10_000, + }) + ); + + let alice_account = client.account(&alice).await?.unwrap_or_default(); + let bob_account = client.account(&bob).await?.unwrap_or_default(); + let post = (alice_account, bob_account); + + assert_eq!(pre.0.free, post.0.free - 10_000); + assert_eq!(pre.1.free, post.1.free + 10_000); + Ok(()) +} diff --git a/src/events.rs b/src/events.rs index c9300fdf68a..b170e1ec483 100644 --- a/src/events.rs +++ b/src/events.rs @@ -37,7 +37,6 @@ use codec::{ }; use crate::{ - frame::balances::Balances, metadata::{ EventArg, Metadata, @@ -66,23 +65,28 @@ pub struct RawEvent { pub data: Vec, } +/// Events error. #[derive(Debug, thiserror::Error)] pub enum EventsError { + /// Codec error. #[error("Scale codec error: {0:?}")] CodecError(#[from] CodecError), + /// Metadata error. #[error("Metadata error: {0:?}")] Metadata(#[from] MetadataError), + /// Type size unavailable. #[error("Type Sizes Unavailable: {0:?}")] TypeSizeUnavailable(String), } +/// Event decoder. pub struct EventsDecoder { metadata: Metadata, // todo: [AJ] borrow? type_sizes: HashMap, marker: PhantomData T>, } -impl TryFrom for EventsDecoder { +impl TryFrom for EventsDecoder { type Error = EventsError; fn try_from(metadata: Metadata) -> Result { @@ -108,15 +112,19 @@ impl TryFrom for EventsDecoder { decoder.register_type_size::("AccountId")?; decoder.register_type_size::("BlockNumber")?; decoder.register_type_size::("Hash")?; - decoder.register_type_size::<::Balance>("Balance")?; - // VoteThreshold enum index decoder.register_type_size::("VoteThreshold")?; Ok(decoder) } } -impl 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 U: Default + Codec + Send + 'static, @@ -130,6 +138,7 @@ impl EventsDecoder { } } + /// Check missing type sizes. pub fn check_missing_type_sizes(&self) { let mut missing = HashSet::new(); for module in self.metadata.modules_with_events() { @@ -194,6 +203,7 @@ impl EventsDecoder { Ok(()) } + /// Decode events. pub fn decode_events( &self, input: &mut &[u8], diff --git a/src/frame/balances.rs b/src/frame/balances.rs index 1b1bb0a7e50..c597f20b1b8 100644 --- a/src/frame/balances.rs +++ b/src/frame/balances.rs @@ -16,15 +16,24 @@ //! Implements support for the pallet_balances module. -use crate::frame::{ - system::System, - Call, +use crate::{ + frame::{ + system::System, + Call, + Event, + Store, + }, + metadata::{ + Metadata, + MetadataError, + }, }; use codec::{ Decode, Encode, }; use frame_support::Parameter; +use sp_core::storage::StorageKey; use sp_runtime::traits::{ AtLeast32Bit, MaybeSerialize, @@ -32,6 +41,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. pub trait Balances: System { /// The balance of an account. @@ -70,15 +81,22 @@ pub struct AccountData { pub fee_frozen: Balance, } -const MODULE: &str = "Balances"; -const TRANSFER: &str = "transfer"; +/// The total issuance of the balances module. +#[derive(Encode)] +pub struct TotalIssuance(pub core::marker::PhantomData); -/// Arguments for transferring a balance -#[derive(codec::Encode)] -pub struct TransferArgs { - to: ::Address, - #[codec(compact)] - amount: ::Balance, +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()) + } } /// Transfer some liquid free balance to another account. @@ -87,9 +105,32 @@ pub struct TransferArgs { /// 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. -pub fn transfer( - to: ::Address, - amount: ::Balance, -) -> Call> { - Call::new(MODULE, TRANSFER, TransferArgs { to, amount }) +#[derive(Encode)] +pub struct TransferCall<'a, T: Balances> { + /// Destination of the transfer. + pub to: &'a ::Address, + /// Amount to transfer. + #[codec(compact)] + 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)] +pub struct TransferEvent { + /// Account balance was transfered from. + pub from: ::AccountId, + /// Account balance was transfered to. + pub to: ::AccountId, + /// Amount of balance that was transfered. + pub amount: T::Balance, +} + +impl Event for TransferEvent { + const MODULE: &'static str = MODULE; + const EVENT: &'static str = "transfer"; } diff --git a/src/frame/contracts.rs b/src/frame/contracts.rs index 11bedf12abc..7060749952d 100644 --- a/src/frame/contracts.rs +++ b/src/frame/contracts.rs @@ -20,23 +20,15 @@ use crate::frame::{ balances::Balances, system::System, Call, + Event, +}; +use codec::{ + Decode, + Encode, }; -use codec::Encode; const MODULE: &str = "Contracts"; -mod calls { - pub const PUT_CODE: &str = "put_code"; - pub const INSTANTIATE: &str = "instantiate"; - pub const CALL: &str = "call"; -} - -#[allow(unused)] -mod events { - pub const CODE_STORED: &str = "CodeStored"; - pub const INSTANTIATED: &str = "Instantiated"; -} - /// Gas units are chosen to be represented by u64 so that gas metering /// instructions can operate on them efficiently. pub type Gas = u64; @@ -44,40 +36,21 @@ pub type Gas = u64; /// The subset of the `pallet_contracts::Trait` that a client must implement. pub trait Contracts: System + Balances {} -/// Arguments for uploading contract code to the chain -#[derive(Encode)] -pub struct PutCodeArgs { - #[codec(compact)] - gas_limit: Gas, - code: Vec, -} - -/// Arguments for creating an instance of a contract -#[derive(Encode)] -pub struct InstantiateArgs { - #[codec(compact)] - endowment: ::Balance, - #[codec(compact)] - gas_limit: Gas, - code_hash: ::Hash, - data: Vec, -} - -/// Arguments for calling a contract -#[derive(Encode)] -pub struct CallArgs { - dest: ::Address, - value: ::Balance, - #[codec(compact)] - gas_limit: Gas, - data: Vec, -} - /// Stores the given binary Wasm code into the chain's storage and returns /// its `codehash`. /// You can instantiate contracts only with stored code. -pub fn put_code(gas_limit: Gas, code: Vec) -> Call { - Call::new(MODULE, calls::PUT_CODE, PutCodeArgs { gas_limit, code }) +#[derive(Debug, Encode)] +pub struct PutCodeCall<'a> { + /// Gas limit. + #[codec(compact)] + pub gas_limit: Gas, + /// 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`, @@ -93,22 +66,23 @@ pub fn put_code(gas_limit: Gas, code: Vec) -> Call { /// of the account. That code will be invoked upon any call received by /// this account. /// - The contract is initialized. -pub fn instantiate( - endowment: ::Balance, - gas_limit: Gas, - code_hash: ::Hash, - data: Vec, -) -> Call> { - Call::new( - MODULE, - calls::INSTANTIATE, - InstantiateArgs { - endowment, - gas_limit, - code_hash, - data, - }, - ) +#[derive(Debug, Encode)] +pub struct InstantiateCall<'a, T: Contracts> { + /// Initial balance transfered to the contract. + #[codec(compact)] + pub endowment: ::Balance, + /// Gas limit. + #[codec(compact)] + pub gas_limit: Gas, + /// Code hash returned by the put_code call. + pub code_hash: &'a ::Hash, + /// Data to initialize the contract with. + 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. @@ -119,22 +93,46 @@ pub fn instantiate( /// * 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. -pub fn call( - dest: ::Address, - value: ::Balance, - gas_limit: Gas, - data: Vec, -) -> Call> { - Call::new( - MODULE, - calls::CALL, - CallArgs { - dest, - value, - gas_limit, - data, - }, - ) +#[derive(Debug, Encode)] +pub struct CallCall<'a, T: Contracts> { + /// Address of the contract. + pub dest: &'a ::Address, + /// Value to transfer to the contract. + pub value: ::Balance, + /// Gas limit. + #[codec(compact)] + pub gas_limit: Gas, + /// Data to send to the contract. + 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)] +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"; } #[cfg(test)] @@ -147,22 +145,16 @@ mod tests { Verify, }; - use super::events; + use super::*; use crate::{ - frame::contracts::MODULE, tests::test_client, - Balances, Client, - DefaultNodeRuntime as Runtime, Error, - System, }; - type AccountId = ::AccountId; - async fn put_code(client: &Client, signer: P) -> Result where - T: System + Balances + Send + Sync, + T: Contracts + Send + Sync, T::Address: From, P: Pair, P::Signature: Codec, @@ -179,10 +171,17 @@ mod tests { let xt = client.xt(signer, None).await?; - let result = xt.watch().submit(super::put_code(500_000, wasm)).await?; + let result = xt + .watch() + .submit(PutCodeCall { + gas_limit: 500_000, + code: &wasm, + }) + .await?; let code_hash = result - .find_event::(MODULE, events::CODE_STORED) - .ok_or(Error::Other("Failed to find CodeStored event".into()))??; + .find_event::>()? + .ok_or(Error::Other("Failed to find CodeStored event".into()))? + .code_hash; Ok(code_hash) } @@ -219,16 +218,16 @@ mod tests { let xt = client.xt(signer, None).await?; let result = xt .watch() - .submit(super::instantiate::( - 100_000_000_000_000, - 500_000, - code_hash, - Vec::new(), - )) + .submit(InstantiateCall { + endowment: 100_000_000_000_000, + gas_limit: 500_000, + code_hash: &code_hash, + data: &[], + }) .await?; let event = result - .find_event::<(AccountId, AccountId)>(MODULE, events::INSTANTIATED) - .ok_or(Error::Other("Failed to find Instantiated event".into()))??; + .find_event::>()? + .ok_or(Error::Other("Failed to find Instantiated event".into()))?; Ok(event) }); diff --git a/src/frame/mod.rs b/src/frame/mod.rs index 7d753dbdedc..898031f753a 100644 --- a/src/frame/mod.rs +++ b/src/frame/mod.rs @@ -16,29 +16,64 @@ //! Implements support for built-in runtime modules. -use codec::Encode; +use crate::{ + events::{ + EventsDecoder, + EventsError, + }, + metadata::{ + Metadata, + MetadataError, + }, +}; +use codec::{ + Decode, + Encode, +}; +use sp_core::storage::StorageKey; pub mod balances; pub mod contracts; pub mod system; -/// Creates module calls -pub struct Call { - /// Module name - pub module: &'static str, - /// Function name - pub function: &'static str, - /// Call arguments - pub args: T, +/// Store trait. +pub trait Store: Encode { + /// Module name. + const MODULE: &'static str; + /// Field name. + const FIELD: &'static str; + /// Return type. + type Returns: Decode; + /// Returns the `StorageKey`. + fn key(&self, metadata: &Metadata) -> Result; + /// Returns the default value. + fn default( + &self, + metadata: &Metadata, + ) -> Result, MetadataError> { + Ok(metadata + .module(Self::MODULE)? + .storage(Self::FIELD)? + .default()) + } } -impl Call { - /// Create a module call - pub fn new(module: &'static str, function: &'static str, args: T) -> Self { - Call { - module, - function, - args, - } +/// Call trait. +pub trait Call: Encode { + /// Module name. + const MODULE: &'static str; + /// Function name. + const FUNCTION: &'static str; + /// Load event decoder. + fn events_decoder(_decoder: &mut EventsDecoder) -> Result<(), EventsError> { + Ok(()) } } + +/// Event trait. +pub trait Event: Decode { + /// Module name. + const MODULE: &'static str; + /// Event name. + const EVENT: &'static str; +} diff --git a/src/frame/system.rs b/src/frame/system.rs index 5fee8f5bc74..d3ed9005bc6 100644 --- a/src/frame/system.rs +++ b/src/frame/system.rs @@ -22,11 +22,8 @@ use codec::{ Encode, }; use frame_support::Parameter; -use futures::future::{ - self, - Future, -}; use serde::de::DeserializeOwned; +use sp_core::storage::StorageKey; use sp_runtime::{ traits::{ AtLeast32Bit, @@ -40,24 +37,21 @@ use sp_runtime::{ MaybeSerialize, MaybeSerializeDeserialize, Member, - SignedExtension, SimpleBitOps, }, RuntimeDebug, }; -use std::{ - fmt::Debug, - pin::Pin, -}; +use std::fmt::Debug; use crate::{ - error::Error, - extrinsic::SignedExtra, frame::{ - balances::Balances, Call, + Store, + }, + metadata::{ + Metadata, + MetadataError, }, - Client, }; /// The subset of the `frame::Trait` that a client must implement. @@ -116,7 +110,7 @@ pub trait System: 'static + Eq + Clone + Debug { + Default; /// The address type. This instead of `::Source`. - type Address: Codec + Clone + PartialEq + Debug; + type Address: Codec + Clone + PartialEq + Debug + Send + Sync; /// The block header. type Header: Parameter @@ -147,59 +141,33 @@ pub struct AccountInfo { pub data: T::AccountData, } -/// The System extension trait for the Client. -pub trait SystemStore { - /// System type. - type System: System; - - /// Returns the nonce and account data for an account_id. - fn account( - &self, - account_id: ::AccountId, - ) -> Pin, Error>> + Send>>; -} +const MODULE: &str = "System"; -impl SystemStore - for Client -where - E: SignedExtra + SignedExtension + 'static, -{ - type System = T; - - fn account( - &self, - account_id: ::AccountId, - ) -> Pin, Error>> + Send>> - { - let account_map = || { - Ok(self - .metadata - .module("System")? - .storage("Account")? - .get_map()?) - }; - let map = match account_map() { - Ok(map) => map, - Err(err) => return Box::pin(future::err(err)), - }; - let client = self.clone(); - Box::pin(async move { - client - .fetch_or(map.key(account_id), None, map.default()) - .await - }) +/// 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)) } } -const MODULE: &str = "System"; -const SET_CODE: &str = "set_code"; - /// Arguments for updating the runtime code -pub type SetCode = Vec; +#[derive(Encode)] +pub struct SetCodeCall<'a>(pub &'a Vec); -/// Sets the new code. -pub fn set_code(code: Vec) -> Call { - Call::new(MODULE, SET_CODE, code) +impl<'a, T: System> Call for SetCodeCall<'a> { + const MODULE: &'static str = MODULE; + const FUNCTION: &'static str = "set_code"; } use frame_support::weights::DispatchInfo; diff --git a/src/lib.rs b/src/lib.rs index c47c6b0919c..61cbe385125 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,6 @@ use std::{ use codec::{ Codec, - Decode, Encode, }; use futures::future; @@ -80,32 +79,35 @@ mod metadata; mod rpc; mod runtimes; -pub use self::{ +pub use crate::{ error::Error, - events::RawEvent, + events::{ + EventsDecoder, + EventsError, + RawEvent, + }, extrinsic::*, frame::*, + metadata::{ + Metadata, + MetadataError, + }, rpc::{ BlockNumber, ExtrinsicSuccess, }, runtimes::*, }; -use self::{ - events::{ - EventsDecoder, - EventsError, - }, +use crate::{ frame::{ balances::Balances, system::{ + AccountStore, Phase, System, SystemEvent, - SystemStore, }, }, - metadata::Metadata, rpc::{ ChainBlock, Rpc, @@ -117,6 +119,7 @@ use self::{ pub struct ClientBuilder> { _marker: std::marker::PhantomData<(T, S, E)>, url: Option, + client: Option, } impl ClientBuilder { @@ -125,19 +128,39 @@ impl ClientBuilder { Self { _marker: std::marker::PhantomData, url: None, + client: None, } } + /// Sets the jsonrpsee client. + pub fn set_client>(mut self, client: P) -> Self { + self.client = Some(client.into()); + self + } + /// Set the substrate rpc address. - pub fn set_url(mut self, url: &str) -> Self { - self.url = Some(url.to_string()); + pub fn set_url>(mut self, url: P) -> Self { + self.url = Some(url.into()); self } /// Creates a new Client. pub async fn build(self) -> Result, Error> { - let url = self.url.unwrap_or("ws://127.0.0.1:9944".to_string()); - let rpc = Rpc::connect_ws(&url).await?; + let client = if let Some(client) = self.client { + client + } else { + let url = self + .url + .as_ref() + .map(|s| &**s) + .unwrap_or("ws://127.0.0.1:9944"); + if url.starts_with("ws://") || url.starts_with("wss://") { + jsonrpsee::ws_client(url).await? + } else { + jsonrpsee::http_client(url) + } + }; + let rpc = Rpc::new(client).await?; let (metadata, genesis_hash, runtime_version) = future::join3( rpc.metadata(), @@ -176,43 +199,25 @@ impl Clone for Client { } } -impl Client -where - E: SignedExtra + SignedExtension + 'static, -{ +impl Client { /// Returns the chain metadata. pub fn metadata(&self) -> &Metadata { &self.metadata } /// Fetch a StorageKey. - pub async fn fetch( - &self, - key: StorageKey, - hash: Option, - ) -> Result, Error> { - self.rpc.storage::(key, hash).await - } - - /// Fetch a StorageKey or return the default. - pub async fn fetch_or( + pub async fn fetch>( &self, - key: StorageKey, + store: F, hash: Option, - default: V, - ) -> Result { - let result = self.fetch(key, hash).await?; - Ok(result.unwrap_or(default)) - } - - /// Fetch a StorageKey or return the default. - pub async fn fetch_or_default( - &self, - key: StorageKey, - hash: Option, - ) -> Result { - let result = self.fetch(key, hash).await?; - Ok(result.unwrap_or_default()) + ) -> Result, Error> { + let key = store.key(&self.metadata)?; + let value = self.rpc.storage::(key, hash).await?; + if let Some(v) = value { + Ok(Some(v)) + } else { + Ok(store.default(&self.metadata)?) + } } /// Query historical storage entries @@ -301,24 +306,31 @@ where let headers = self.rpc.subscribe_finalized_blocks().await?; Ok(headers) } +} +impl Client +where + T: System + Balances + Send + Sync, + S: 'static, + E: SignedExtra + SignedExtension + 'static, +{ /// Creates raw payload to be signed for the supplied `Call` without private key - pub async fn create_raw_payload( + pub async fn create_raw_payload>( &self, - account_id: ::AccountId, - call: Call, - ) -> Result< - SignedPayload>::Extra>, - Error - > - { - let account_nonce = self.account(account_id).await?.nonce; + account_id: &::AccountId, + call: C, + ) -> Result>::Extra>, Error> { + let account_nonce = self + .fetch(AccountStore(account_id), None) + .await? + .unwrap() + .nonce; let version = self.runtime_version.spec_version; let genesis_hash = self.genesis_hash; let call = self .metadata() - .module_with_calls(&call.module) - .and_then(|module| module.call(&call.function, call.args))?; + .module_with_calls(C::MODULE) + .and_then(|module| module.call(C::FUNCTION, call))?; let extra: E = E::new(version, account_nonce, genesis_hash); let raw_payload = SignedPayload::new(call, extra.extra())?; Ok(raw_payload) @@ -339,7 +351,12 @@ where let account_id = S::Signer::from(signer.public()).into_account(); let nonce = match nonce { Some(nonce) => nonce, - None => self.account(account_id).await?.nonce, + None => { + self.fetch(AccountStore(&account_id), None) + .await? + .unwrap() + .nonce + } }; let genesis_hash = self.genesis_hash; @@ -364,11 +381,7 @@ pub struct XtBuilder { signer: P, } -impl XtBuilder -where - P: Pair, - E: SignedExtra + SignedExtension + 'static, -{ +impl XtBuilder { /// Returns the chain metadata. pub fn metadata(&self) -> &Metadata { self.client.metadata() @@ -392,7 +405,7 @@ where } } -impl XtBuilder +impl XtBuilder where P: Pair, S: Verify + Codec + From, @@ -401,24 +414,21 @@ where E: SignedExtra + SignedExtension + 'static, { /// Creates and signs an Extrinsic for the supplied `Call` - pub fn create_and_sign( + pub fn create_and_sign>( &self, - call: Call, + call: C, ) -> Result< UncheckedExtrinsic>::Extra>, Error, - > - where - C: codec::Encode, - { + > { let signer = self.signer.clone(); let account_nonce = self.nonce; let version = self.runtime_version.spec_version; let genesis_hash = self.genesis_hash; let call = self .metadata() - .module_with_calls(&call.module) - .and_then(|module| module.call(&call.function, call.args))?; + .module_with_calls(C::MODULE) + .and_then(|module| module.call(C::FUNCTION, call))?; log::info!( "Creating Extrinsic with genesis hash {:?} and account nonce {:?}", @@ -432,7 +442,7 @@ where } /// Submits a transaction to the chain. - pub async fn submit(&self, call: Call) -> Result { + pub async fn submit>(&self, call: C) -> Result { let extrinsic = self.create_and_sign(call)?; let xt_hash = self.client.submit_extrinsic(extrinsic).await?; Ok(xt_hash) @@ -457,15 +467,7 @@ pub struct EventsSubscriber { decoder: Result, EventsError>, } -impl - EventsSubscriber -where - P: Pair, - S: Verify + Codec + From, - S::Signer: From + IdentifyAccount, - T::Address: From, - E: SignedExtra + SignedExtension + 'static, -{ +impl EventsSubscriber { /// Access the events decoder for registering custom type sizes pub fn events_decoder< F: FnOnce(&mut EventsDecoder) -> Result, @@ -481,13 +483,20 @@ where } this } +} +impl EventsSubscriber +where + P: Pair, + S: Verify + Codec + From, + S::Signer: From + IdentifyAccount, + T::Address: From, + E: SignedExtra + SignedExtension + 'static, +{ /// Submits transaction to the chain and watch for events. - pub async fn submit( - self, - call: Call, - ) -> Result, Error> { - let decoder = self.decoder?; + pub async fn submit>(self, call: C) -> Result, Error> { + let mut decoder = self.decoder?; + C::events_decoder(&mut decoder)?; let extrinsic = self.builder.create_and_sign(call)?; let xt_success = self .client @@ -516,143 +525,131 @@ mod tests { }; use super::*; - use crate::{ - DefaultNodeRuntime as Runtime, - Error, - }; - pub(crate) async fn test_client() -> Client { - ClientBuilder::::new() + pub(crate) async fn test_client() -> Client { + ClientBuilder::new() .build() .await .expect("Error creating client") } - #[test] + #[async_std::test] #[ignore] // requires locally running substrate node - fn test_tx_transfer_balance() { + async fn test_tx_transfer_balance() { env_logger::try_init().ok(); - let transfer = async_std::task::block_on(async move { - let signer = AccountKeyring::Alice.pair(); - let dest = AccountKeyring::Bob.to_account_id(); - - let client = test_client().await; - let mut xt = client.xt(signer, None).await?; - let _ = xt - .submit(balances::transfer::(dest.clone().into(), 10_000)) - .await?; - - // check that nonce is handled correctly - xt.increment_nonce() - .submit(balances::transfer::(dest.clone().into(), 10_000)) - .await - }); - - assert!(transfer.is_ok()) + let signer = AccountKeyring::Alice.pair(); + let dest = AccountKeyring::Bob.to_account_id().into(); + + let client = test_client().await; + let mut xt = client.xt(signer, None).await.unwrap(); + let _ = xt + .submit(balances::TransferCall { + to: &dest, + amount: 10_000, + }) + .await + .unwrap(); + + // check that nonce is handled correctly + xt.increment_nonce() + .submit(balances::TransferCall { + to: &dest, + amount: 10_000, + }) + .await + .unwrap(); } - #[test] + #[async_std::test] #[ignore] // requires locally running substrate node - fn test_getting_hash() { - let result: Result<_, Error> = async_std::task::block_on(async move { - let client = test_client().await; - let block_hash = client.block_hash(None).await?; - Ok(block_hash) - }); - - assert!(result.is_ok()) + async fn test_getting_hash() { + let client = test_client().await; + client.block_hash(None).await.unwrap(); } - #[test] + #[async_std::test] #[ignore] // requires locally running substrate node - fn test_getting_block() { - let result: Result<_, Error> = async_std::task::block_on(async move { - let client = test_client().await; - let block_hash = client.block_hash(None).await?; - let block = client.block(block_hash).await?; - Ok(block) - }); - - assert!(result.is_ok()) + async fn test_getting_block() { + let client = test_client().await; + let block_hash = client.block_hash(None).await.unwrap(); + client.block(block_hash).await.unwrap(); } - #[test] + #[async_std::test] #[ignore] // requires locally running substrate node - fn test_state_read_free_balance() { - let result: Result<_, Error> = async_std::task::block_on(async move { - let account = AccountKeyring::Alice.to_account_id(); - let client = test_client().await; - let balance = client.account(account.into()).await?.data.free; - Ok(balance) - }); - - assert!(result.is_ok()) + async fn test_state_total_issuance() { + let client = test_client().await; + client + .fetch(balances::TotalIssuance(Default::default()), None) + .await + .unwrap() + .unwrap(); } - #[test] + #[async_std::test] #[ignore] // requires locally running substrate node - fn test_chain_subscribe_blocks() { - let result: Result<_, Error> = async_std::task::block_on(async move { - let client = test_client().await; - let mut blocks = client.subscribe_blocks().await?; - let block = blocks.next().await; - Ok(block) - }); - - assert!(result.is_ok()) + 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(); } - #[test] + #[async_std::test] #[ignore] // requires locally running substrate node - fn test_chain_subscribe_finalized_blocks() { - let result: Result<_, Error> = async_std::task::block_on(async move { - let client = test_client().await; - let mut blocks = client.subscribe_finalized_blocks().await?; - let block = blocks.next().await; - Ok(block) - }); + async fn test_chain_subscribe_blocks() { + let client = test_client().await; + let mut blocks = client.subscribe_blocks().await.unwrap(); + blocks.next().await; + } - assert!(result.is_ok()) + #[async_std::test] + #[ignore] // requires locally running substrate node + async fn test_chain_subscribe_finalized_blocks() { + let client = test_client().await; + let mut blocks = client.subscribe_finalized_blocks().await.unwrap(); + blocks.next().await; } - #[test] + #[async_std::test] #[ignore] // requires locally running substrate node - fn test_create_raw_payload() { - let result: Result<_, Error> = async_std::task::block_on(async move { - let signer_pair = Ed25519Keyring::Alice.pair(); - let signer_account_id = Ed25519Keyring::Alice.to_account_id(); - let dest = AccountKeyring::Bob.to_account_id(); - - let client = test_client().await; - - // create raw payload with AccoundId and sign it - let raw_payload = client - .create_raw_payload( - signer_account_id, - balances::transfer::(dest.clone().into(), 10_000), - ) - .await?; - let raw_signature = - signer_pair.sign(raw_payload.encode().as_slice()); - let raw_multisig = MultiSignature::from(raw_signature); - - // create signature with Xtbuilder - let xt = client.xt(signer_pair.clone(), None).await?; - let xt_multi_sig = xt - .create_and_sign(balances::transfer::( - dest.clone().into(), - 10_000, - ))? - .signature - .unwrap() - .1; - - // compare signatures - assert_eq!(raw_multisig, xt_multi_sig); - - Ok(()) - }); - - assert!(result.is_ok()) + async fn test_create_raw_payload() { + let signer_pair = Ed25519Keyring::Alice.pair(); + let signer_account_id = Ed25519Keyring::Alice.to_account_id(); + let dest = AccountKeyring::Bob.to_account_id().into(); + + let client = test_client().await; + + // create raw payload with AccoundId and sign it + let raw_payload = client + .create_raw_payload( + &signer_account_id, + balances::TransferCall { + to: &dest, + amount: 10_000, + }, + ) + .await + .unwrap(); + let raw_signature = signer_pair.sign(raw_payload.encode().as_slice()); + let raw_multisig = MultiSignature::from(raw_signature); + + // create signature with Xtbuilder + let xt = client.xt(signer_pair.clone(), None).await.unwrap(); + let xt_multi_sig = xt + .create_and_sign(balances::TransferCall { + to: &dest, + amount: 10_000, + }) + .unwrap() + .signature + .unwrap() + .1; + + // compare signatures + assert_eq!(raw_multisig, xt_multi_sig); } } diff --git a/src/metadata.rs b/src/metadata.rs index ac60b97a89d..c4dc6c4562f 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -39,26 +39,33 @@ use sp_core::storage::StorageKey; use crate::Encoded; +/// Metadata error. #[derive(Debug, thiserror::Error)] pub enum MetadataError { + /// Failed to parse metadata. #[error("Error converting substrate metadata: {0}")] Conversion(#[from] ConversionError), - #[error("Module not found")] + /// Module is not in metadata. + #[error("Module {0} not found")] ModuleNotFound(String), - #[error("Module with events not found")] - ModuleWithEventsNotFound(u8), - #[error("Call not found")] + /// Module is not in metadata. + #[error("Module index {0} not found")] + ModuleIndexNotFound(u8), + /// Call is not in metadata. + #[error("Call {0} not found")] CallNotFound(&'static str), - #[error("Event not found")] + /// Event is not in metadata. + #[error("Event {0} not found")] EventNotFound(u8), - #[error("Storage not found")] + /// Storage is not in metadata. + #[error("Storage {0} not found")] StorageNotFound(&'static str), + /// Storage type does not match requested type. #[error("Storage type error")] StorageTypeError, - #[error("Map value type error")] - MapValueTypeError, } +/// Runtime metadata. #[derive(Clone, Debug)] pub struct Metadata { modules: HashMap, @@ -67,6 +74,7 @@ pub struct Metadata { } impl Metadata { + /// Returns `ModuleMetadata`. pub fn module(&self, name: S) -> Result<&ModuleMetadata, MetadataError> where S: ToString, @@ -77,7 +85,10 @@ impl Metadata { .ok_or(MetadataError::ModuleNotFound(name)) } - pub fn module_with_calls(&self, name: S) -> Result<&ModuleWithCalls, MetadataError> + pub(crate) fn module_with_calls( + &self, + name: S, + ) -> Result<&ModuleWithCalls, MetadataError> where S: ToString, { @@ -87,20 +98,21 @@ impl Metadata { .ok_or(MetadataError::ModuleNotFound(name)) } - pub fn modules_with_events(&self) -> impl Iterator { + pub(crate) fn modules_with_events(&self) -> impl Iterator { self.modules_with_events.values() } - pub fn module_with_events( + pub(crate) fn module_with_events( &self, module_index: u8, ) -> Result<&ModuleWithEvents, MetadataError> { self.modules_with_events .values() .find(|&module| module.index == module_index) - .ok_or(MetadataError::ModuleWithEventsNotFound(module_index)) + .ok_or(MetadataError::ModuleIndexNotFound(module_index)) } + /// Pretty print metadata. pub fn pretty(&self) -> String { let mut string = String::new(); for (name, module) in &self.modules { @@ -200,65 +212,125 @@ pub struct StorageMetadata { } impl StorageMetadata { - pub fn get_map( - &self, - ) -> Result, MetadataError> { + pub fn prefix(&self) -> Vec { + let mut bytes = sp_core::twox_128(self.module_prefix.as_bytes()).to_vec(); + bytes.extend(&sp_core::twox_128(self.storage_prefix.as_bytes())[..]); + 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 hash(hasher: &StorageHasher, bytes: &[u8]) -> Vec { + match hasher { + StorageHasher::Identity => bytes.to_vec(), + StorageHasher::Blake2_128 => sp_core::blake2_128(bytes).to_vec(), + StorageHasher::Blake2_128Concat => { + // copied from substrate Blake2_128Concat::hash since StorageHasher is not public + sp_core::blake2_128(bytes) + .iter() + .chain(bytes) + .cloned() + .collect() + } + StorageHasher::Blake2_256 => sp_core::blake2_256(bytes).to_vec(), + StorageHasher::Twox128 => sp_core::twox_128(bytes).to_vec(), + StorageHasher::Twox256 => sp_core::twox_256(bytes).to_vec(), + StorageHasher::Twox64Concat => sp_core::twox_64(bytes).to_vec(), + } + } + + pub fn hash_key(hasher: &StorageHasher, key: &K) -> Vec { + Self::hash(hasher, &key.encode()) + } + + pub fn plain(&self) -> Result { + match &self.ty { + StorageEntryType::Plain(_) => { + Ok(StoragePlain { + prefix: self.prefix(), + }) + } + _ => Err(MetadataError::StorageTypeError), + } + } + + pub fn map(&self) -> Result, MetadataError> { match &self.ty { StorageEntryType::Map { hasher, .. } => { - let module_prefix = self.module_prefix.as_bytes().to_vec(); - let storage_prefix = self.storage_prefix.as_bytes().to_vec(); - let hasher = hasher.to_owned(); - let default = Decode::decode(&mut &self.default[..]) - .map_err(|_| MetadataError::MapValueTypeError)?; Ok(StorageMap { _marker: PhantomData, - module_prefix, - storage_prefix, - hasher, - default, + prefix: self.prefix(), + hasher: hasher.clone(), }) } _ => Err(MetadataError::StorageTypeError), } } + + pub fn double_map( + &self, + ) -> Result, MetadataError> { + match &self.ty { + StorageEntryType::DoubleMap { + hasher, + key2_hasher, + .. + } => { + Ok(StorageDoubleMap { + _marker: PhantomData, + prefix: self.prefix(), + hasher1: hasher.clone(), + hasher2: key2_hasher.clone(), + }) + } + _ => Err(MetadataError::StorageTypeError), + } + } +} + +#[derive(Clone, Debug)] +pub struct StoragePlain { + prefix: Vec, +} + +impl StoragePlain { + pub fn key(&self) -> StorageKey { + StorageKey(self.prefix.clone()) + } } #[derive(Clone, Debug)] -pub struct StorageMap { +pub struct StorageMap { _marker: PhantomData, - module_prefix: Vec, - storage_prefix: Vec, + prefix: Vec, hasher: StorageHasher, - default: V, } -impl StorageMap { - pub fn key(&self, key: K) -> StorageKey { - let mut bytes = sp_core::twox_128(&self.module_prefix).to_vec(); - bytes.extend(&sp_core::twox_128(&self.storage_prefix)[..]); - let encoded_key = key.encode(); - let hash = match self.hasher { - StorageHasher::Identity => encoded_key.to_vec(), - StorageHasher::Blake2_128 => sp_core::blake2_128(&encoded_key).to_vec(), - StorageHasher::Blake2_128Concat => { - // copied from substrate Blake2_128Concat::hash since StorageHasher is not public - sp_core::blake2_128(&encoded_key) - .iter() - .chain(&encoded_key) - .cloned() - .collect::>() - } - StorageHasher::Blake2_256 => sp_core::blake2_256(&encoded_key).to_vec(), - StorageHasher::Twox128 => sp_core::twox_128(&encoded_key).to_vec(), - StorageHasher::Twox256 => sp_core::twox_256(&encoded_key).to_vec(), - StorageHasher::Twox64Concat => sp_core::twox_64(&encoded_key).to_vec(), - }; - bytes.extend(hash); +impl StorageMap { + pub fn key(&self, key: &K) -> StorageKey { + let mut bytes = self.prefix.clone(); + bytes.extend(StorageMetadata::hash_key(&self.hasher, key)); StorageKey(bytes) } +} + +#[derive(Clone, Debug)] +pub struct StorageDoubleMap { + _marker: PhantomData<(K1, K2)>, + prefix: Vec, + hasher1: StorageHasher, + hasher2: StorageHasher, +} - pub fn default(&self) -> V { - self.default.clone() +impl StorageDoubleMap { + pub fn key(&self, key1: &K1, key2: &K2) -> StorageKey { + let mut bytes = self.prefix.clone(); + bytes.extend(StorageMetadata::hash_key(&self.hasher1, key1)); + bytes.extend(StorageMetadata::hash_key(&self.hasher2, key2)); + StorageKey(bytes) } } diff --git a/src/rpc.rs b/src/rpc.rs index 04acb4669ab..b2d71837104 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -71,12 +71,12 @@ use crate::{ RuntimeEvent, }, frame::{ - balances::Balances, system::{ Phase, System, SystemEvent, }, + Event, }, metadata::Metadata, }; @@ -115,14 +115,10 @@ pub struct Rpc { marker: std::marker::PhantomData, } -impl Rpc -where - T: System, -{ - pub async fn connect_ws(url: &str) -> Result { - let client = jsonrpsee::ws_client(&url).await?; +impl Rpc { + pub async fn new(client: Client) -> Result { Ok(Rpc { - client: client.into(), + client, marker: PhantomData, }) } @@ -140,6 +136,7 @@ where self.client.request("state_getStorage", params).await?; match data { Some(data) => { + log::debug!("state_getStorage {:?}", data.0); let value = Decode::decode(&mut &data.0[..])?; Ok(Some(value)) } @@ -247,9 +244,7 @@ where .await?; Ok(version) } -} -impl Rpc { /// Subscribe to substrate System Events pub async fn subscribe_events( &self, @@ -432,19 +427,18 @@ impl ExtrinsicSuccess { /// Find the Event for the given module/variant, attempting to decode the event data. /// Returns `None` if the Event is not found. - /// Returns `Err` if the data fails to decode into the supplied type - pub fn find_event( - &self, - module: &str, - variant: &str, - ) -> Option> { - self.find_event_raw(module, variant) - .map(|evt| E::decode(&mut &evt.data[..])) + /// Returns `Err` if the data fails to decode into the supplied type. + pub fn find_event>(&self) -> Result, CodecError> { + if let Some(event) = self.find_event_raw(E::MODULE, E::EVENT) { + Ok(Some(E::decode(&mut &event.data[..])?)) + } else { + Ok(None) + } } } /// Waits for events for the block triggered by the extrinsic -pub async fn wait_for_block_events( +pub async fn wait_for_block_events( decoder: EventsDecoder, ext_hash: T::Hash, signed_block: ChainBlock,