Rework Subxt API to support offline and dynamic transactions (#593)

* WIP API changes

* debug impls

* Get main crate compiling with first round of changes

* Some tidy up

* Add WithExtrinsicParams, and have SubstrateConfig + PolkadotConfig, not DefaultConfig

* move transaction into extrinsic folder

* Add runtime updates back to OnlineClient

* rework to be 'client first' to fit better with storage + events

* add support for events to Client

* tidy dupe trait bound

* Wire storage into client, but need to remove static reliance

* various tidy up and start stripping codegen to remove bits we dont need now

* First pass updating calls and constants codegen

* WIP storage client updates

* First pass migrated runtime storage over to new format

* pass over codegen to generate StorageAddresses and throw other stuff out

* don't need a Call trait any more

* shuffle things around a bit

* Various proc_macro fixes to get 'cargo check' working

* organise what's exposed from subxt

* Get first example working; balance_transfer_with_params

* get balance_transfer example compiling

* get concurrent_storage_requests.rs example compiling

* get fetch_all_accounts example compiling

* get a bunch more of the examples compiling

* almost get final example working; type mismatch to look into

* wee tweaks

* move StorageAddress to separate file

* pass Defaultable/Iterable info to StorageAddress in codegen

* fix storage validation ne, and partial run through example code

* Remove static iteration and strip a generic param from everything

* fix doc tests in subxt crate

* update test utils and start fixing frame tests

* fix frame staking tests

* fix the rest of the test compile issues, Borrow on storage values

* cargo fmt

* remove extra logging during tests

* Appease clippy and no more need for into_iter on events

* cargo fmt

* fix dryRun tests by waiting for blocks

* wait for blocks instead of sleeping or other test hacks

* cargo fmt

* Fix doc links

* Traitify StorageAddress

* remove out-of-date doc comments

* optimise decoding storage a little

* cleanup tx stuff, trait for TxPayload, remove Err type param and decode at runtime

* clippy fixes

* fix doc links

* fix doc example

* constant address trait for consistency

* fix a typo and remove EncodeWithMetadata stuff

* Put EventDetails behind a proper interface and allow decoding into top level event, too

* fix docs

* tweak StorageAddress docs

* re-export StorageAddress at root for consistency

* fix clippy things

* Add support for dynamic values

* fix double encoding of storage map key after refactor

* clippy fix

* Fixes and add a dynamic usage example (needs new scale_value release)

* bump scale_value version

* cargo fmt

* Tweak event bits

* cargo fmt

* Add a test and bump scale-value to 0.4.0 to support this

* remove unnecessary vec from dynamic example

* Various typo/grammar fixes

Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>

* Address PR nits

* Undo accidental rename in changelog

* Small PR nits/tidyups

* fix tests; codegen change against latest substrate

* tweak storage address util names

* move error decoding to DecodeError and expose

* impl some basic traits on the extrinsic param builder

Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
This commit is contained in:
James Wilson
2022-08-08 11:55:20 +01:00
committed by GitHub
parent 7a09ac6cd7
commit e48f0e3b1d
84 changed files with 23097 additions and 35863 deletions
+142 -47
View File
@@ -3,24 +3,29 @@
// see LICENSE for license details.
use crate::{
test_node_process,
test_node_process_with,
utils::node_runtime::system,
pair_signer,
test_context,
test_context_with,
utils::{
node_runtime,
wait_for_blocks,
},
};
use sp_core::storage::{
well_known_keys,
StorageKey,
use sp_core::{
sr25519::Pair as Sr25519Pair,
storage::well_known_keys,
Pair,
};
use sp_keyring::AccountKeyring;
use subxt::error::DispatchError;
#[tokio::test]
async fn insert_key() {
let test_node_process = test_node_process_with(AccountKeyring::Bob).await;
let client = test_node_process.client();
let ctx = test_context_with(AccountKeyring::Bob).await;
let api = ctx.client();
let public = AccountKeyring::Alice.public().as_array_ref().to_vec();
client
.rpc()
api.rpc()
.insert_key(
"aura".to_string(),
"//Alice".to_string(),
@@ -28,7 +33,7 @@ async fn insert_key() {
)
.await
.unwrap();
assert!(client
assert!(api
.rpc()
.has_key(public.clone().into(), "aura".to_string())
.await
@@ -37,29 +42,30 @@ async fn insert_key() {
#[tokio::test]
async fn fetch_block_hash() {
let node_process = test_node_process().await;
node_process.client().rpc().block_hash(None).await.unwrap();
let ctx = test_context().await;
ctx.client().rpc().block_hash(None).await.unwrap();
}
#[tokio::test]
async fn fetch_block() {
let node_process = test_node_process().await;
let client = node_process.client();
let block_hash = client.rpc().block_hash(None).await.unwrap();
client.rpc().block(block_hash).await.unwrap();
let ctx = test_context().await;
let api = ctx.client();
let block_hash = api.rpc().block_hash(None).await.unwrap();
api.rpc().block(block_hash).await.unwrap();
}
#[tokio::test]
async fn fetch_read_proof() {
let node_process = test_node_process().await;
let client = node_process.client();
let block_hash = client.rpc().block_hash(None).await.unwrap();
client
.rpc()
let ctx = test_context().await;
let api = ctx.client();
let block_hash = api.rpc().block_hash(None).await.unwrap();
api.rpc()
.read_proof(
vec![
StorageKey(well_known_keys::HEAP_PAGES.to_vec()),
StorageKey(well_known_keys::EXTRINSIC_INDEX.to_vec()),
well_known_keys::HEAP_PAGES,
well_known_keys::EXTRINSIC_INDEX,
],
block_hash,
)
@@ -69,27 +75,31 @@ async fn fetch_read_proof() {
#[tokio::test]
async fn chain_subscribe_blocks() {
let node_process = test_node_process().await;
let client = node_process.client();
let mut blocks = client.rpc().subscribe_blocks().await.unwrap();
let ctx = test_context().await;
let api = ctx.client();
let mut blocks = api.rpc().subscribe_blocks().await.unwrap();
blocks.next().await.unwrap().unwrap();
}
#[tokio::test]
async fn chain_subscribe_finalized_blocks() {
let node_process = test_node_process().await;
let client = node_process.client();
let mut blocks = client.rpc().subscribe_finalized_blocks().await.unwrap();
let ctx = test_context().await;
let api = ctx.client();
let mut blocks = api.rpc().subscribe_finalized_blocks().await.unwrap();
blocks.next().await.unwrap().unwrap();
}
#[tokio::test]
async fn fetch_keys() {
let node_process = test_node_process().await;
let client = node_process.client();
let keys = client
let ctx = test_context().await;
let api = ctx.client();
let addr = node_runtime::storage().system().account_root();
let keys = api
.storage()
.fetch_keys::<system::storage::Account>(4, None, None)
.fetch_keys(&addr.to_root_bytes(), 4, None, None)
.await
.unwrap();
assert_eq!(keys.len(), 4)
@@ -97,13 +107,11 @@ async fn fetch_keys() {
#[tokio::test]
async fn test_iter() {
let node_process = test_node_process().await;
let client = node_process.client();
let mut iter = client
.storage()
.iter::<system::storage::Account>(None)
.await
.unwrap();
let ctx = test_context().await;
let api = ctx.client();
let addr = node_runtime::storage().system().account_root();
let mut iter = api.storage().iter(addr, 10, None).await.unwrap();
let mut i = 0;
while iter.next().await.unwrap().is_some() {
i += 1;
@@ -113,9 +121,96 @@ async fn test_iter() {
#[tokio::test]
async fn fetch_system_info() {
let node_process = test_node_process().await;
let client = node_process.client();
assert_eq!(client.rpc().system_chain().await.unwrap(), "Development");
assert_eq!(client.rpc().system_name().await.unwrap(), "Substrate Node");
assert!(!client.rpc().system_version().await.unwrap().is_empty());
let ctx = test_context().await;
let api = ctx.client();
assert_eq!(api.rpc().system_chain().await.unwrap(), "Development");
assert_eq!(api.rpc().system_name().await.unwrap(), "Substrate Node");
assert!(!api.rpc().system_version().await.unwrap().is_empty());
}
#[tokio::test]
async fn dry_run_passes() {
let ctx = test_context().await;
let api = ctx.client();
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = pair_signer(AccountKeyring::Bob.pair());
wait_for_blocks(&api).await;
let tx = node_runtime::tx()
.balances()
.transfer(bob.account_id().clone().into(), 10_000);
let signed_extrinsic = api
.tx()
.create_signed(&tx, &alice, Default::default())
.await
.unwrap();
api.rpc()
.dry_run(signed_extrinsic.encoded(), None)
.await
.expect("dryrunning failed")
.expect("expected dryrunning to be successful")
.unwrap();
signed_extrinsic
.submit_and_watch()
.await
.unwrap()
.wait_for_finalized_success()
.await
.unwrap();
}
#[tokio::test]
async fn dry_run_fails() {
let ctx = test_context().await;
let api = ctx.client();
wait_for_blocks(&api).await;
let alice = pair_signer(AccountKeyring::Alice.pair());
let hans = pair_signer(Sr25519Pair::generate().0);
let tx = node_runtime::tx().balances().transfer(
hans.account_id().clone().into(),
100_000_000_000_000_000_000_000_000_000_000_000,
);
let signed_extrinsic = api
.tx()
.create_signed(&tx, &alice, Default::default())
.await
.unwrap();
let dry_run_res = api
.rpc()
.dry_run(signed_extrinsic.encoded(), None)
.await
.expect("dryrunning failed")
.expect("expected dryrun transaction to be valid");
if let Err(sp_runtime::DispatchError::Module(module_error)) = dry_run_res {
assert_eq!(module_error.index, 6);
assert_eq!(module_error.error, 2);
} else {
panic!("expected a module error when dryrunning");
}
let res = signed_extrinsic
.submit_and_watch()
.await
.unwrap()
.wait_for_finalized_success()
.await;
if let Err(subxt::error::Error::Runtime(DispatchError::Module(err))) = res {
assert_eq!(err.pallet, "Balances");
assert_eq!(err.error, "InsufficientBalance");
} else {
panic!("expected a runtime module error");
}
}
File diff suppressed because it is too large Load Diff
+36 -34
View File
@@ -4,28 +4,30 @@
use crate::{
node_runtime::{
self,
balances,
system,
},
pair_signer,
test_context,
utils::wait_for_blocks,
};
use futures::StreamExt;
use sp_keyring::AccountKeyring;
// Check that we can subscribe to non-finalized block events.
#[tokio::test]
async fn non_finalized_block_subscription() -> Result<(), subxt::BasicError> {
tracing_subscriber::fmt::try_init().ok();
async fn non_finalized_block_subscription() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let mut event_sub = ctx.api.events().subscribe().await?;
let mut event_sub = api.events().subscribe().await?;
// Wait for the next set of events, and check that the
// associated block hash is not finalized yet.
let events = event_sub.next().await.unwrap()?;
let event_block_hash = events.block_hash();
let current_block_hash = ctx.api.client.rpc().block_hash(None).await?.unwrap();
let current_block_hash = api.rpc().block_hash(None).await?.unwrap();
assert_eq!(event_block_hash, current_block_hash);
Ok(())
@@ -33,18 +35,18 @@ async fn non_finalized_block_subscription() -> Result<(), subxt::BasicError> {
// Check that we can subscribe to finalized block events.
#[tokio::test]
async fn finalized_block_subscription() -> Result<(), subxt::BasicError> {
tracing_subscriber::fmt::try_init().ok();
async fn finalized_block_subscription() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let mut event_sub = ctx.api.events().subscribe_finalized().await?;
let mut event_sub = api.events().subscribe_finalized().await?;
// Wait for the next set of events, and check that the
// associated block hash is the one we just finalized.
// (this can be a bit slow as we have to wait for finalization)
let events = event_sub.next().await.unwrap()?;
let event_block_hash = events.block_hash();
let finalized_hash = ctx.api.client.rpc().finalized_head().await?;
let finalized_hash = api.rpc().finalized_head().await?;
assert_eq!(event_block_hash, finalized_hash);
Ok(())
@@ -53,24 +55,25 @@ async fn finalized_block_subscription() -> Result<(), subxt::BasicError> {
// Check that our subscription actually keeps producing events for
// a few blocks.
#[tokio::test]
async fn subscription_produces_events_each_block() -> Result<(), subxt::BasicError> {
tracing_subscriber::fmt::try_init().ok();
async fn subscription_produces_events_each_block() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let mut event_sub = ctx.api.events().subscribe().await?;
wait_for_blocks(&api).await;
let mut event_sub = api.events().subscribe().await?;
for i in 0..3 {
let events = event_sub
.next()
.await
.expect("events expected each block")?;
let success_event = events
.find_first::<system::events::ExtrinsicSuccess>()
.expect("decode error");
// Every now and then I get no bytes back for the first block events;
// I assume that this might be the case for the genesis block, so don't
// worry if no event found (but we should have no decode errors etc either way).
if i > 0 && success_event.is_none() {
if success_event.is_none() {
let n = events.len();
panic!("Expected an extrinsic success event on iteration {i} (saw {n} other events)")
}
@@ -82,13 +85,12 @@ async fn subscription_produces_events_each_block() -> Result<(), subxt::BasicErr
// Check that our subscription receives events, and we can filter them based on
// it's Stream impl, and ultimately see the event we expect.
#[tokio::test]
async fn balance_transfer_subscription() -> Result<(), subxt::BasicError> {
tracing_subscriber::fmt::try_init().ok();
async fn balance_transfer_subscription() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
// Subscribe to balance transfer events, ignoring all else.
let event_sub = ctx
.api
let event_sub = api
.events()
.subscribe()
.await?
@@ -101,11 +103,12 @@ async fn balance_transfer_subscription() -> Result<(), subxt::BasicError> {
// Make a transfer:
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = AccountKeyring::Bob.to_account_id();
ctx.api
.tx()
let transfer_tx = node_runtime::tx()
.balances()
.transfer(bob.clone().into(), 10_000)?
.sign_and_submit_then_watch_default(&alice)
.transfer(bob.clone().into(), 10_000);
api.tx()
.sign_and_submit_then_watch_default(&transfer_tx, &alice)
.await?;
// Wait for the next balance transfer event in our subscription stream
@@ -124,21 +127,20 @@ async fn balance_transfer_subscription() -> Result<(), subxt::BasicError> {
}
#[tokio::test]
async fn missing_block_headers_will_be_filled_in() -> Result<(), subxt::BasicError> {
async fn missing_block_headers_will_be_filled_in() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
// This function is not publically available to use, but contains
// the key logic for filling in missing blocks, so we want to test it.
// This is used in `subscribe_finalized` to ensure no block headers are
// missed.
use subxt::events::subscribe_to_block_headers_filling_in_gaps;
let ctx = test_context().await;
// Manually subscribe to the next 6 finalized block headers, but deliberately
// filter out some in the middle so we get back b _ _ b _ b. This guarantees
// that there will be some gaps, even if there aren't any from the subscription.
let some_finalized_blocks = ctx
.api
.client
let some_finalized_blocks = api
.rpc()
.subscribe_finalized_blocks()
.await?
@@ -152,7 +154,7 @@ async fn missing_block_headers_will_be_filled_in() -> Result<(), subxt::BasicErr
// This should spot any gaps in the middle and fill them back in.
let all_finalized_blocks = subscribe_to_block_headers_filling_in_gaps(
&ctx.api.client,
ctx.client(),
None,
some_finalized_blocks,
);
@@ -185,7 +187,7 @@ async fn check_events_are_sendable() {
tokio::task::spawn(async {
let ctx = test_context().await;
let mut event_sub = ctx.api.events().subscribe().await?;
let mut event_sub = ctx.client().events().subscribe().await?;
while let Some(ev) = event_sub.next().await {
// if `event_sub` doesn't implement Send, we can't hold
@@ -193,7 +195,7 @@ async fn check_events_are_sendable() {
// requires Send. This will lead to a compile error.
}
Ok::<_, subxt::BasicError>(())
Ok::<_, subxt::Error>(())
});
// Check that FilterEvents can be used across await points.
@@ -201,7 +203,7 @@ async fn check_events_are_sendable() {
let ctx = test_context().await;
let mut event_sub = ctx
.api
.client()
.events()
.subscribe()
.await?
@@ -213,6 +215,6 @@ async fn check_events_are_sendable() {
// requires Send; This will lead to a compile error.
}
Ok::<_, subxt::BasicError>(())
Ok::<_, subxt::Error>(())
});
}
+169 -84
View File
@@ -4,10 +4,10 @@
use crate::{
node_runtime::{
self,
balances,
runtime_types,
system,
DispatchError,
},
pair_signer,
test_context,
@@ -22,32 +22,36 @@ use sp_runtime::{
AccountId32,
MultiAddress,
};
use subxt::Error;
use subxt::error::{
DispatchError,
Error,
};
#[tokio::test]
async fn tx_basic_transfer() -> Result<(), subxt::Error<DispatchError>> {
async fn tx_basic_transfer() -> Result<(), subxt::Error> {
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = pair_signer(AccountKeyring::Bob.pair());
let bob_address = bob.account_id().clone().into();
let cxt = test_context().await;
let api = &cxt.api;
let ctx = test_context().await;
let api = ctx.client();
let alice_account_addr = node_runtime::storage().system().account(alice.account_id());
let bob_account_addr = node_runtime::storage().system().account(bob.account_id());
let alice_pre = api
.storage()
.system()
.account(alice.account_id(), None)
.fetch_or_default(&alice_account_addr, None)
.await?;
let bob_pre = api
.storage()
.system()
.account(bob.account_id(), None)
.fetch_or_default(&bob_account_addr, None)
.await?;
let tx = node_runtime::tx().balances().transfer(bob_address, 10_000);
let events = api
.tx()
.balances()
.transfer(bob_address, 10_000)?
.sign_and_submit_then_watch_default(&alice)
.sign_and_submit_then_watch_default(&tx, &alice)
.await?
.wait_for_finalized_success()
.await?;
@@ -69,13 +73,11 @@ async fn tx_basic_transfer() -> Result<(), subxt::Error<DispatchError>> {
let alice_post = api
.storage()
.system()
.account(alice.account_id(), None)
.fetch_or_default(&alice_account_addr, None)
.await?;
let bob_post = api
.storage()
.system()
.account(bob.account_id(), None)
.fetch_or_default(&bob_account_addr, None)
.await?;
assert!(alice_pre.data.free - 10_000 >= alice_post.data.free);
@@ -84,26 +86,118 @@ async fn tx_basic_transfer() -> Result<(), subxt::Error<DispatchError>> {
}
#[tokio::test]
async fn multiple_transfers_work_nonce_incremented(
) -> Result<(), subxt::Error<DispatchError>> {
async fn tx_dynamic_transfer() -> Result<(), subxt::Error> {
use subxt::ext::scale_value::{
At,
Composite,
Value,
};
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = pair_signer(AccountKeyring::Bob.pair());
let ctx = test_context().await;
let api = ctx.client();
let alice_account_addr = subxt::dynamic::storage(
"System",
"Account",
vec![Value::from_bytes(&alice.account_id())],
);
let bob_account_addr = subxt::dynamic::storage(
"System",
"Account",
vec![Value::from_bytes(&bob.account_id())],
);
let alice_pre = api
.storage()
.fetch_or_default(&alice_account_addr, None)
.await?;
let bob_pre = api
.storage()
.fetch_or_default(&bob_account_addr, None)
.await?;
let tx = subxt::dynamic::tx(
"Balances",
"transfer",
vec![
Value::unnamed_variant("Id", vec![Value::from_bytes(&bob.account_id())]),
Value::u128(10_000u128),
],
);
let events = api
.tx()
.sign_and_submit_then_watch_default(&tx, &alice)
.await?
.wait_for_finalized_success()
.await?;
let event_fields = events
.iter()
.filter_map(|ev| ev.ok())
.find(|ev| ev.pallet_name() == "Balances" && ev.variant_name() == "Transfer")
.expect("Failed to find Transfer event")
.field_values()
.map_context(|_| ());
let expected_fields = Composite::Named(vec![
(
"from".into(),
Value::unnamed_composite(vec![Value::from_bytes(&alice.account_id())]),
),
(
"to".into(),
Value::unnamed_composite(vec![Value::from_bytes(&bob.account_id())]),
),
("amount".into(), Value::u128(10_000)),
]);
assert_eq!(event_fields, expected_fields);
let alice_post = api
.storage()
.fetch_or_default(&alice_account_addr, None)
.await?;
let bob_post = api
.storage()
.fetch_or_default(&bob_account_addr, None)
.await?;
let alice_pre_free = alice_pre.at("data").at("free").unwrap().as_u128().unwrap();
let alice_post_free = alice_post.at("data").at("free").unwrap().as_u128().unwrap();
let bob_pre_free = bob_pre.at("data").at("free").unwrap().as_u128().unwrap();
let bob_post_free = bob_post.at("data").at("free").unwrap().as_u128().unwrap();
assert!(alice_pre_free - 10_000 >= alice_post_free);
assert_eq!(bob_pre_free + 10_000, bob_post_free);
Ok(())
}
#[tokio::test]
async fn multiple_transfers_work_nonce_incremented() -> Result<(), subxt::Error> {
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = pair_signer(AccountKeyring::Bob.pair());
let bob_address: MultiAddress<AccountId32, u32> = bob.account_id().clone().into();
let cxt = test_context().await;
let api = &cxt.api;
let ctx = test_context().await;
let api = ctx.client();
let bob_account_addr = node_runtime::storage().system().account(bob.account_id());
let bob_pre = api
.storage()
.system()
.account(bob.account_id(), None)
.fetch_or_default(&bob_account_addr, None)
.await?;
let tx = node_runtime::tx()
.balances()
.transfer(bob_address.clone(), 10_000);
for _ in 0..3 {
api
.tx()
.balances()
.transfer(bob_address.clone(), 10_000)?
.sign_and_submit_then_watch_default(&alice)
.sign_and_submit_then_watch_default(&tx, &alice)
.await?
.wait_for_in_block() // Don't need to wait for finalization; this is quicker.
.await?
@@ -113,8 +207,7 @@ async fn multiple_transfers_work_nonce_incremented(
let bob_post = api
.storage()
.system()
.account(bob.account_id(), None)
.fetch_or_default(&bob_account_addr, None)
.await?;
assert_eq!(bob_pre.data.free + 30_000, bob_post.data.free);
@@ -123,45 +216,40 @@ async fn multiple_transfers_work_nonce_incremented(
#[tokio::test]
async fn storage_total_issuance() {
let cxt = test_context().await;
let total_issuance = cxt
.api
.storage()
.balances()
.total_issuance(None)
.await
.unwrap();
let ctx = test_context().await;
let api = ctx.client();
let addr = node_runtime::storage().balances().total_issuance();
let total_issuance = api.storage().fetch_or_default(&addr, None).await.unwrap();
assert_ne!(total_issuance, 0);
}
#[tokio::test]
async fn storage_balance_lock() -> Result<(), subxt::Error<DispatchError>> {
async fn storage_balance_lock() -> Result<(), subxt::Error> {
let bob = pair_signer(AccountKeyring::Bob.pair());
let charlie = AccountKeyring::Charlie.to_account_id();
let cxt = test_context().await;
let ctx = test_context().await;
let api = ctx.client();
cxt.api
.tx()
.staking()
.bond(
charlie.into(),
100_000_000_000_000,
runtime_types::pallet_staking::RewardDestination::Stash,
)?
.sign_and_submit_then_watch_default(&bob)
let tx = node_runtime::tx().staking().bond(
charlie.into(),
100_000_000_000_000,
runtime_types::pallet_staking::RewardDestination::Stash,
);
api.tx()
.sign_and_submit_then_watch_default(&tx, &bob)
.await?
.wait_for_finalized_success()
.await?
.find_first::<system::events::ExtrinsicSuccess>()?
.expect("No ExtrinsicSuccess Event found");
let locked_account = AccountKeyring::Bob.to_account_id();
let locks = cxt
.api
.storage()
let locks_addr = node_runtime::storage()
.balances()
.locks(&locked_account, None)
.await?;
.locks(&AccountKeyring::Bob.to_account_id());
let locks = api.storage().fetch_or_default(&locks_addr, None).await?;
assert_eq!(
locks.0,
@@ -177,38 +265,37 @@ async fn storage_balance_lock() -> Result<(), subxt::Error<DispatchError>> {
#[tokio::test]
async fn transfer_error() {
tracing_subscriber::fmt::try_init().ok();
let alice = pair_signer(AccountKeyring::Alice.pair());
let alice_addr = alice.account_id().clone().into();
let hans = pair_signer(Pair::generate().0);
let hans_address = hans.account_id().clone().into();
let ctx = test_context().await;
let api = ctx.client();
ctx.api
.tx()
let to_hans_tx = node_runtime::tx()
.balances()
.transfer(hans_address, 100_000_000_000_000_000)
.unwrap()
.sign_and_submit_then_watch_default(&alice)
.transfer(hans_address, 100_000_000_000_000_000);
let to_alice_tx = node_runtime::tx()
.balances()
.transfer(alice_addr, 100_000_000_000_000_000);
api.tx()
.sign_and_submit_then_watch_default(&to_hans_tx, &alice)
.await
.unwrap()
.wait_for_finalized_success()
.await
.unwrap();
let res = ctx
.api
let res = api
.tx()
.balances()
.transfer(alice_addr, 100_000_000_000_000_000)
.unwrap()
.sign_and_submit_then_watch_default(&hans)
.sign_and_submit_then_watch_default(&to_alice_tx, &hans)
.await
.unwrap()
.wait_for_finalized_success()
.await;
if let Err(Error::Module(err)) = res {
if let Err(Error::Runtime(DispatchError::Module(err))) = res {
assert_eq!(err.pallet, "Balances");
assert_eq!(err.error, "InsufficientBalance");
} else {
@@ -218,19 +305,17 @@ async fn transfer_error() {
#[tokio::test]
async fn transfer_implicit_subscription() {
tracing_subscriber::fmt::try_init().ok();
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = AccountKeyring::Bob.to_account_id();
let bob_addr = bob.clone().into();
let cxt = test_context().await;
let ctx = test_context().await;
let api = ctx.client();
let event = cxt
.api
let to_bob_tx = node_runtime::tx().balances().transfer(bob_addr, 10_000);
let event = api
.tx()
.balances()
.transfer(bob_addr, 10_000)
.unwrap()
.sign_and_submit_then_watch_default(&alice)
.sign_and_submit_then_watch_default(&to_bob_tx, &alice)
.await
.unwrap()
.wait_for_finalized_success()
@@ -252,19 +337,19 @@ async fn transfer_implicit_subscription() {
#[tokio::test]
async fn constant_existential_deposit() {
let cxt = test_context().await;
let locked_metadata = cxt.client().metadata();
let metadata = locked_metadata.read();
let ctx = test_context().await;
let api = ctx.client();
// get and decode constant manually via metadata:
let metadata = api.metadata();
let balances_metadata = metadata.pallet("Balances").unwrap();
let constant_metadata = balances_metadata.constant("ExistentialDeposit").unwrap();
let existential_deposit = u128::decode(&mut &constant_metadata.value[..]).unwrap();
assert_eq!(existential_deposit, 100_000_000_000_000);
assert_eq!(
existential_deposit,
cxt.api
.constants()
.balances()
.existential_deposit()
.unwrap()
);
// constant address for API access:
let addr = node_runtime::constants().balances().existential_deposit();
// Make sure thetwo are identical:
assert_eq!(existential_deposit, api.constants().at(&addr).unwrap());
}
@@ -7,57 +7,46 @@ use sp_keyring::AccountKeyring;
use crate::{
node_runtime::{
self,
contracts::{
calls::TransactionApi,
events,
storage,
},
contracts::events,
system,
DispatchError,
},
test_context,
NodeRuntimeParams,
TestContext,
};
use sp_core::sr25519::Pair;
use sp_runtime::MultiAddress;
use subxt::{
Client,
tx::{
PairSigner,
TxProgress,
},
Config,
DefaultConfig,
Error,
PairSigner,
TransactionProgress,
OnlineClient,
SubstrateConfig,
};
struct ContractsTestContext {
cxt: TestContext,
signer: PairSigner<DefaultConfig, Pair>,
signer: PairSigner<SubstrateConfig, Pair>,
}
type Hash = <DefaultConfig as Config>::Hash;
type AccountId = <DefaultConfig as Config>::AccountId;
type Hash = <SubstrateConfig as Config>::Hash;
type AccountId = <SubstrateConfig as Config>::AccountId;
impl ContractsTestContext {
async fn init() -> Self {
tracing_subscriber::fmt::try_init().ok();
let cxt = test_context().await;
let signer = PairSigner::new(AccountKeyring::Alice.pair());
Self { cxt, signer }
}
fn client(&self) -> &Client<DefaultConfig> {
fn client(&self) -> OnlineClient<SubstrateConfig> {
self.cxt.client()
}
fn contracts_tx(&self) -> TransactionApi<DefaultConfig, NodeRuntimeParams> {
self.cxt.api.tx().contracts()
}
async fn instantiate_with_code(
&self,
) -> Result<(Hash, AccountId), Error<DispatchError>> {
async fn instantiate_with_code(&self) -> Result<(Hash, AccountId), Error> {
tracing::info!("instantiate_with_code:");
const CONTRACT: &str = r#"
(module
@@ -67,20 +56,19 @@ impl ContractsTestContext {
"#;
let code = wabt::wat2wasm(CONTRACT).expect("invalid wabt");
let instantiate_tx = node_runtime::tx().contracts().instantiate_with_code(
100_000_000_000_000_000, // endowment
500_000_000_000, // gas_limit
None, // storage_deposit_limit
code,
vec![], // data
vec![], // salt
);
let events = self
.cxt
.api
.client()
.tx()
.contracts()
.instantiate_with_code(
100_000_000_000_000_000, // endowment
500_000_000_000, // gas_limit
None, // storage_deposit_limit
code,
vec![], // data
vec![], // salt
)?
.sign_and_submit_then_watch_default(&self.signer)
.sign_and_submit_then_watch_default(&instantiate_tx, &self.signer)
.await?
.wait_for_finalized_success()
.await?;
@@ -108,19 +96,21 @@ impl ContractsTestContext {
code_hash: Hash,
data: Vec<u8>,
salt: Vec<u8>,
) -> Result<AccountId, Error<DispatchError>> {
) -> Result<AccountId, Error> {
// call instantiate extrinsic
let instantiate_tx = node_runtime::tx().contracts().instantiate(
100_000_000_000_000_000, // endowment
500_000_000_000, // gas_limit
None, // storage_deposit_limit
code_hash,
data,
salt,
);
let result = self
.contracts_tx()
.instantiate(
100_000_000_000_000_000, // endowment
500_000_000_000, // gas_limit
None, // storage_deposit_limit
code_hash,
data,
salt,
)?
.sign_and_submit_then_watch_default(&self.signer)
.client()
.tx()
.sign_and_submit_then_watch_default(&instantiate_tx, &self.signer)
.await?
.wait_for_finalized_success()
.await?;
@@ -137,21 +127,20 @@ impl ContractsTestContext {
&self,
contract: AccountId,
input_data: Vec<u8>,
) -> Result<
TransactionProgress<'_, DefaultConfig, DispatchError, node_runtime::Event>,
Error<DispatchError>,
> {
) -> Result<TxProgress<SubstrateConfig, OnlineClient<SubstrateConfig>>, Error> {
tracing::info!("call: {:?}", contract);
let call_tx = node_runtime::tx().contracts().call(
MultiAddress::Id(contract),
0, // value
500_000_000, // gas_limit
None, // storage_deposit_limit
input_data,
);
let result = self
.contracts_tx()
.call(
MultiAddress::Id(contract),
0, // value
500_000_000, // gas_limit
None, // storage_deposit_limit
input_data,
)?
.sign_and_submit_then_watch_default(&self.signer)
.client()
.tx()
.sign_and_submit_then_watch_default(&call_tx, &self.signer)
.await?;
tracing::info!("Call result: {:?}", result);
@@ -190,19 +179,17 @@ async fn tx_call() {
let cxt = ContractsTestContext::init().await;
let (_, contract) = cxt.instantiate_with_code().await.unwrap();
let contract_info = cxt
.cxt
.api
.storage()
let info_addr = node_runtime::storage()
.contracts()
.contract_info_of(&contract, None)
.await;
.contract_info_of(&contract);
let contract_info = cxt.client().storage().fetch(&info_addr, None).await;
assert!(contract_info.is_ok());
let keys = cxt
.client()
.storage()
.fetch_keys::<storage::ContractInfoOf>(5, None, None)
.fetch_keys(&info_addr.to_bytes(), 10, None, None)
.await
.unwrap()
.iter()
+96 -93
View File
@@ -4,12 +4,12 @@
use crate::{
node_runtime::{
self,
runtime_types::pallet_staking::{
RewardDestination,
ValidatorPrefs,
},
staking,
DispatchError,
},
pair_signer,
test_context,
@@ -20,7 +20,10 @@ use sp_core::{
Pair,
};
use sp_keyring::AccountKeyring;
use subxt::Error;
use subxt::error::{
DispatchError,
Error,
};
/// Helper function to generate a crypto pair from seed
fn get_from_seed(seed: &str) -> sr25519::Pair {
@@ -37,14 +40,17 @@ fn default_validator_prefs() -> ValidatorPrefs {
#[tokio::test]
async fn validate_with_controller_account() {
let alice = pair_signer(AccountKeyring::Alice.pair());
let ctx = test_context().await;
ctx.api
.tx()
let api = ctx.client();
let alice = pair_signer(AccountKeyring::Alice.pair());
let tx = node_runtime::tx()
.staking()
.validate(default_validator_prefs())
.unwrap()
.sign_and_submit_then_watch_default(&alice)
.validate(default_validator_prefs());
api.tx()
.sign_and_submit_then_watch_default(&tx, &alice)
.await
.unwrap()
.wait_for_finalized_success()
@@ -53,19 +59,23 @@ async fn validate_with_controller_account() {
}
#[tokio::test]
async fn validate_not_possible_for_stash_account() -> Result<(), Error<DispatchError>> {
let alice_stash = pair_signer(get_from_seed("Alice//stash"));
async fn validate_not_possible_for_stash_account() -> Result<(), Error> {
let ctx = test_context().await;
let announce_validator = ctx
.api
.tx()
let api = ctx.client();
let alice_stash = pair_signer(get_from_seed("Alice//stash"));
let tx = node_runtime::tx()
.staking()
.validate(default_validator_prefs())?
.sign_and_submit_then_watch_default(&alice_stash)
.validate(default_validator_prefs());
let announce_validator = api
.tx()
.sign_and_submit_then_watch_default(&tx, &alice_stash)
.await?
.wait_for_finalized_success()
.await;
assert_matches!(announce_validator, Err(Error::Module(err)) => {
assert_matches!(announce_validator, Err(Error::Runtime(DispatchError::Module(err))) => {
assert_eq!(err.pallet, "Staking");
assert_eq!(err.error, "NotController");
});
@@ -74,16 +84,18 @@ async fn validate_not_possible_for_stash_account() -> Result<(), Error<DispatchE
#[tokio::test]
async fn nominate_with_controller_account() {
let ctx = test_context().await;
let api = ctx.client();
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = pair_signer(AccountKeyring::Bob.pair());
let ctx = test_context().await;
ctx.api
.tx()
let tx = node_runtime::tx()
.staking()
.nominate(vec![bob.account_id().clone().into()])
.unwrap()
.sign_and_submit_then_watch_default(&alice)
.nominate(vec![bob.account_id().clone().into()]);
api.tx()
.sign_and_submit_then_watch_default(&tx, &alice)
.await
.unwrap()
.wait_for_finalized_success()
@@ -92,22 +104,26 @@ async fn nominate_with_controller_account() {
}
#[tokio::test]
async fn nominate_not_possible_for_stash_account() -> Result<(), Error<DispatchError>> {
async fn nominate_not_possible_for_stash_account() -> Result<(), Error> {
let ctx = test_context().await;
let api = ctx.client();
let alice_stash = pair_signer(get_from_seed("Alice//stash"));
let bob = pair_signer(AccountKeyring::Bob.pair());
let ctx = test_context().await;
let nomination = ctx
.api
.tx()
let tx = node_runtime::tx()
.staking()
.nominate(vec![bob.account_id().clone().into()])?
.sign_and_submit_then_watch_default(&alice_stash)
.await?
.nominate(vec![bob.account_id().clone().into()]);
let nomination = api
.tx()
.sign_and_submit_then_watch_default(&tx, &alice_stash)
.await
.unwrap()
.wait_for_finalized_success()
.await;
assert_matches!(nomination, Err(Error::Module(err)) => {
assert_matches!(nomination, Err(Error::Runtime(DispatchError::Module(err))) => {
assert_eq!(err.pallet, "Staking");
assert_eq!(err.error, "NotController");
});
@@ -115,52 +131,45 @@ async fn nominate_not_possible_for_stash_account() -> Result<(), Error<DispatchE
}
#[tokio::test]
async fn chill_works_for_controller_only() -> Result<(), Error<DispatchError>> {
async fn chill_works_for_controller_only() -> Result<(), Error> {
let ctx = test_context().await;
let api = ctx.client();
let alice_stash = pair_signer(get_from_seed("Alice//stash"));
let bob_stash = pair_signer(get_from_seed("Bob//stash"));
let alice = pair_signer(AccountKeyring::Alice.pair());
let ctx = test_context().await;
// this will fail the second time, which is why this is one test, not two
ctx.api
.tx()
let nominate_tx = node_runtime::tx()
.staking()
.nominate(vec![bob_stash.account_id().clone().into()])?
.sign_and_submit_then_watch_default(&alice)
.nominate(vec![bob_stash.account_id().clone().into()]);
api.tx()
.sign_and_submit_then_watch_default(&nominate_tx, &alice)
.await?
.wait_for_finalized_success()
.await?;
let ledger = ctx
.api
.storage()
.staking()
.ledger(alice.account_id(), None)
.await?
.unwrap();
let ledger_addr = node_runtime::storage().staking().ledger(alice.account_id());
let ledger = api.storage().fetch(&ledger_addr, None).await?.unwrap();
assert_eq!(alice_stash.account_id(), &ledger.stash);
let chill = ctx
.api
let chill_tx = node_runtime::tx().staking().chill();
let chill = api
.tx()
.staking()
.chill()?
.sign_and_submit_then_watch_default(&alice_stash)
.sign_and_submit_then_watch_default(&chill_tx, &alice_stash)
.await?
.wait_for_finalized_success()
.await;
assert_matches!(chill, Err(Error::Module(err)) => {
assert_matches!(chill, Err(Error::Runtime(DispatchError::Module(err))) => {
assert_eq!(err.pallet, "Staking");
assert_eq!(err.error, "NotController");
});
let is_chilled = ctx
.api
let is_chilled = api
.tx()
.staking()
.chill()?
.sign_and_submit_then_watch_default(&alice)
.sign_and_submit_then_watch_default(&chill_tx, &alice)
.await?
.wait_for_finalized_success()
.await?
@@ -171,43 +180,35 @@ async fn chill_works_for_controller_only() -> Result<(), Error<DispatchError>> {
}
#[tokio::test]
async fn tx_bond() -> Result<(), Error<DispatchError>> {
let alice = pair_signer(AccountKeyring::Alice.pair());
async fn tx_bond() -> Result<(), Error> {
let ctx = test_context().await;
let api = ctx.client();
let bond = ctx
.api
let alice = pair_signer(AccountKeyring::Alice.pair());
let bond_tx = node_runtime::tx().staking().bond(
AccountKeyring::Bob.to_account_id().into(),
100_000_000_000_000,
RewardDestination::Stash,
);
let bond = api
.tx()
.staking()
.bond(
AccountKeyring::Bob.to_account_id().into(),
100_000_000_000_000,
RewardDestination::Stash,
)
.unwrap()
.sign_and_submit_then_watch_default(&alice)
.sign_and_submit_then_watch_default(&bond_tx, &alice)
.await?
.wait_for_finalized_success()
.await;
assert!(bond.is_ok());
let bond_again = ctx
.api
let bond_again = api
.tx()
.staking()
.bond(
AccountKeyring::Bob.to_account_id().into(),
100_000_000_000_000,
RewardDestination::Stash,
)
.unwrap()
.sign_and_submit_then_watch_default(&alice)
.sign_and_submit_then_watch_default(&bond_tx, &alice)
.await?
.wait_for_finalized_success()
.await;
assert_matches!(bond_again, Err(Error::Module(err)) => {
assert_matches!(bond_again, Err(Error::Runtime(DispatchError::Module(err))) => {
assert_eq!(err.pallet, "Staking");
assert_eq!(err.error, "AlreadyBonded");
});
@@ -215,35 +216,37 @@ async fn tx_bond() -> Result<(), Error<DispatchError>> {
}
#[tokio::test]
async fn storage_history_depth() -> Result<(), Error<DispatchError>> {
async fn storage_history_depth() -> Result<(), Error> {
let ctx = test_context().await;
let history_depth = ctx.api.storage().staking().history_depth(None).await?;
let api = ctx.client();
let history_depth_addr = node_runtime::storage().staking().history_depth();
let history_depth = api
.storage()
.fetch_or_default(&history_depth_addr, None)
.await?;
assert_eq!(history_depth, 84);
Ok(())
}
#[tokio::test]
async fn storage_current_era() -> Result<(), Error<DispatchError>> {
async fn storage_current_era() -> Result<(), Error> {
let ctx = test_context().await;
let _current_era = ctx
.api
let api = ctx.client();
let current_era_addr = node_runtime::storage().staking().current_era();
let _current_era = api
.storage()
.staking()
.current_era(None)
.fetch(&current_era_addr, None)
.await?
.expect("current era always exists");
Ok(())
}
#[tokio::test]
async fn storage_era_reward_points() -> Result<(), Error<DispatchError>> {
let cxt = test_context().await;
let current_era_result = cxt
.api
.storage()
.staking()
.eras_reward_points(&0, None)
.await;
async fn storage_era_reward_points() -> Result<(), Error> {
let ctx = test_context().await;
let api = ctx.client();
let reward_points_addr = node_runtime::storage().staking().eras_reward_points(&0);
let current_era_result = api.storage().fetch(&reward_points_addr, None).await;
assert!(current_era_result.is_ok());
Ok(())
+16 -16
View File
@@ -4,35 +4,35 @@
use crate::{
node_runtime::{
self,
runtime_types,
sudo,
DispatchError,
},
pair_signer,
test_context,
};
use sp_keyring::AccountKeyring;
type Call = runtime_types::node_runtime::Call;
type Call = runtime_types::kitchensink_runtime::Call;
type BalancesCall = runtime_types::pallet_balances::pallet::Call;
#[tokio::test]
async fn test_sudo() -> Result<(), subxt::Error<DispatchError>> {
async fn test_sudo() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = AccountKeyring::Bob.to_account_id().into();
let cxt = test_context().await;
let call = Call::Balances(BalancesCall::transfer {
dest: bob,
value: 10_000,
});
let tx = node_runtime::tx().sudo().sudo(call);
let found_event = cxt
.api
let found_event = api
.tx()
.sudo()
.sudo(call)?
.sign_and_submit_then_watch_default(&alice)
.sign_and_submit_then_watch_default(&tx, &alice)
.await?
.wait_for_finalized_success()
.await?
@@ -43,22 +43,22 @@ async fn test_sudo() -> Result<(), subxt::Error<DispatchError>> {
}
#[tokio::test]
async fn test_sudo_unchecked_weight() -> Result<(), subxt::Error<DispatchError>> {
async fn test_sudo_unchecked_weight() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = AccountKeyring::Bob.to_account_id().into();
let cxt = test_context().await;
let call = Call::Balances(BalancesCall::transfer {
dest: bob,
value: 10_000,
});
let tx = node_runtime::tx().sudo().sudo_unchecked_weight(call, 0);
let found_event = cxt
.api
let found_event = api
.tx()
.sudo()
.sudo_unchecked_weight(call, 0)?
.sign_and_submit_then_watch_default(&alice)
.sign_and_submit_then_watch_default(&tx, &alice)
.await?
.wait_for_finalized_success()
.await?
+20 -15
View File
@@ -4,8 +4,8 @@
use crate::{
node_runtime::{
self,
system,
DispatchError,
},
pair_signer,
test_context,
@@ -14,15 +14,17 @@ use assert_matches::assert_matches;
use sp_keyring::AccountKeyring;
#[tokio::test]
async fn storage_account() -> Result<(), subxt::Error<DispatchError>> {
async fn storage_account() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let alice = pair_signer(AccountKeyring::Alice.pair());
let cxt = test_context().await;
let account_info = cxt
.api
let account_info_addr = node_runtime::storage().system().account(alice.account_id());
let account_info = api
.storage()
.system()
.account(alice.account_id(), None)
.fetch_or_default(&account_info_addr, None)
.await;
assert_matches!(account_info, Ok(_));
@@ -30,16 +32,19 @@ async fn storage_account() -> Result<(), subxt::Error<DispatchError>> {
}
#[tokio::test]
async fn tx_remark_with_event() -> Result<(), subxt::Error<DispatchError>> {
let alice = pair_signer(AccountKeyring::Alice.pair());
let cxt = test_context().await;
async fn tx_remark_with_event() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let found_event = cxt
.api
.tx()
let alice = pair_signer(AccountKeyring::Alice.pair());
let tx = node_runtime::tx()
.system()
.remark_with_event(b"remarkable".to_vec())?
.sign_and_submit_then_watch_default(&alice)
.remark_with_event(b"remarkable".to_vec());
let found_event = api
.tx()
.sign_and_submit_then_watch_default(&tx, &alice)
.await?
.wait_for_finalized_success()
.await?
@@ -2,13 +2,20 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::test_context;
use crate::{
node_runtime,
test_context,
};
#[tokio::test]
async fn storage_get_current_timestamp() {
let cxt = test_context().await;
let ctx = test_context().await;
let api = ctx.client();
let timestamp = cxt.api.storage().timestamp().now(None).await;
let timestamp = api
.storage()
.fetch(&node_runtime::storage().timestamp().now(), None)
.await;
assert!(timestamp.is_ok())
}
+8
View File
@@ -24,3 +24,11 @@ mod storage;
use test_runtime::node_runtime;
#[cfg(test)]
use utils::*;
// We don't use this dependency, but it's here so that we
// can enable logging easily if need be. Add this to a test
// to enable tracing for it:
//
// tracing_subscriber::fmt::init();
#[cfg(test)]
use tracing_subscriber as _;
@@ -3,6 +3,7 @@
// see LICENSE for license details.
use crate::{
node_runtime,
test_context,
TestContext,
};
@@ -28,72 +29,57 @@ use scale_info::{
TypeInfo,
};
use subxt::{
ClientBuilder,
DefaultConfig,
Metadata,
SubstrateExtrinsicParams,
OfflineClient,
SubstrateConfig,
};
use crate::utils::node_runtime;
type RuntimeApi =
node_runtime::RuntimeApi<DefaultConfig, SubstrateExtrinsicParams<DefaultConfig>>;
async fn metadata_to_api(metadata: RuntimeMetadataV14, cxt: &TestContext) -> RuntimeApi {
async fn metadata_to_api(
metadata: RuntimeMetadataV14,
ctx: &TestContext,
) -> OfflineClient<SubstrateConfig> {
let prefixed = RuntimeMetadataPrefixed::from(metadata);
let metadata = Metadata::try_from(prefixed).unwrap();
ClientBuilder::new()
.set_url(cxt.node_proc.ws_url().to_string())
.set_metadata(metadata)
.build()
.await
.unwrap()
.to_runtime_api::<node_runtime::RuntimeApi<
DefaultConfig,
SubstrateExtrinsicParams<DefaultConfig>,
>>()
OfflineClient::new(
ctx.client().genesis_hash(),
ctx.client().runtime_version(),
metadata,
)
}
#[tokio::test]
async fn full_metadata_check() {
let cxt = test_context().await;
let api = &cxt.api;
let ctx = test_context().await;
let api = ctx.client();
// Runtime metadata is identical to the metadata used during API generation.
assert!(api.validate_metadata().is_ok());
assert!(node_runtime::validate_codegen(&api).is_ok());
// Modify the metadata.
let mut metadata: RuntimeMetadataV14 = {
let locked_client_metadata = api.client.metadata();
let client_metadata = locked_client_metadata.read();
client_metadata.runtime_metadata().clone()
};
let mut metadata: RuntimeMetadataV14 = api.metadata().runtime_metadata().clone();
metadata.pallets[0].name = "NewPallet".to_string();
let new_api = metadata_to_api(metadata, &cxt).await;
let api = metadata_to_api(metadata, &ctx).await;
assert_eq!(
new_api
.validate_metadata()
node_runtime::validate_codegen(&api)
.expect_err("Validation should fail for incompatible metadata"),
::subxt::MetadataError::IncompatibleMetadata
::subxt::error::MetadataError::IncompatibleMetadata
);
}
#[tokio::test]
async fn constant_values_are_not_validated() {
let cxt = test_context().await;
let api = &cxt.api;
let ctx = test_context().await;
let api = ctx.client();
// Ensure that `ExistentialDeposit` is compatible before altering the metadata.
assert!(cxt.api.constants().balances().existential_deposit().is_ok());
let deposit_addr = node_runtime::constants().balances().existential_deposit();
// Retrieve existential deposit to validate it and confirm that it's OK.
assert!(api.constants().at(&deposit_addr).is_ok());
// Modify the metadata.
let mut metadata: RuntimeMetadataV14 = {
let locked_client_metadata = api.client.metadata();
let client_metadata = locked_client_metadata.read();
client_metadata.runtime_metadata().clone()
};
let mut metadata: RuntimeMetadataV14 = api.metadata().runtime_metadata().clone();
let mut existential = metadata
.pallets
@@ -108,13 +94,10 @@ async fn constant_values_are_not_validated() {
// Modifying a constant value should not lead to an error:
existential.value = vec![0u8; 32];
let new_api = metadata_to_api(metadata, &cxt).await;
let api = metadata_to_api(metadata, &ctx).await;
assert!(new_api.validate_metadata().is_ok());
assert!(new_api.constants().balances().existential_deposit().is_ok());
// Other constant validation should not be impacted.
assert!(new_api.constants().balances().max_locks().is_ok());
assert!(node_runtime::validate_codegen(&api).is_ok());
assert!(api.constants().at(&deposit_addr).is_ok());
}
fn default_pallet() -> PalletMetadata {
@@ -143,11 +126,15 @@ fn pallets_to_metadata(pallets: Vec<PalletMetadata>) -> RuntimeMetadataV14 {
#[tokio::test]
async fn calls_check() {
let cxt = test_context().await;
let ctx = test_context().await;
let api = ctx.client();
let unbond_tx = node_runtime::tx().staking().unbond(123_456_789_012_345);
let withdraw_unbonded_addr = node_runtime::tx().staking().withdraw_unbonded(10);
// Ensure that `Unbond` and `WinthdrawUnbonded` calls are compatible before altering the metadata.
assert!(cxt.api.tx().staking().unbond(123_456_789_012_345).is_ok());
assert!(cxt.api.tx().staking().withdraw_unbonded(10).is_ok());
assert!(api.tx().validate(&unbond_tx).is_ok());
assert!(api.tx().validate(&withdraw_unbonded_addr).is_ok());
// Reconstruct the `Staking` call as is.
struct CallRec;
@@ -181,9 +168,11 @@ async fn calls_check() {
..default_pallet()
};
let metadata = pallets_to_metadata(vec![pallet]);
let new_api = metadata_to_api(metadata, &cxt).await;
assert!(new_api.tx().staking().unbond(123_456_789_012_345).is_ok());
assert!(new_api.tx().staking().withdraw_unbonded(10).is_ok());
let api = metadata_to_api(metadata, &ctx).await;
// The calls should still be valid with this new type info:
assert!(api.tx().validate(&unbond_tx).is_ok());
assert!(api.tx().validate(&withdraw_unbonded_addr).is_ok());
// Change `Unbond` call but leave the rest as is.
struct CallRecSecond;
@@ -216,31 +205,24 @@ async fn calls_check() {
..default_pallet()
};
let metadata = pallets_to_metadata(vec![pallet]);
let new_api = metadata_to_api(metadata, &cxt).await;
let api = metadata_to_api(metadata, &ctx).await;
// Unbond call should fail, while withdraw_unbonded remains compatible.
assert!(new_api.tx().staking().unbond(123_456_789_012_345).is_err());
assert!(new_api.tx().staking().withdraw_unbonded(10).is_ok());
assert!(api.tx().validate(&unbond_tx).is_err());
assert!(api.tx().validate(&withdraw_unbonded_addr).is_ok());
}
#[tokio::test]
async fn storage_check() {
let cxt = test_context().await;
let ctx = test_context().await;
let api = ctx.client();
let tx_count_addr = node_runtime::storage().system().extrinsic_count();
let tx_len_addr = node_runtime::storage().system().all_extrinsics_len();
// Ensure that `ExtrinsicCount` and `EventCount` storages are compatible before altering the metadata.
assert!(cxt
.api
.storage()
.system()
.extrinsic_count(None)
.await
.is_ok());
assert!(cxt
.api
.storage()
.system()
.all_extrinsics_len(None)
.await
.is_ok());
assert!(api.storage().validate(&tx_count_addr).is_ok());
assert!(api.storage().validate(&tx_len_addr).is_ok());
// Reconstruct the storage.
let storage = PalletStorageMetadata {
@@ -268,19 +250,11 @@ async fn storage_check() {
..default_pallet()
};
let metadata = pallets_to_metadata(vec![pallet]);
let new_api = metadata_to_api(metadata, &cxt).await;
assert!(new_api
.storage()
.system()
.extrinsic_count(None)
.await
.is_ok());
assert!(new_api
.storage()
.system()
.all_extrinsics_len(None)
.await
.is_ok());
let api = metadata_to_api(metadata, &ctx).await;
// The addresses should still validate:
assert!(api.storage().validate(&tx_count_addr).is_ok());
assert!(api.storage().validate(&tx_len_addr).is_ok());
// Reconstruct the storage while modifying ExtrinsicCount.
let storage = PalletStorageMetadata {
@@ -309,17 +283,9 @@ async fn storage_check() {
..default_pallet()
};
let metadata = pallets_to_metadata(vec![pallet]);
let new_api = metadata_to_api(metadata, &cxt).await;
assert!(new_api
.storage()
.system()
.extrinsic_count(None)
.await
.is_err());
assert!(new_api
.storage()
.system()
.all_extrinsics_len(None)
.await
.is_ok());
let api = metadata_to_api(metadata, &ctx).await;
// The count route should fail now; the other will be ok still.
assert!(api.storage().validate(&tx_count_addr).is_err());
assert!(api.storage().validate(&tx_len_addr).is_ok());
}
+35 -44
View File
@@ -3,47 +3,48 @@
// see LICENSE for license details.
use crate::{
node_runtime::{
self,
DispatchError,
},
node_runtime,
pair_signer,
test_context,
utils::wait_for_blocks,
};
use sp_keyring::AccountKeyring;
#[tokio::test]
async fn storage_plain_lookup() -> Result<(), subxt::Error<DispatchError>> {
async fn storage_plain_lookup() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
// Look up a plain value. Wait long enough that we don't get the genesis block data,
// because it may have no storage associated with it.
tokio::time::sleep(std::time::Duration::from_secs(6)).await;
let entry = ctx.api.storage().timestamp().now(None).await?;
wait_for_blocks(&api).await;
let addr = node_runtime::storage().timestamp().now();
let entry = api.storage().fetch_or_default(&addr, None).await?;
assert!(entry > 0);
Ok(())
}
#[tokio::test]
async fn storage_map_lookup() -> Result<(), subxt::Error<DispatchError>> {
async fn storage_map_lookup() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let signer = pair_signer(AccountKeyring::Alice.pair());
let alice = AccountKeyring::Alice.to_account_id();
// Do some transaction to bump the Alice nonce to 1:
ctx.api
.tx()
.system()
.remark(vec![1, 2, 3, 4, 5])?
.sign_and_submit_then_watch_default(&signer)
let remark_tx = node_runtime::tx().system().remark(vec![1, 2, 3, 4, 5]);
api.tx()
.sign_and_submit_then_watch_default(&remark_tx, &signer)
.await?
.wait_for_finalized_success()
.await?;
// Look up the nonce for the user (we expect it to be 1).
let entry = ctx.api.storage().system().account(&alice, None).await?;
let nonce_addr = node_runtime::storage().system().account(&alice);
let entry = api.storage().fetch_or_default(&nonce_addr, None).await?;
assert_eq!(entry.nonce, 1);
Ok(())
@@ -54,23 +55,15 @@ async fn storage_map_lookup() -> Result<(), subxt::Error<DispatchError>> {
// treated as a StorageKey (ie we should hash both values together with one hasher, rather
// than hash both values separately, or ignore the second value).
#[tokio::test]
async fn storage_n_mapish_key_is_properly_created(
) -> Result<(), subxt::Error<DispatchError>> {
async fn storage_n_mapish_key_is_properly_created() -> Result<(), subxt::Error> {
use codec::Encode;
use node_runtime::{
runtime_types::sp_core::crypto::KeyTypeId,
session::storage::KeyOwner,
};
use subxt::{
storage::StorageKeyPrefix,
StorageEntry,
};
use node_runtime::runtime_types::sp_core::crypto::KeyTypeId;
// This is what the generated code hashes a `session().key_owner(..)` key into:
let actual_key_bytes = KeyOwner(&KeyTypeId([1, 2, 3, 4]), &[5u8, 6, 7, 8])
.key()
.final_key(StorageKeyPrefix::new::<KeyOwner>())
.0;
let actual_key_bytes = node_runtime::storage()
.session()
.key_owner(KeyTypeId([1, 2, 3, 4]), [5u8, 6, 7, 8])
.to_bytes();
// Let's manually hash to what we assume it should be and compare:
let expected_key_bytes = {
@@ -89,8 +82,9 @@ async fn storage_n_mapish_key_is_properly_created(
}
#[tokio::test]
async fn storage_n_map_storage_lookup() -> Result<(), subxt::Error<DispatchError>> {
async fn storage_n_map_storage_lookup() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
// Boilerplate; we create a new asset class with ID 99, and then
// we "approveTransfer" of some of this asset class. This gives us an
@@ -98,30 +92,27 @@ async fn storage_n_map_storage_lookup() -> Result<(), subxt::Error<DispatchError
let signer = pair_signer(AccountKeyring::Alice.pair());
let alice = AccountKeyring::Alice.to_account_id();
let bob = AccountKeyring::Bob.to_account_id();
ctx.api
.tx()
let tx1 = node_runtime::tx()
.assets()
.create(99, alice.clone().into(), 1)?
.sign_and_submit_then_watch_default(&signer)
.create(99, alice.clone().into(), 1);
let tx2 = node_runtime::tx()
.assets()
.approve_transfer(99, bob.clone().into(), 123);
api.tx()
.sign_and_submit_then_watch_default(&tx1, &signer)
.await?
.wait_for_finalized_success()
.await?;
ctx.api
.tx()
.assets()
.approve_transfer(99, bob.clone().into(), 123)?
.sign_and_submit_then_watch_default(&signer)
api.tx()
.sign_and_submit_then_watch_default(&tx2, &signer)
.await?
.wait_for_finalized_success()
.await?;
// The actual test; look up this approval in storage:
let entry = ctx
.api
.storage()
.assets()
.approvals(&99, &alice, &bob, None)
.await?;
let addr = node_runtime::storage().assets().approvals(99, &alice, &bob);
let entry = api.storage().fetch(&addr, None).await?;
assert_eq!(entry.map(|a| a.amount), Some(123));
Ok(())
}
+8 -30
View File
@@ -10,20 +10,14 @@ pub(crate) use crate::{
use sp_core::sr25519::Pair;
use sp_keyring::AccountKeyring;
use subxt::{
Client,
DefaultConfig,
PairSigner,
SubstrateExtrinsicParams,
tx::PairSigner,
SubstrateConfig,
};
/// substrate node should be installed on the $PATH
const SUBSTRATE_NODE_PATH: &str = "substrate";
pub type NodeRuntimeParams = SubstrateExtrinsicParams<DefaultConfig>;
pub async fn test_node_process_with(
key: AccountKeyring,
) -> TestNodeProcess<DefaultConfig> {
pub async fn test_context_with(key: AccountKeyring) -> TestContext {
let path = std::env::var("SUBSTRATE_NODE_PATH").unwrap_or_else(|_| {
if which::which(SUBSTRATE_NODE_PATH).is_err() {
panic!("A substrate binary should be installed on your path for integration tests. \
@@ -32,35 +26,19 @@ pub async fn test_node_process_with(
SUBSTRATE_NODE_PATH.to_string()
});
let proc = TestNodeProcess::<DefaultConfig>::build(path.as_str())
let proc = TestContext::build(path.as_str())
.with_authority(key)
.spawn::<DefaultConfig>()
.spawn::<SubstrateConfig>()
.await;
proc.unwrap()
}
pub async fn test_node_process() -> TestNodeProcess<DefaultConfig> {
test_node_process_with(AccountKeyring::Alice).await
}
pub struct TestContext {
pub node_proc: TestNodeProcess<DefaultConfig>,
pub api: node_runtime::RuntimeApi<DefaultConfig, NodeRuntimeParams>,
}
impl TestContext {
pub fn client(&self) -> &Client<DefaultConfig> {
&self.api.client
}
}
pub type TestContext = TestNodeProcess<SubstrateConfig>;
pub async fn test_context() -> TestContext {
tracing_subscriber::fmt::try_init().ok();
let node_proc = test_node_process_with(AccountKeyring::Alice).await;
let api = node_proc.client().clone().to_runtime_api();
TestContext { node_proc, api }
test_context_with(AccountKeyring::Alice).await
}
pub fn pair_signer(pair: Pair) -> PairSigner<DefaultConfig, Pair> {
pub fn pair_signer(pair: Pair) -> PairSigner<SubstrateConfig, Pair> {
PairSigner::new(pair)
}
@@ -4,6 +4,8 @@
mod context;
mod node_proc;
mod wait_for_blocks;
pub use context::*;
pub use node_proc::TestNodeProcess;
pub use wait_for_blocks::wait_for_blocks;
@@ -16,16 +16,14 @@ use std::{
process,
};
use subxt::{
Client,
ClientBuilder,
Config,
OnlineClient,
};
/// Spawn a local substrate node for testing subxt.
pub struct TestNodeProcess<R: Config> {
proc: process::Child,
client: Client<R>,
ws_url: String,
client: OnlineClient<R>,
}
impl<R> Drop for TestNodeProcess<R>
@@ -61,13 +59,8 @@ where
}
/// Returns the subxt client connected to the running node.
pub fn client(&self) -> &Client<R> {
&self.client
}
/// Returns the address to which the client is connected.
pub fn ws_url(&self) -> &str {
&self.ws_url
pub fn client(&self) -> OnlineClient<R> {
self.client.clone()
}
}
@@ -129,15 +122,9 @@ impl TestNodeProcessBuilder {
let ws_url = format!("ws://127.0.0.1:{}", ws_port);
// Connect to the node with a subxt client:
let client = ClientBuilder::new().set_url(ws_url.clone()).build().await;
let client = OnlineClient::from_url(ws_url.clone()).await;
match client {
Ok(client) => {
Ok(TestNodeProcess {
proc,
client,
ws_url,
})
}
Ok(client) => Ok(TestNodeProcess { proc, client }),
Err(err) => {
let err = format!("Failed to connect to node rpc at {}: {}", ws_url, err);
tracing::error!("{}", err);
@@ -0,0 +1,17 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use subxt::{
client::OnlineClientT,
Config,
};
/// Wait for blocks to be produced before running tests. Waiting for two blocks
/// (the genesis block and another one) seems to be enough to allow tests
/// like `dry_run_passes` to work properly.
pub async fn wait_for_blocks<C: Config>(api: &impl OnlineClientT<C>) {
let mut sub = api.rpc().subscribe_blocks().await.unwrap();
sub.next().await;
sub.next().await;
}