From 5a0201c1303ed6b68339c9a24175c21a7e154974 Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Fri, 15 Jan 2021 17:34:35 +0800 Subject: [PATCH] Add Balances Locks (#197) * Add Balances Locks * Pass fmt * Add tests for Balances Locks In order to write this test, I just added the new staking::BondCall :). * . * In case you want to run multiple tests at the same time * Return Result in integration test --- src/frame/balances.rs | 87 ++++++++++++++++++++++++++++++++++++++++++- src/frame/staking.rs | 50 +++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) diff --git a/src/frame/balances.rs b/src/frame/balances.rs index 598421c8f1f1c..4d9a331bf8c9a 100644 --- a/src/frame/balances.rs +++ b/src/frame/balances.rs @@ -25,7 +25,10 @@ use codec::{ Encode, }; use core::marker::PhantomData; -use frame_support::Parameter; +use frame_support::{ + traits::LockIdentifier, + Parameter, +}; use sp_runtime::traits::{ AtLeast32Bit, MaybeSerialize, @@ -80,6 +83,47 @@ pub struct TotalIssuanceStore { pub _runtime: PhantomData, } +/// The locks of the balances module. +#[derive(Clone, Debug, Eq, PartialEq, Store, Encode, Decode)] +pub struct LocksStore<'a, T: Balances> { + #[store(returns = Vec>)] + /// Account to retrieve the balance locks for. + pub account_id: &'a T::AccountId, +} + +/// A single lock on a balance. There can be many of these on an account and they "overlap", so the +/// same balance is frozen by multiple locks. +#[derive(Clone, PartialEq, Eq, Encode, Decode)] +pub struct BalanceLock { + /// An identifier for this lock. Only one lock may be in existence for each identifier. + pub id: LockIdentifier, + /// The amount which the free balance may not drop below when this lock is in effect. + pub amount: Balance, + /// If true, then the lock remains in effect even for payment of transaction fees. + pub reasons: Reasons, +} + +impl Debug for BalanceLock { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct("BalanceLock") + .field("id", &String::from_utf8_lossy(&self.id)) + .field("amount", &self.amount) + .field("reasons", &self.reasons) + .finish() + } +} + +/// Simplified reasons for withdrawing balance. +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, Debug)] +pub enum Reasons { + /// Paying system transaction fees. + Fee, + /// Any reason other than paying system transaction fees. + Misc, + /// Any reason at all. + All, +} + /// Transfer some liquid free balance to another account. /// /// `transfer` will set the `FreeBalance` of the sender and receiver. @@ -181,6 +225,47 @@ mod tests { assert_ne!(info.data.free, 0); } + #[async_std::test] + #[cfg(feature = "integration-tests")] + async fn test_state_balance_lock() -> Result<(), crate::Error> { + use crate::{ + frame::staking::{ + BondCallExt, + RewardDestination, + }, + runtimes::KusamaRuntime as RT, + ClientBuilder, + }; + + env_logger::try_init().ok(); + let bob = PairSigner::::new(AccountKeyring::Bob.pair()); + let client = ClientBuilder::::new().build().await?; + + client + .bond_and_watch( + &bob, + AccountKeyring::Charlie.to_account_id(), + 100_000_000_000, + RewardDestination::Stash, + ) + .await?; + + let locks = client + .locks(&AccountKeyring::Bob.to_account_id(), None) + .await?; + + assert_eq!( + locks, + vec![BalanceLock { + id: *b"staking ", + amount: 100_000_000_000, + reasons: Reasons::All, + }] + ); + + Ok(()) + } + #[async_std::test] async fn test_transfer_error() { env_logger::try_init().ok(); diff --git a/src/frame/staking.rs b/src/frame/staking.rs index 06aa598143a3c..361d77df26ca3 100644 --- a/src/frame/staking.rs +++ b/src/frame/staking.rs @@ -196,6 +196,19 @@ pub struct NominateCall { pub targets: Vec, } +/// Take the origin account as a stash and lock up `value` of its balance. +/// `controller` will be the account that controls it. +#[derive(Call, Encode, Debug)] +pub struct BondCall { + /// Tٗhe controller account + pub contrller: T::AccountId, + /// Lock up `value` of its balance. + #[codec(compact)] + pub value: T::Balance, + /// Destination of Staking reward. + pub payee: RewardDestination, +} + #[cfg(test)] #[cfg(feature = "integration-tests")] mod tests { @@ -324,6 +337,43 @@ mod tests { Ok(()) } + #[async_std::test] + async fn test_bond() -> Result<(), Error> { + env_logger::try_init().ok(); + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + let client = ClientBuilder::::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}) => { + // TOOD: this is unsatisfying – can we do better? + 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();