mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 21:21:11 +00:00
Staking support (#99)
* Initial Staking API * Add more staking types * Reformat * Remove dead code * Fix missing documentation * Reformat * Staking: use proc macros * Add partial session support * Reformat * Try to implement nomination This currently fails with compilation errors I do not understand. * Use the #[module] macro This fixes a compile error * Explain undefined method diagnostics * Use ‘#[module]’ and implement session for Kusama * Don’t impl ‘Staking’ for all ‘T: System’ * Add staking payout support * Fix compilation errors and remove useless lifetimes * Respond to code review This fixes most of the issues found during review, with the exception of tests. * Make signing fallable and asynchronous This is needed for hardware wallets, which require human confirmation to sign transactions. Blocking on a human to sign transactions is not a good idea, and the signing might fail for many reasons (device unplugged, authorization not granted, etc). * Reformat * Refactor as suggested by Andrew Jones (@ascjones). * Reformat * Refactor as suggested by Andrew Jones (@ascjones). * Trait cleanups * Make the `Signer` impl require Send + Sync This is what Ledgeracio needs. * Use the correct key for staking maps They use the key type, not ‘PhantomData’. * Implement set_payee call * Switch to associated types for Staking * Implement `set_keys` This is needed for Ledgeracio. * Remove impl of Signer for Box<dyn Signer + Send + Sync> It isn’t needed, since Box implements Deref. * Fix Polkadot and Kusama ‘SessionKey’ structs I had failed to include the ‘Parachains’ component, which the default Substrate runtime doesn’t have. * Include a copy of `ValidatorId` This avoids needing to depend on Polkadot. * Fix syntax error in Cargo.toml * Fix compile errors * Add Debug impls * Fix return type of `BondedStore` * Use some upstream type definitions Also add `Default` impls. * Bump deps and fix build * Remove last reference to Kusama feature * Fix compilation errors * Implement the `concat` in `twox_64_concat` * Expose properties and per-era preferences * Era rewards point support I also did some refactoring. * Expose clipped exposure * Era reward points support * Make `PayoutStakersCall` public * Add in all default features for debugging * Chill support and update to latest Substrate * If property fetch fails, use dummy values * Fix tests * Fix header * Remove some code Ledgeracio does not need * More deletions * Remove more code not needed for Ledgeracio * Remove a pointless change in Cargo.toml w.r.t. upstream. * Remove more junk * Revert contracts put_code test to pure code (not using the macro) * Test contract instantiate * Fmt * WIP * Add some more submission tests * Reformat * More tests * Cleanup * Hopefully fix CI * Remove dead code * Test chill * Add missing docs * Remove unnecessary use * Revert "Remove unnecessary use" This reverts commit bc8bc36bde581f1892ea88a778dfe0fe5bff24d7. * Retry on temporary failures * Ignore the staking tests on CI * Obey the fmt * Run CI with at most one test thread * Implement tests for staking * More tests * Remove unhelpful println! * Revert changes in contract tests * Reformat * Remove spurious diff * More tests Co-authored-by: Demi M. Obenour <demiobenour@gmail.com> Co-authored-by: David Palm <dvdplm@gmail.com> Co-authored-by: Andrew Jones <ascjones@gmail.com>
This commit is contained in:
@@ -26,4 +26,4 @@ jobs:
|
||||
run: cargo build --workspace --verbose
|
||||
|
||||
- name: test
|
||||
run: cargo test --workspace --verbose
|
||||
run: cargo test --workspace --verbose -- --test-threads=1
|
||||
|
||||
+12
-1
@@ -17,6 +17,8 @@ keywords = ["parity", "substrate", "blockchain"]
|
||||
include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE"]
|
||||
|
||||
[features]
|
||||
kusama = []
|
||||
default = ["kusama"]
|
||||
client = ["substrate-subxt-client"]
|
||||
|
||||
# enable this feature to run tests which require a local dev chain node
|
||||
@@ -31,7 +33,7 @@ num-traits = { version = "0.2.12", default-features = false }
|
||||
serde = { version = "1.0.115", features = ["derive"] }
|
||||
serde_json = "1.0.57"
|
||||
url = "2.1.1"
|
||||
codec = { package = "parity-scale-codec", version = "1.3.4", default-features = false, features = ["derive", "full"] }
|
||||
codec = { package = "parity-scale-codec", version = "1.3.5", default-features = false, features = ["derive", "full"] }
|
||||
|
||||
frame-metadata = { version = "11.0.0-rc6", package = "frame-metadata" }
|
||||
frame-support = { version = "2.0.0-rc6", package = "frame-support" }
|
||||
@@ -39,6 +41,14 @@ sp-runtime = { version = "2.0.0-rc6", package = "sp-runtime" }
|
||||
sp-version = { version = "2.0.0-rc6", package = "sp-version" }
|
||||
pallet-indices = { version = "2.0.0-rc6", package = "pallet-indices" }
|
||||
hex = "0.4.2"
|
||||
sp-std = "2.0.0-rc6"
|
||||
application-crypto = { version = "2.0.0-rc6", package = "sp-application-crypto" }
|
||||
sp-finality-grandpa = "2.0.0-rc6"
|
||||
sp-consensus-babe = "0.8.0-rc6"
|
||||
pallet-im-online = "2.0.0-rc6"
|
||||
sp-authority-discovery = "2.0.0-rc6"
|
||||
pallet-staking = "2.0.0-rc6"
|
||||
|
||||
sp-rpc = { version = "2.0.0-rc6", package = "sp-rpc" }
|
||||
sp-core = { version = "2.0.0-rc6", package = "sp-core" }
|
||||
sc-rpc-api = { version = "0.8.0-rc6", package = "sc-rpc-api" }
|
||||
@@ -56,3 +66,4 @@ substrate-subxt-client = { version = "0.4.0", path = "client" }
|
||||
tempdir = "0.3.7"
|
||||
test-node = { path = "test-node" }
|
||||
wabt = "0.10.0"
|
||||
assert_matches = "1.3"
|
||||
|
||||
@@ -6,6 +6,9 @@ A library to **sub**mit e**xt**rinsics to a [substrate](https://github.com/parit
|
||||
|
||||
See [examples](./examples).
|
||||
|
||||
If you use `#[derive(Call)]` without `#[module]` in the same module, you will get errors
|
||||
complaining about an undefined method with a name starting with `with_`.
|
||||
|
||||
**Alternatives**
|
||||
|
||||
[substrate-api-client](https://github.com/scs/substrate-api-client) provides similar functionality.
|
||||
|
||||
@@ -54,6 +54,7 @@ pub trait Signer<T: Runtime> {
|
||||
}
|
||||
|
||||
/// Extrinsic signer using a private key.
|
||||
#[derive(Debug)]
|
||||
pub struct PairSigner<T: Runtime, P: Pair> {
|
||||
account_id: T::AccountId,
|
||||
nonce: Option<T::Index>,
|
||||
|
||||
@@ -31,6 +31,8 @@ use sp_core::storage::StorageKey;
|
||||
|
||||
pub mod balances;
|
||||
pub mod contracts;
|
||||
pub mod session;
|
||||
pub mod staking;
|
||||
pub mod sudo;
|
||||
pub mod system;
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Session support
|
||||
use crate::frame::system::{
|
||||
System,
|
||||
SystemEventsDecoder as _,
|
||||
};
|
||||
use codec::Encode;
|
||||
use frame_support::Parameter;
|
||||
use sp_runtime::traits::{
|
||||
Member,
|
||||
OpaqueKeys,
|
||||
};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
marker::PhantomData,
|
||||
};
|
||||
use substrate_subxt_proc_macro::Store;
|
||||
|
||||
/// Impls `Default::default` for some types that have a `_runtime` field of type
|
||||
/// `PhantomData` as their only field.
|
||||
macro_rules! default_impl {
|
||||
($name:ident) => {
|
||||
impl<T: Session> Default for $name<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
_runtime: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// The trait needed for this module.
|
||||
#[module]
|
||||
pub trait Session: System {
|
||||
/// The validator account identifier type for the runtime.
|
||||
type ValidatorId: Parameter + Debug + Ord + Default + Send + Sync + 'static;
|
||||
|
||||
/// The keys.
|
||||
type Keys: OpaqueKeys + Member + Parameter + Default;
|
||||
}
|
||||
|
||||
/// The current set of validators.
|
||||
#[derive(Encode, Store, Debug)]
|
||||
pub struct ValidatorsStore<T: Session> {
|
||||
#[store(returns = Vec<<T as Session>::ValidatorId>)]
|
||||
/// Marker for the runtime
|
||||
pub _runtime: PhantomData<T>,
|
||||
}
|
||||
|
||||
default_impl!(ValidatorsStore);
|
||||
|
||||
/// Set the session keys for a validator.
|
||||
#[derive(Encode, Call, Debug)]
|
||||
pub struct SetKeysCall<T: Session> {
|
||||
/// The keys
|
||||
pub keys: T::Keys,
|
||||
/// The proof. This is not currently used and can be set to an empty vector.
|
||||
pub proof: Vec<u8>,
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Implements support for the pallet_staking module.
|
||||
|
||||
use super::balances::{
|
||||
Balances,
|
||||
BalancesEventsDecoder as _,
|
||||
};
|
||||
use codec::{
|
||||
Decode,
|
||||
Encode,
|
||||
};
|
||||
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fmt::Debug,
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
pub use pallet_staking::{
|
||||
ActiveEraInfo,
|
||||
EraIndex,
|
||||
Exposure,
|
||||
Nominations,
|
||||
RewardDestination,
|
||||
RewardPoint,
|
||||
StakingLedger,
|
||||
ValidatorPrefs,
|
||||
};
|
||||
|
||||
/// Rewards for the last `HISTORY_DEPTH` eras.
|
||||
/// If reward hasn't been set or has been removed then 0 reward is returned.
|
||||
#[derive(Clone, Encode, Decode, Debug, Store)]
|
||||
pub struct ErasRewardPointsStore<T: Staking> {
|
||||
#[store(returns = EraRewardPoints<T::AccountId>)]
|
||||
/// Era index
|
||||
pub index: EraIndex,
|
||||
/// Marker for the runtime
|
||||
pub _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
/// Preference of what happens regarding validation.
|
||||
#[derive(Clone, Encode, Decode, Debug, Call)]
|
||||
pub struct SetPayeeCall<T: Staking> {
|
||||
/// The payee
|
||||
pub payee: RewardDestination,
|
||||
/// Marker for the runtime
|
||||
pub _runtime: PhantomData<T>,
|
||||
}
|
||||
|
||||
/// The subset of the `frame::Trait` that a client must implement.
|
||||
#[module]
|
||||
pub trait Staking: Balances {}
|
||||
|
||||
/// Number of eras to keep in history.
|
||||
///
|
||||
/// Information is kept for eras in `[current_era - history_depth; current_era]`.
|
||||
///
|
||||
/// Must be more than the number of eras delayed by session otherwise.
|
||||
/// I.e. active era must always be in history.
|
||||
/// I.e. `active_era > current_era - history_depth` must be guaranteed.
|
||||
#[derive(Encode, Decode, Copy, Clone, Debug, Default, Store)]
|
||||
pub struct HistoryDepthStore<T: Staking> {
|
||||
#[store(returns = u32)]
|
||||
/// Marker for the runtime
|
||||
pub _runtime: PhantomData<T>,
|
||||
}
|
||||
|
||||
/// Map from all locked "stash" accounts to the controller account.
|
||||
#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Store)]
|
||||
pub struct BondedStore<T: Staking> {
|
||||
#[store(returns = Option<T::AccountId>)]
|
||||
/// Tٗhe stash account
|
||||
pub stash: T::AccountId,
|
||||
}
|
||||
|
||||
/// Map from all (unlocked) "controller" accounts to the info regarding the staking.
|
||||
#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Store)]
|
||||
pub struct LedgerStore<T: Staking> {
|
||||
#[store(returns = Option<StakingLedger<T::AccountId, T::Balance>>)]
|
||||
/// The controller account
|
||||
pub controller: T::AccountId,
|
||||
}
|
||||
|
||||
/// Where the reward payment should be made. Keyed by stash.
|
||||
#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Store)]
|
||||
pub struct PayeeStore<T: Staking> {
|
||||
#[store(returns = RewardDestination)]
|
||||
/// Tٗhe stash account
|
||||
pub stash: T::AccountId,
|
||||
}
|
||||
|
||||
/// The map from (wannabe) validator stash key to the preferences of that validator.
|
||||
#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Store)]
|
||||
pub struct ValidatorsStore<T: Staking> {
|
||||
#[store(returns = ValidatorPrefs)]
|
||||
/// Tٗhe stash account
|
||||
pub stash: T::AccountId,
|
||||
}
|
||||
|
||||
/// The map from nominator stash key to the set of stash keys of all validators to nominate.
|
||||
#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Store)]
|
||||
pub struct NominatorsStore<T: Staking> {
|
||||
#[store(returns = Option<Nominations<T::AccountId>>)]
|
||||
/// Tٗhe stash account
|
||||
pub stash: T::AccountId,
|
||||
}
|
||||
|
||||
/// The current era index.
|
||||
///
|
||||
/// This is the latest planned era, depending on how the Session pallet queues the validator
|
||||
/// set, it might be active or not.
|
||||
#[derive(Encode, Copy, Clone, Debug, Store)]
|
||||
pub struct CurrentEraStore<T: Staking> {
|
||||
#[store(returns = Option<EraIndex>)]
|
||||
/// Marker for the runtime
|
||||
pub _runtime: PhantomData<T>,
|
||||
}
|
||||
|
||||
/// Reward points of an era. Used to split era total payout between validators.
|
||||
///
|
||||
/// This points will be used to reward validators and their respective nominators.
|
||||
#[derive(PartialEq, Encode, Decode, Default, Debug)]
|
||||
pub struct EraRewardPoints<AccountId: Ord> {
|
||||
/// Total number of points. Equals the sum of reward points for each validator.
|
||||
pub total: RewardPoint,
|
||||
/// The reward points earned by a given validator.
|
||||
pub individual: BTreeMap<AccountId, RewardPoint>,
|
||||
}
|
||||
|
||||
/// Declare no desire to either validate or nominate.
|
||||
///
|
||||
/// Effective at the beginning of the next era.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_ by the controller, not the stash.
|
||||
/// Can only be called when [`EraElectionStatus`] is `Closed`.
|
||||
#[derive(Debug, Call, Encode)]
|
||||
pub struct ChillCall<T: Staking> {
|
||||
/// Runtime marker
|
||||
pub _runtime: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Staking> Default for ChillCall<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
_runtime: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: Staking> Clone for ChillCall<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
_runtime: self._runtime,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: Staking> Copy for ChillCall<T> {}
|
||||
|
||||
/// Declare the desire to validate for the origin controller.
|
||||
///
|
||||
/// Effective at the beginning of the next era.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_ by the controller, not the stash.
|
||||
/// Can only be called when [`EraElectionStatus`] is `Closed`.
|
||||
#[derive(Clone, Debug, PartialEq, Call, Encode)]
|
||||
pub struct ValidateCall<T: Staking> {
|
||||
/// Runtime marker
|
||||
pub _runtime: PhantomData<T>,
|
||||
/// Validation preferences
|
||||
pub prefs: ValidatorPrefs,
|
||||
}
|
||||
|
||||
/// Declare the desire to nominate `targets` for the origin controller.
|
||||
///
|
||||
/// Effective at the beginning of the next era.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_ by the controller, not the stash.
|
||||
/// Can only be called when [`EraElectionStatus`] is `Closed`.
|
||||
#[derive(Call, Encode, Debug)]
|
||||
pub struct NominateCall<T: Staking> {
|
||||
/// The targets that are being nominated
|
||||
pub targets: Vec<T::Address>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "integration-tests")]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
error::RuntimeError,
|
||||
extrinsic::{
|
||||
PairSigner,
|
||||
Signer,
|
||||
},
|
||||
frame::balances::*,
|
||||
runtimes::KusamaRuntime as RT,
|
||||
ClientBuilder,
|
||||
Error,
|
||||
ExtrinsicSuccess,
|
||||
};
|
||||
use assert_matches::assert_matches;
|
||||
use sp_core::{
|
||||
sr25519,
|
||||
Pair,
|
||||
};
|
||||
use sp_keyring::AccountKeyring;
|
||||
|
||||
/// Helper function to generate a crypto pair from seed
|
||||
fn get_from_seed(seed: &str) -> sr25519::Pair {
|
||||
sr25519::Pair::from_string(&format!("//{}", seed), None)
|
||||
.expect("static values are valid; qed")
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_validate_with_controller_account() -> Result<(), Error> {
|
||||
env_logger::try_init().ok();
|
||||
let alice = PairSigner::<RT, _>::new(AccountKeyring::Alice.pair());
|
||||
let client = ClientBuilder::<RT>::new().build().await?;
|
||||
let announce_validator = client
|
||||
.validate_and_watch(&alice, ValidatorPrefs::default())
|
||||
.await;
|
||||
assert_matches!(announce_validator, Ok(ExtrinsicSuccess {block: _, extrinsic: _, events}) => {
|
||||
// TOOD: this is unsatisfying – can we do better?
|
||||
assert_eq!(events.len(), 3);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_validate_not_possible_for_stash_account() -> Result<(), Error> {
|
||||
env_logger::try_init().ok();
|
||||
let alice_stash = PairSigner::<RT, _>::new(get_from_seed("Alice//stash"));
|
||||
let client = ClientBuilder::<RT>::new().build().await?;
|
||||
let announce_validator = client
|
||||
.validate_and_watch(&alice_stash, ValidatorPrefs::default())
|
||||
.await;
|
||||
assert_matches!(announce_validator, Err(Error::Runtime(RuntimeError::Module(module_err))) => {
|
||||
assert_eq!(module_err.module, "Staking");
|
||||
assert_eq!(module_err.error, "NotController");
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_nominate_with_controller_account() -> Result<(), Error> {
|
||||
env_logger::try_init().ok();
|
||||
let alice = PairSigner::<RT, _>::new(AccountKeyring::Alice.pair());
|
||||
let bob = PairSigner::<RT, _>::new(AccountKeyring::Bob.pair());
|
||||
let client = ClientBuilder::<RT>::new().build().await?;
|
||||
|
||||
let nomination = client
|
||||
.nominate_and_watch(&alice, vec![bob.account_id().clone()])
|
||||
.await;
|
||||
assert_matches!(nomination, Ok(ExtrinsicSuccess {block: _, extrinsic: _, events}) => {
|
||||
// TOOD: this is unsatisfying – can we do better?
|
||||
assert_eq!(events.len(), 3);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_nominate_not_possible_for_stash_account() -> Result<(), Error> {
|
||||
env_logger::try_init().ok();
|
||||
let alice_stash =
|
||||
PairSigner::<RT, sr25519::Pair>::new(get_from_seed("Alice//stash"));
|
||||
let bob = PairSigner::<RT, _>::new(AccountKeyring::Bob.pair());
|
||||
let client = ClientBuilder::<RT>::new().build().await?;
|
||||
|
||||
let nomination = client
|
||||
.nominate_and_watch(&alice_stash, vec![bob.account_id().clone()])
|
||||
.await;
|
||||
assert_matches!(nomination, Err(Error::Runtime(RuntimeError::Module(module_err))) => {
|
||||
assert_eq!(module_err.module, "Staking");
|
||||
assert_eq!(module_err.error, "NotController");
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_chill_works_for_controller_only() -> Result<(), Error> {
|
||||
env_logger::try_init().ok();
|
||||
let alice_stash =
|
||||
PairSigner::<RT, sr25519::Pair>::new(get_from_seed("Alice//stash"));
|
||||
let bob_stash = PairSigner::<RT, sr25519::Pair>::new(get_from_seed("Bob//stash"));
|
||||
let alice = PairSigner::<RT, _>::new(AccountKeyring::Alice.pair());
|
||||
let client = ClientBuilder::<RT>::new().build().await?;
|
||||
|
||||
// this will fail the second time, which is why this is one test, not two
|
||||
client
|
||||
.nominate_and_watch(&alice, vec![bob_stash.account_id().clone()])
|
||||
.await?;
|
||||
let store = LedgerStore {
|
||||
controller: alice.account_id().clone(),
|
||||
};
|
||||
let StakingLedger { stash, .. } = client.fetch(&store, None).await?.unwrap();
|
||||
assert_eq!(alice_stash.account_id(), &stash);
|
||||
let chill = client.chill_and_watch(&alice_stash).await;
|
||||
|
||||
assert_matches!(chill, Err(Error::Runtime(RuntimeError::Module(module_err))) => {
|
||||
assert_eq!(module_err.module, "Staking");
|
||||
assert_eq!(module_err.error, "NotController");
|
||||
});
|
||||
|
||||
let chill = client.chill_and_watch(&alice).await;
|
||||
assert_matches!(chill, Ok(ExtrinsicSuccess {block: _, extrinsic: _, events}) => {
|
||||
// TOOD: this is unsatisfying – can we do better?
|
||||
assert_eq!(events.len(), 3);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_total_issuance_is_okay() -> Result<(), Error> {
|
||||
env_logger::try_init().ok();
|
||||
let client = ClientBuilder::<RT>::new().build().await?;
|
||||
let total_issuance = client.total_issuance(None).await?;
|
||||
assert!(total_issuance > 1u128 << 32);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_history_depth_is_okay() -> Result<(), Error> {
|
||||
env_logger::try_init().ok();
|
||||
let client = ClientBuilder::<RT>::new().build().await?;
|
||||
let history_depth = client.history_depth(None).await?;
|
||||
assert_eq!(history_depth, 84);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_current_era_is_okay() -> Result<(), Error> {
|
||||
env_logger::try_init().ok();
|
||||
let client = ClientBuilder::<RT>::new().build().await?;
|
||||
let _current_era = client
|
||||
.current_era(None)
|
||||
.await?
|
||||
.expect("current era always exists");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_era_reward_points_is_okay() -> Result<(), Error> {
|
||||
env_logger::try_init().ok();
|
||||
let client = ClientBuilder::<RT>::new().build().await?;
|
||||
let store = ErasRewardPointsStore {
|
||||
_phantom: PhantomData,
|
||||
index: 0,
|
||||
};
|
||||
|
||||
let _current_era = client
|
||||
.fetch(&store, None)
|
||||
.await?
|
||||
.expect("current era always exists");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -101,6 +101,8 @@ pub trait System {
|
||||
+ MaybeSerialize
|
||||
+ Debug
|
||||
+ MaybeDisplay
|
||||
+ Encode
|
||||
+ Decode
|
||||
+ Ord
|
||||
+ Default;
|
||||
|
||||
|
||||
+13
-2
@@ -35,7 +35,8 @@
|
||||
while_true,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unused_extern_crates
|
||||
unused_extern_crates,
|
||||
clippy::all
|
||||
)]
|
||||
#![allow(clippy::type_complexity)]
|
||||
|
||||
@@ -93,6 +94,7 @@ pub use crate::{
|
||||
rpc::{
|
||||
BlockNumber,
|
||||
ExtrinsicSuccess,
|
||||
SystemProperties,
|
||||
},
|
||||
runtimes::*,
|
||||
subscription::*,
|
||||
@@ -161,16 +163,18 @@ impl<T: Runtime> ClientBuilder<T> {
|
||||
}
|
||||
};
|
||||
let rpc = Rpc::new(client);
|
||||
let (metadata, genesis_hash, runtime_version) = future::join3(
|
||||
let (metadata, genesis_hash, runtime_version, properties) = future::join4(
|
||||
rpc.metadata(),
|
||||
rpc.genesis_hash(),
|
||||
rpc.runtime_version(None),
|
||||
rpc.system_properties(),
|
||||
)
|
||||
.await;
|
||||
Ok(Client {
|
||||
rpc,
|
||||
genesis_hash: genesis_hash?,
|
||||
metadata: metadata?,
|
||||
properties: properties.unwrap_or_else(|_| Default::default()),
|
||||
runtime_version: runtime_version?,
|
||||
_marker: PhantomData,
|
||||
page_size: self.page_size.unwrap_or(10),
|
||||
@@ -183,6 +187,7 @@ pub struct Client<T: Runtime> {
|
||||
rpc: Rpc<T>,
|
||||
genesis_hash: T::Hash,
|
||||
metadata: Metadata,
|
||||
properties: SystemProperties,
|
||||
runtime_version: RuntimeVersion,
|
||||
_marker: PhantomData<(fn() -> T::Signature, T::Extra)>,
|
||||
page_size: u32,
|
||||
@@ -194,6 +199,7 @@ impl<T: Runtime> Clone for Client<T> {
|
||||
rpc: self.rpc.clone(),
|
||||
genesis_hash: self.genesis_hash,
|
||||
metadata: self.metadata.clone(),
|
||||
properties: self.properties.clone(),
|
||||
runtime_version: self.runtime_version.clone(),
|
||||
_marker: PhantomData,
|
||||
page_size: self.page_size,
|
||||
@@ -258,6 +264,11 @@ impl<T: Runtime> Client<T> {
|
||||
&self.metadata
|
||||
}
|
||||
|
||||
/// Returns the system properties
|
||||
pub fn properties(&self) -> &SystemProperties {
|
||||
&self.properties
|
||||
}
|
||||
|
||||
/// Fetch the value under an unhashed storage key
|
||||
pub async fn fetch_unhashed<V: Decode>(
|
||||
&self,
|
||||
|
||||
+20
@@ -96,6 +96,18 @@ impl From<u32> for BlockNumber {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// System properties for a Substrate-based runtime
|
||||
pub struct SystemProperties {
|
||||
/// The address format
|
||||
pub ss58_format: u8,
|
||||
/// The number of digits after the decimal point in the native token
|
||||
pub token_decimals: u8,
|
||||
/// The symbol of the native token
|
||||
pub token_symbol: String,
|
||||
}
|
||||
|
||||
/// Client for substrate rpc interfaces
|
||||
pub struct Rpc<T: Runtime> {
|
||||
client: Client,
|
||||
@@ -208,6 +220,14 @@ impl<T: Runtime> Rpc<T> {
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
/// Fetch system properties
|
||||
pub async fn system_properties(&self) -> Result<SystemProperties, Error> {
|
||||
Ok(self
|
||||
.client
|
||||
.request("system_properties", Params::None)
|
||||
.await?)
|
||||
}
|
||||
|
||||
/// Get a header
|
||||
pub async fn header(
|
||||
&self,
|
||||
|
||||
+105
@@ -15,8 +15,10 @@
|
||||
// along with substrate-subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use codec::Encode;
|
||||
use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
|
||||
use sp_runtime::{
|
||||
generic::Header,
|
||||
impl_opaque_keys,
|
||||
traits::{
|
||||
BlakeTwo256,
|
||||
IdentifyAccount,
|
||||
@@ -25,6 +27,88 @@ use sp_runtime::{
|
||||
MultiSignature,
|
||||
OpaqueExtrinsic,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
/// BABE marker struct
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Babe;
|
||||
impl sp_runtime::BoundToRuntimeAppPublic for Babe {
|
||||
type Public = sp_consensus_babe::AuthorityId;
|
||||
}
|
||||
|
||||
/// ImOnline marker struct
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct ImOnline;
|
||||
impl sp_runtime::BoundToRuntimeAppPublic for ImOnline {
|
||||
type Public = ImOnlineId;
|
||||
}
|
||||
|
||||
/// GRANDPA marker struct
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Grandpa;
|
||||
impl sp_runtime::BoundToRuntimeAppPublic for Grandpa {
|
||||
type Public = sp_finality_grandpa::AuthorityId;
|
||||
}
|
||||
|
||||
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
|
||||
|
||||
#[cfg(feature = "kusama")]
|
||||
mod validator_app {
|
||||
use application_crypto::{
|
||||
app_crypto,
|
||||
sr25519,
|
||||
};
|
||||
app_crypto!(sr25519, sp_core::crypto::KeyTypeId(*b"para"));
|
||||
}
|
||||
|
||||
/// Parachain marker struct
|
||||
#[cfg(feature = "kusama")]
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Parachains;
|
||||
|
||||
#[cfg(feature = "kusama")]
|
||||
impl sp_runtime::BoundToRuntimeAppPublic for Parachains {
|
||||
type Public = validator_app::Public;
|
||||
}
|
||||
|
||||
/// Authority discovery marker struct
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct AuthorityDiscovery;
|
||||
impl sp_runtime::BoundToRuntimeAppPublic for AuthorityDiscovery {
|
||||
type Public = AuthorityDiscoveryId;
|
||||
}
|
||||
|
||||
impl_opaque_keys! {
|
||||
/// Substrate base runtime keys
|
||||
pub struct BasicSessionKeys {
|
||||
/// GRANDPA session key
|
||||
pub grandpa: Grandpa,
|
||||
/// BABE session key
|
||||
pub babe: Babe,
|
||||
/// ImOnline session key
|
||||
pub im_online: ImOnline,
|
||||
/// Parachain validation session key
|
||||
pub parachains: Parachains,
|
||||
/// AuthorityDiscovery session key
|
||||
pub authority_discovery: AuthorityDiscovery,
|
||||
}
|
||||
}
|
||||
|
||||
impl_opaque_keys! {
|
||||
/// Polkadot/Kusama runtime keys
|
||||
pub struct SessionKeys {
|
||||
/// GRANDPA session key
|
||||
pub grandpa: Grandpa,
|
||||
/// BABE session key
|
||||
pub babe: Babe,
|
||||
/// ImOnline session key
|
||||
pub im_online: ImOnline,
|
||||
/// ParachainValidator session key
|
||||
pub parachain_validator: Parachains,
|
||||
/// AuthorityDiscovery session key
|
||||
pub authority_discovery: AuthorityDiscovery,
|
||||
}
|
||||
}
|
||||
|
||||
use crate::{
|
||||
extrinsic::{
|
||||
@@ -37,6 +121,8 @@ use crate::{
|
||||
Balances,
|
||||
},
|
||||
contracts::Contracts,
|
||||
session::Session,
|
||||
staking::Staking,
|
||||
sudo::Sudo,
|
||||
system::System,
|
||||
},
|
||||
@@ -59,6 +145,8 @@ pub trait Runtime: System + Sized + Send + Sync + 'static {
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct DefaultNodeRuntime;
|
||||
|
||||
impl Staking for DefaultNodeRuntime {}
|
||||
|
||||
impl Runtime for DefaultNodeRuntime {
|
||||
type Signature = MultiSignature;
|
||||
type Extra = DefaultExtra<Self>;
|
||||
@@ -80,6 +168,11 @@ impl Balances for DefaultNodeRuntime {
|
||||
type Balance = u128;
|
||||
}
|
||||
|
||||
impl Session for DefaultNodeRuntime {
|
||||
type ValidatorId = <Self as System>::AccountId;
|
||||
type Keys = BasicSessionKeys;
|
||||
}
|
||||
|
||||
impl Contracts for DefaultNodeRuntime {}
|
||||
|
||||
impl Sudo for DefaultNodeRuntime {}
|
||||
@@ -114,6 +207,11 @@ impl Balances for NodeTemplateRuntime {
|
||||
type Balance = u128;
|
||||
}
|
||||
|
||||
impl Session for NodeTemplateRuntime {
|
||||
type ValidatorId = <Self as System>::AccountId;
|
||||
type Keys = BasicSessionKeys;
|
||||
}
|
||||
|
||||
impl Sudo for NodeTemplateRuntime {}
|
||||
|
||||
/// Concrete type definitions compatible with the node template, with the
|
||||
@@ -175,6 +273,13 @@ impl System for KusamaRuntime {
|
||||
type AccountData = AccountData<<Self as Balances>::Balance>;
|
||||
}
|
||||
|
||||
impl Session for KusamaRuntime {
|
||||
type ValidatorId = <Self as System>::AccountId;
|
||||
type Keys = SessionKeys;
|
||||
}
|
||||
|
||||
impl Staking for KusamaRuntime {}
|
||||
|
||||
impl Balances for KusamaRuntime {
|
||||
type Balance = u128;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ pallet-aura = { version = "2.0.0-rc6", default-features = false }
|
||||
pallet-balances = { version = "2.0.0-rc6", default-features = false }
|
||||
pallet-grandpa = { version = "2.0.0-rc6", default-features = false }
|
||||
pallet-randomness-collective-flip = { version = "2.0.0-rc6", default-features = false }
|
||||
pallet-staking = { version = "2.0.0-rc6", default-features = false }
|
||||
pallet-sudo = { version = "2.0.0-rc6", default-features = false }
|
||||
pallet-timestamp = { version = "2.0.0-rc6", default-features = false }
|
||||
pallet-transaction-payment = { version = "2.0.0-rc6", default-features = false }
|
||||
|
||||
Reference in New Issue
Block a user