Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Balances Locks #197

Merged
merged 9 commits into from
Jan 15, 2021
87 changes: 86 additions & 1 deletion src/frame/balances.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -80,6 +83,47 @@ pub struct TotalIssuanceStore<T: Balances> {
pub _runtime: PhantomData<T>,
}

/// The locks of the balances module.
#[derive(Clone, Debug, Eq, PartialEq, Store, Encode, Decode)]
pub struct LocksStore<'a, T: Balances> {
#[store(returns = Vec<BalanceLock<T::Balance>>)]
/// 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<Balance> {
/// 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<Balance: Debug> Debug for BalanceLock<Balance> {
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.
Expand Down Expand Up @@ -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::<RT, _>::new(AccountKeyring::Bob.pair());
let client = ClientBuilder::<RT>::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();
Expand Down
50 changes: 50 additions & 0 deletions src/frame/staking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,19 @@ pub struct NominateCall<T: Staking> {
pub targets: Vec<T::Address>,
}

/// 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: Staking> {
/// 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<T::AccountId>,
}

#[cfg(test)]
#[cfg(feature = "integration-tests")]
mod tests {
Expand Down Expand Up @@ -324,6 +337,43 @@ mod tests {
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}) => {
// 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();
Expand Down