diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 20cd48ddc4..48d6fd2823 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -16,8 +16,7 @@ jobs: - name: setup run: | - rustup install nightly - rustup component add rustfmt --toolchain nightly + rustup install nightly --profile default - name: fmt run: cargo +nightly fmt --all -- --check diff --git a/examples/kusama_balance_transfer.rs b/examples/kusama_balance_transfer.rs index 646edf4e7f..dba35b8771 100644 --- a/examples/kusama_balance_transfer.rs +++ b/examples/kusama_balance_transfer.rs @@ -19,22 +19,18 @@ use substrate_subxt::{ balances::*, ClientBuilder, KusamaRuntime, + PairSigner, }; #[async_std::main] async fn main() -> Result<(), Box> { env_logger::init(); - let signer = AccountKeyring::Alice.pair(); + let signer = PairSigner::new(AccountKeyring::Alice.pair()); let dest = AccountKeyring::Bob.to_account_id().into(); let client = ClientBuilder::::new().build().await?; - - let hash = client - .xt(signer, None) - .await? - .transfer(&dest, 10_000) - .await?; + let hash = client.transfer(&signer, &dest, 10_000).await?; println!("Balance transfer extrinsic submitted: {}", hash); diff --git a/examples/submit_and_watch.rs b/examples/submit_and_watch.rs index ede981bc29..81701f7383 100644 --- a/examples/submit_and_watch.rs +++ b/examples/submit_and_watch.rs @@ -19,22 +19,18 @@ use substrate_subxt::{ balances::*, ClientBuilder, DefaultNodeRuntime, + PairSigner, }; #[async_std::main] async fn main() -> Result<(), Box> { env_logger::init(); - let signer = AccountKeyring::Alice.pair(); + let signer = PairSigner::new(AccountKeyring::Alice.pair()); let dest = AccountKeyring::Bob.to_account_id().into(); let client = ClientBuilder::::new().build().await?; - let result = client - .xt(signer, None) - .await? - .watch() - .transfer(&dest, 10_000) - .await?; + let result = client.transfer_and_watch(&signer, &dest, 10_000).await?; if let Some(event) = result.transfer()? { println!("Balance transfer success: value: {:?}", event.amount); diff --git a/proc-macro/src/call.rs b/proc-macro/src/call.rs index 9ccb419b87..234edbd8dd 100644 --- a/proc-macro/src/call.rs +++ b/proc-macro/src/call.rs @@ -28,6 +28,7 @@ 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 ident = &s.ast().ident; let generics = &s.ast().generics; let params = utils::type_params(generics); @@ -43,26 +44,9 @@ pub fn call(s: Structure) -> TokenStream { 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 call_trait = format_ident!("{}CallExt", call_name.to_camel_case()); + let call = format_ident!("{}", call_name); + let call_and_watch = format_ident!("{}_and_watch", call_name); quote! { impl#generics #subxt::Call for #ident<#(#params),*> { @@ -76,52 +60,45 @@ 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. + pub trait #call_trait> { + /// Create and submit an extrinsic. fn #call<'a>( - #me, + &'a self, + signer: &'a (dyn #subxt::Signer + Send + Sync), #args - ) -> core::pin::Pin> + Send + 'a>>; + ) -> core::pin::Pin> + Send + 'a>>; + + /// Create, submit and watch an extrinsic. + fn #call_and_watch<'a>( + &'a self, + signer: &'a (dyn #subxt::Signer + Send + Sync), + #args + ) -> core::pin::Pin, #subxt::Error>> + Send + 'a>>; } - impl #call_trait for #subxt::#ty + impl #call_trait for #subxt::Client where 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 + #subxt::sp_runtime::traits::SignedExtension + 'static, + S: #codec::Encode + Send + Sync + 'static, + E: #subxt::SignedExtra + #subxt::sp_runtime::traits::SignedExtension + Send + Sync + 'static, { fn #call<'a>( - #me, + &'a self, + signer: &'a (dyn #subxt::Signer + Send + Sync), #args - ) -> core::pin::Pin> + Send + 'a>> { + ) -> core::pin::Pin> + Send + 'a>> { let #marker = core::marker::PhantomData::; - Box::pin(self.submit(#build_struct)) + Box::pin(self.submit(#build_struct, signer)) + } + + fn #call_and_watch<'a>( + &'a self, + signer: &'a (dyn #subxt::Signer + Send + Sync), + #args + ) -> core::pin::Pin, #subxt::Error>> + Send + 'a>> { + let #marker = core::marker::PhantomData::; + Box::pin(self.watch(#build_struct, signer)) } } } @@ -154,62 +131,48 @@ mod tests { } /// Call extension trait. - pub trait TransferCallXtBuilder { - /// Create and submit the extrinsic. + pub trait TransferCallExt> { + /// Create and submit an extrinsic. fn transfer<'a>( &'a self, + signer: &'a (dyn substrate_subxt::Signer + Send + Sync), 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>( + /// Create, submit and watch an extrinsic. + fn transfer_and_watch<'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, + signer: &'a (dyn substrate_subxt::Signer + Send + Sync), to: &'a ::Address, amount: T::Balance, ) -> core::pin::Pin, substrate_subxt::Error>> + Send + 'a>>; } - impl TransferCallEventsSubscriber for substrate_subxt::EventsSubscriber + impl TransferCallExt for substrate_subxt::Client 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, + S: codec::Encode + Send + Sync + 'static, + E: substrate_subxt::SignedExtra + substrate_subxt::sp_runtime::traits::SignedExtension + Send + Sync + 'static, { fn transfer<'a>( - self, + &'a self, + signer: &'a (dyn substrate_subxt::Signer + Send + Sync), + to: &'a ::Address, + amount: T::Balance, + ) -> core::pin::Pin> + Send + 'a>> { + let _ = core::marker::PhantomData::; + Box::pin(self.submit(TransferCall { to, amount, }, signer)) + } + + fn transfer_and_watch<'a>( + &'a self, + signer: &'a (dyn substrate_subxt::Signer + Send + Sync), 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, })) + Box::pin(self.watch(TransferCall { to, amount, }, signer)) } } }; diff --git a/proc-macro/src/test.rs b/proc-macro/src/test.rs index 5a8f7a29d1..f625ad68c6 100644 --- a/proc-macro/src/test.rs +++ b/proc-macro/src/test.rs @@ -228,7 +228,7 @@ impl Test { let prelude = prelude.map(|block| block.stmts).unwrap_or_default(); let step = steps .into_iter() - .map(|step| step.into_tokens(&account, state.as_ref())); + .map(|step| step.into_tokens(state.as_ref())); quote! { #[async_std::test] #[ignore] @@ -236,6 +236,8 @@ impl Test { #env_logger let client = #subxt::ClientBuilder::<#runtime, #signature, #extra>::new() .build().await.unwrap(); + let signer = #subxt::PairSigner::new(#sp_keyring::AccountKeyring::#account.pair()); + #[allow(unused)] let alice = #sp_keyring::AccountKeyring::Alice.to_account_id(); #[allow(unused)] @@ -304,12 +306,7 @@ impl From for Step { } impl Step { - fn into_tokens( - self, - account: &syn::Ident, - test_state: Option<&State>, - ) -> TokenStream { - let sp_keyring = utils::use_crate("sp-keyring"); + fn into_tokens(self, test_state: Option<&State>) -> TokenStream { let Step { state, call, @@ -359,14 +356,11 @@ impl Step { }); let assert = assert.map(|block| block.stmts).unwrap_or_default(); quote! { - let xt = client.xt(#sp_keyring::AccountKeyring::#account.pair(), None).await.unwrap(); - #pre #[allow(unused)] - let result = xt - .watch() - .submit(#call) + let result = client + .watch(#call, &signer) .await .unwrap(); @@ -461,6 +455,7 @@ mod tests { substrate_subxt::sp_runtime::MultiSignature, substrate_subxt::DefaultExtra >::new().build().await.unwrap(); + let signer = substrate_subxt::PairSigner::new(sp_keyring::AccountKeyring::Alice.pair()); #[allow(unused)] let alice = sp_keyring::AccountKeyring::Alice.to_account_id(); #[allow(unused)] @@ -475,8 +470,6 @@ mod tests { 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, @@ -495,12 +488,11 @@ mod tests { }; #[allow(unused)] - let result = xt - .watch() - .submit(TransferCall { + let result = client + .watch(TransferCall { to: &bob, amount: 10_000, - }) + }, &signer) .await .unwrap(); diff --git a/proc-macro/tests/balances.rs b/proc-macro/tests/balances.rs index a600277c16..062d4be74f 100644 --- a/proc-macro/tests/balances.rs +++ b/proc-macro/tests/balances.rs @@ -36,6 +36,7 @@ use substrate_subxt::{ }, ClientBuilder, KusamaRuntime, + PairSigner, }; #[module] @@ -112,6 +113,7 @@ subxt_test!({ async fn transfer_balance_example() -> Result<(), Box> { env_logger::init(); let client = ClientBuilder::::new().build().await?; + let signer = PairSigner::new(AccountKeyring::Alice.pair()); let alice = AccountKeyring::Alice.to_account_id(); let bob = AccountKeyring::Bob.to_account_id(); @@ -119,13 +121,12 @@ async fn transfer_balance_example() -> Result<(), Box> { let bob_account = client.account(&bob).await?; let pre = (alice_account, bob_account); - let builder = client.xt(AccountKeyring::Alice.pair(), None).await?; + let _hash = client + .transfer(&signer, &bob.clone().into(), 10_000) + .await?; - let _hash = builder.transfer(&bob.clone().into(), 10_000).await?; - - let result = builder - .watch() - .transfer(&bob.clone().into(), 10_000) + let result = client + .transfer_and_watch(&signer, &bob.clone().into(), 10_000) .await?; assert_eq!( diff --git a/src/extrinsic.rs b/src/extra.rs similarity index 88% rename from src/extrinsic.rs rename to src/extra.rs index 8dedf70386..a7278c98a2 100644 --- a/src/extrinsic.rs +++ b/src/extra.rs @@ -15,7 +15,6 @@ // along with substrate-subxt. If not, see . use codec::{ - Codec, Decode, Encode, }; @@ -23,18 +22,9 @@ use core::{ fmt::Debug, marker::PhantomData, }; -use sp_core::Pair; use sp_runtime::{ - generic::{ - Era, - SignedPayload, - UncheckedExtrinsic, - }, - traits::{ - IdentifyAccount, - SignedExtension, - Verify, - }, + generic::Era, + traits::SignedExtension, transaction_validity::TransactionValidityError, }; @@ -289,32 +279,3 @@ impl SignedExtension self.extra().additional_signed() } } - -pub(crate) fn create_and_sign( - signer: P, - call: C, - extra: E, -) -> Result< - UncheckedExtrinsic>::Extra>, - TransactionValidityError, -> -where - P: Pair, - S: Verify + Codec + From, - S::Signer: From + IdentifyAccount, - C: Encode, - E: SignedExtra + SignedExtension, - T::Address: From, -{ - let raw_payload = SignedPayload::new(call, extra.extra())?; - let signature = raw_payload.using_encoded(|payload| signer.sign(payload)); - let (call, extra, _) = raw_payload.deconstruct(); - let account_id = S::Signer::from(signer.public()).into_account(); - - Ok(UncheckedExtrinsic::new_signed( - call, - account_id.into(), - signature.into(), - extra, - )) -} diff --git a/src/lib.rs b/src/lib.rs index 1c6bc8bc5e..eb7b7256a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,46 +45,36 @@ extern crate substrate_subxt_proc_macro; pub use sp_core; pub use sp_runtime; -use std::{ - convert::TryFrom, - marker::PhantomData, -}; - -use codec::{ - Codec, - Encode, -}; +use codec::Encode; use futures::future; use jsonrpsee::client::Subscription; use sc_rpc_api::state::ReadProof; -use sp_core::{ - storage::{ - StorageChangeSet, - StorageKey, - }, - Pair, +use sp_core::storage::{ + StorageChangeSet, + StorageKey, }; use sp_runtime::{ generic::{ SignedPayload, UncheckedExtrinsic, }, - traits::{ - IdentifyAccount, - SignedExtension, - Verify, - }, + traits::SignedExtension, MultiSignature, }; use sp_version::RuntimeVersion; +use std::{ + convert::TryFrom, + marker::PhantomData, +}; mod error; mod events; -mod extrinsic; +mod extra; mod frame; mod metadata; mod rpc; mod runtimes; +mod signer; pub use crate::{ error::Error, @@ -93,7 +83,7 @@ pub use crate::{ EventsError, RawEvent, }, - extrinsic::*, + extra::*, frame::*, metadata::{ Metadata, @@ -104,17 +94,15 @@ pub use crate::{ ExtrinsicSuccess, }, runtimes::*, + signer::*, substrate_subxt_proc_macro::*, }; use crate::{ - frame::{ - balances::Balances, - system::{ - AccountStoreExt, - Phase, - System, - SystemEvent, - }, + frame::system::{ + AccountStoreExt, + Phase, + System, + SystemEvent, }, rpc::{ ChainBlock, @@ -283,28 +271,6 @@ impl Client { Ok(proof) } - /// Create and submit an extrinsic and return corresponding Hash if successful - pub async fn submit_extrinsic( - &self, - extrinsic: X, - ) -> Result { - let xt_hash = self.rpc.submit_extrinsic(extrinsic).await?; - Ok(xt_hash) - } - - /// Create and submit an extrinsic and return corresponding Event if successful - pub async fn submit_and_watch_extrinsic( - self, - extrinsic: X, - decoder: EventsDecoder, - ) -> Result, Error> { - let success = self - .rpc - .submit_and_watch_extrinsic(extrinsic, decoder) - .await?; - Ok(success) - } - /// Subscribe to events. pub async fn subscribe_events( &self, @@ -330,17 +296,24 @@ impl Client { impl Client where - T: System + Balances + Send + Sync + 'static, - S: 'static, - E: SignedExtra + SignedExtension + 'static, + T: System + Send + Sync + 'static, + S: Encode + Send + Sync + 'static, + E: SignedExtra + SignedExtension + Send + Sync + 'static, { - /// Creates raw payload to be signed for the supplied `Call` without private key - pub async fn create_raw_payload>( + /// Creates an unsigned extrinsic. + /// + /// If `nonce` is `None` the nonce will be fetched from the chain. + pub async fn create_unsigned>( &self, - account_id: &::AccountId, call: C, + account_id: &::AccountId, + nonce: Option, ) -> Result>::Extra>, Error> { - let account_nonce = self.account(account_id).await?.nonce; + let account_nonce = if let Some(nonce) = nonce { + nonce + } else { + self.account(account_id).await?.nonce + }; let version = self.runtime_version.spec_version; let genesis_hash = self.genesis_hash; let call = self @@ -352,168 +325,77 @@ where Ok(raw_payload) } - /// Create a transaction builder for a private key. - pub async fn xt

( - &self, - signer: P, - nonce: Option, - ) -> Result, Error> - where - P: Pair, - P::Signature: Codec, - S: Verify, - S::Signer: From + IdentifyAccount, - { - let account_id = S::Signer::from(signer.public()).into_account(); - let nonce = match nonce { - Some(nonce) => nonce, - None => self.account(&account_id).await?.nonce, - }; - - let genesis_hash = self.genesis_hash; - let runtime_version = self.runtime_version.clone(); - Ok(XtBuilder { - client: self.clone(), - nonce, - runtime_version, - genesis_hash, - signer, - }) - } -} - -/// Transaction builder. -#[derive(Clone)] -pub struct XtBuilder { - client: Client, - nonce: T::Index, - runtime_version: RuntimeVersion, - genesis_hash: T::Hash, - signer: P, -} - -impl XtBuilder { - /// Returns the chain metadata. - pub fn metadata(&self) -> &Metadata { - self.client.metadata() - } - - /// Returns the nonce. - pub fn nonce(&self) -> T::Index { - self.nonce - } - - /// Sets the nonce to a new value. - pub fn set_nonce(&mut self, nonce: T::Index) -> &mut XtBuilder { - self.nonce = nonce; - self - } - - /// Increment the nonce - pub fn increment_nonce(&mut self) -> &mut XtBuilder { - self.set_nonce(self.nonce() + 1.into()); - self - } -} - -impl XtBuilder -where - P: Pair, - S: Verify + Codec + From, - S::Signer: From + IdentifyAccount, - T::Address: From, - E: SignedExtra + SignedExtension + 'static, -{ - /// Creates and signs an Extrinsic for the supplied `Call` - pub fn create_and_sign>( + /// Creates a signed extrinsic. + pub async fn create_signed>( &self, call: C, + signer: &(dyn Signer + Send + Sync), ) -> Result< UncheckedExtrinsic>::Extra>, Error, > { - 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(C::MODULE) - .and_then(|module| module.call(C::FUNCTION, call))?; + let unsigned = self + .create_unsigned(call, signer.account_id(), signer.nonce()) + .await?; + Ok(signer.sign(unsigned)) + } - log::info!( - "Creating Extrinsic with genesis hash {:?} and account nonce {:?}", - genesis_hash, - account_nonce - ); + /// Returns an events decoder for a call. + pub fn events_decoder>(&self) -> Result, Error> { + let metadata = self.metadata().clone(); + let mut decoder = EventsDecoder::try_from(metadata)?; + C::events_decoder(&mut decoder)?; + Ok(decoder) + } - let extra = E::new(version, account_nonce, genesis_hash); - let xt = extrinsic::create_and_sign::<_, _, _, S, _>(signer, call, extra)?; - Ok(xt) + /// Create and submit an extrinsic and return corresponding Hash if successful + pub async fn submit_extrinsic( + &self, + extrinsic: UncheckedExtrinsic< + T::Address, + Encoded, + S, + >::Extra, + >, + ) -> Result { + self.rpc.submit_extrinsic(extrinsic).await + } + + /// Create and submit an extrinsic and return corresponding Event if successful + pub async fn submit_and_watch_extrinsic( + &self, + extrinsic: UncheckedExtrinsic< + T::Address, + Encoded, + S, + >::Extra, + >, + decoder: EventsDecoder, + ) -> Result, Error> { + self.rpc + .submit_and_watch_extrinsic(extrinsic, decoder) + .await } /// Submits a transaction to the chain. - 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) + pub async fn submit>( + &self, + call: C, + signer: &(dyn Signer + Send + Sync), + ) -> Result { + let extrinsic = self.create_signed(call, signer).await?; + self.submit_extrinsic(extrinsic).await } /// Submits transaction to the chain and watch for events. - pub fn watch(self) -> EventsSubscriber { - let metadata = self.client.metadata().clone(); - let decoder = EventsDecoder::try_from(metadata).map_err(Into::into); - EventsSubscriber { - client: self.client.clone(), - builder: self, - decoder, - } - } -} - -/// Submits an extrinsic and subscribes to the triggered events -pub struct EventsSubscriber { - client: Client, - builder: XtBuilder, - decoder: Result, EventsError>, -} - -impl EventsSubscriber { - /// Access the events decoder for registering custom type sizes - pub fn events_decoder< - F: FnOnce(&mut EventsDecoder) -> Result, - >( - self, - f: F, - ) -> Self { - let mut this = self; - if let Ok(ref mut decoder) = this.decoder { - if let Err(err) = f(decoder) { - this.decoder = Err(err) - } - } - 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: 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 - .submit_and_watch_extrinsic(extrinsic, decoder) - .await?; - Ok(xt_success) + pub async fn watch>( + &self, + call: C, + signer: &(dyn Signer + Send + Sync), + ) -> Result, Error> { + let extrinsic = self.create_signed(call, signer).await?; + let decoder = self.events_decoder::()?; + self.submit_and_watch_extrinsic(extrinsic, decoder).await } } @@ -530,9 +412,12 @@ impl codec::Encode for Encoded { #[cfg(test)] mod tests { - use sp_core::storage::{ - well_known_keys, - StorageKey, + use sp_core::{ + storage::{ + well_known_keys, + StorageKey, + }, + Pair, }; use sp_keyring::{ AccountKeyring, @@ -552,25 +437,37 @@ mod tests { #[ignore] // requires locally running substrate node async fn test_tx_transfer_balance() { env_logger::try_init().ok(); - let signer = AccountKeyring::Alice.pair(); + let mut signer = PairSigner::new(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, - }) + let nonce = client + .account(&AccountKeyring::Alice.to_account_id()) + .await + .unwrap() + .nonce; + signer.set_nonce(nonce); + client + .submit( + balances::TransferCall { + to: &dest, + amount: 10_000, + }, + &signer, + ) .await .unwrap(); // check that nonce is handled correctly - xt.increment_nonce() - .submit(balances::TransferCall { - to: &dest, - amount: 10_000, - }) + signer.increment_nonce(); + client + .submit( + balances::TransferCall { + to: &dest, + amount: 10_000, + }, + &signer, + ) .await .unwrap(); } @@ -634,12 +531,13 @@ mod tests { // create raw payload with AccoundId and sign it let raw_payload = client - .create_raw_payload( - &signer_account_id, + .create_unsigned( balances::TransferCall { to: &dest, amount: 10_000, }, + &signer_account_id, + None, ) .await .unwrap(); @@ -647,12 +545,16 @@ mod tests { 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, - }) + let signer = PairSigner::new(Ed25519Keyring::Alice.pair()); + let xt_multi_sig = client + .create_signed( + balances::TransferCall { + to: &dest, + amount: 10_000, + }, + &signer, + ) + .await .unwrap() .signature .unwrap() diff --git a/src/rpc.rs b/src/rpc.rs index b6bef3f34f..c5898bf9b5 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -342,7 +342,7 @@ impl Rpc { /// Create and submit an extrinsic and return corresponding Event if successful pub async fn submit_and_watch_extrinsic( - self, + &self, extrinsic: E, decoder: EventsDecoder, ) -> Result, Error> { diff --git a/src/signer.rs b/src/signer.rs new file mode 100644 index 0000000000..b33b3fdd33 --- /dev/null +++ b/src/signer.rs @@ -0,0 +1,122 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of substrate-subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with substrate-subxt. If not, see . + +//! A library to **sub**mit e**xt**rinsics to a +//! [substrate](https://github.com/paritytech/substrate) node via RPC. + +use crate::{ + extra::SignedExtra, + frame::system::System, + Encoded, +}; +use codec::Encode; +use sp_core::Pair; +use sp_runtime::{ + generic::{ + SignedPayload, + UncheckedExtrinsic, + }, + traits::{ + IdentifyAccount, + Verify, + }, +}; +use std::marker::PhantomData; + +/// Extrinsic signer. +pub trait Signer> { + /// Returns the account id. + fn account_id(&self) -> &T::AccountId; + + /// Optionally returns a nonce. + fn nonce(&self) -> Option; + + /// Takes an unsigned extrinsic and returns a signed extrinsic. + fn sign( + &self, + extrinsic: SignedPayload, + ) -> UncheckedExtrinsic; +} + +/// Extrinsic signer using a private key. +pub struct PairSigner, P: Pair> { + _marker: PhantomData<(S, E)>, + account_id: T::AccountId, + nonce: Option, + signer: P, +} + +impl PairSigner +where + T: System, + S: Encode + Verify + From, + S::Signer: From + IdentifyAccount, + E: SignedExtra, + P: Pair, +{ + /// Creates a new `Signer` from a `Pair`. + pub fn new(signer: P) -> Self { + let account_id = S::Signer::from(signer.public()).into_account(); + Self { + _marker: PhantomData, + account_id, + nonce: None, + signer, + } + } + + /// Sets the nonce to a new value. + pub fn set_nonce(&mut self, nonce: T::Index) { + self.nonce = Some(nonce); + } + + /// Increment the nonce + pub fn increment_nonce(&mut self) { + self.nonce = self.nonce.map(|nonce| nonce + 1.into()); + } +} + +impl Signer for PairSigner +where + T: System, + T::AccountId: Into, + S: Encode, + E: SignedExtra, + P: Pair, + P::Signature: Into, +{ + fn account_id(&self) -> &T::AccountId { + &self.account_id + } + + fn nonce(&self) -> Option { + self.nonce + } + + fn sign( + &self, + extrinsic: SignedPayload, + ) -> UncheckedExtrinsic { + let signature = extrinsic.using_encoded(|payload| self.signer.sign(payload)); + let (call, extra, _) = extrinsic.deconstruct(); + UncheckedExtrinsic::new_signed( + call, + self.account_id.clone().into(), + signature.into(), + extra, + ) + } +}