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,
};
#[derive(Clone, Encode, Decode, Debug, Store)]
pub struct ErasRewardPointsStore<T: Staking> {
#[store(returns = EraRewardPoints<T::AccountId>)]
pub index: EraIndex,
pub _phantom: PhantomData<T>,
}
#[derive(Clone, Encode, Decode, Debug, Call)]
pub struct SetPayeeCall<T: Staking> {
pub payee: RewardDestination<T::AccountId>,
pub _runtime: PhantomData<T>,
}
pub type AuthorityId = crate::runtimes::app::grandpa::Public;
pub type AuthorityWeight = u64;
pub type AuthorityList = Vec<(AuthorityId, AuthorityWeight)>;
#[module]
#[rustfmt::skip]
pub trait Staking: Balances {
#![event_alias(ElectionCompute = u8)]
#![event_type(EraIndex)]
#![event_type(AuthorityList)]
}
#[derive(Encode, Decode, Copy, Clone, Debug, Default, Store)]
pub struct HistoryDepthStore<T: Staking> {
#[store(returns = u32)]
pub _runtime: PhantomData<T>,
}
#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Store)]
pub struct BondedStore<T: Staking> {
#[store(returns = Option<T::AccountId>)]
pub stash: T::AccountId,
}
#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Store)]
pub struct LedgerStore<T: Staking> {
#[store(returns = Option<StakingLedger<T::AccountId, T::Balance>>)]
pub controller: T::AccountId,
}
#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Store)]
pub struct PayeeStore<T: Staking> {
#[store(returns = RewardDestination<T::AccountId>)]
pub stash: T::AccountId,
}
#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Store)]
pub struct ValidatorsStore<T: Staking> {
#[store(returns = ValidatorPrefs)]
pub stash: T::AccountId,
}
#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Store)]
pub struct NominatorsStore<T: Staking> {
#[store(returns = Option<Nominations<T::AccountId>>)]
pub stash: T::AccountId,
}
#[derive(Encode, Copy, Clone, Debug, Store)]
pub struct CurrentEraStore<T: Staking> {
#[store(returns = Option<EraIndex>)]
pub _runtime: PhantomData<T>,
}
#[derive(PartialEq, Encode, Decode, Default, Debug)]
pub struct EraRewardPoints<AccountId: Ord> {
pub total: RewardPoint,
pub individual: BTreeMap<AccountId, RewardPoint>,
}
#[derive(Debug, Call, Encode)]
pub struct ChillCall<T: Staking> {
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> {}
#[derive(Clone, Debug, PartialEq, Call, Encode)]
pub struct ValidateCall<T: Staking> {
pub _runtime: PhantomData<T>,
pub prefs: ValidatorPrefs,
}
#[derive(Call, Encode, Debug)]
pub struct NominateCall<T: Staking> {
pub targets: Vec<T::Address>,
}
#[derive(Call, Encode, Debug)]
pub struct BondCall<T: Staking> {
pub contrller: T::AccountId,
#[codec(compact)]
pub value: T::Balance,
pub payee: RewardDestination<T::AccountId>,
}
#[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;
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}) => {
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}) => {
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?;
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}) => {
assert_eq!(events.len(), 3);
});
Ok(())
}
#[async_std::test]
async fn test_bond() -> Result<(), Error> {
env_logger::try_init().ok();
let alice = PairSigner::<RT, _>::new(AccountKeyring::Alice.pair());
let client = ClientBuilder::<RT>::new().build().await.unwrap();
let bond = client
.bond_and_watch(
&alice,
AccountKeyring::Bob.to_account_id(),
100_000_000_000,
RewardDestination::Stash,
)
.await;
assert_matches!(bond, Ok(ExtrinsicSuccess {block: _, extrinsic: _, events}) => {
assert_eq!(events.len(), 3);
});
let bond_again = client
.bond_and_watch(
&alice,
AccountKeyring::Bob.to_account_id(),
100_000_000_000,
RewardDestination::Stash,
)
.await;
assert_matches!(bond_again, Err(Error::Runtime(RuntimeError::Module(module_err))) => {
assert_eq!(module_err.module, "Staking");
assert_eq!(module_err.error, "AlreadyBonded");
});
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(())
}
}