Introduce safe types for handling imbalances (#2048)

* Be a little safer with total issuance.

* PairT instead of _Pair

* Remove rev causing upset

* Remove fees stuff.

* Fix build (including tests)

* Update runtime, bump version

* Fix

* Handle gas refunds properly.

* Rename identifier

ala #2025

* Address grumbles

* New not-quite-linear-typing API

* Slimmer API

* More linear-type test fixes

* Fix tests

* Tidy

* Fix some grumbles

* Keep unchecked functions private

* Remove another less-than-safe currency function and ensure that
contracts module can never create cash.

* Address a few grumbles and fix tests
This commit is contained in:
Gav Wood
2019-03-20 14:07:28 +01:00
committed by Robert Habermeier
parent f9e224e7b8
commit dcd77a147c
49 changed files with 1108 additions and 1100 deletions
+396 -190
View File
@@ -27,20 +27,36 @@
use rstd::prelude::*;
use rstd::{cmp, result};
use parity_codec::{Codec, Encode, Decode};
use srml_support::{StorageValue, StorageMap, Parameter, decl_event, decl_storage, decl_module, ensure};
use srml_support::{StorageValue, StorageMap, Parameter, decl_event, decl_storage, decl_module};
use srml_support::traits::{
UpdateBalanceOutcome, Currency, OnFreeBalanceZero, TransferAsset,
WithdrawReason, WithdrawReasons, ArithmeticType, LockIdentifier, LockableCurrency
UpdateBalanceOutcome, Currency, OnFreeBalanceZero, MakePayment, OnUnbalanced,
WithdrawReason, WithdrawReasons, LockIdentifier, LockableCurrency, ExistenceRequirement,
Imbalance, SignedImbalance
};
use srml_support::dispatch::Result;
use primitives::traits::{
Zero, SimpleArithmetic, As, StaticLookup, Member, CheckedAdd, CheckedSub, MaybeSerializeDebug
Zero, SimpleArithmetic, As, StaticLookup, Member, CheckedAdd, CheckedSub,
MaybeSerializeDebug, Saturating
};
use system::{IsDeadAccount, OnNewAccount, ensure_signed};
mod mock;
mod tests;
pub trait Subtrait<I: Instance = DefaultInstance>: system::Trait {
/// The balance of an account.
type Balance: Parameter + Member + SimpleArithmetic + Codec + Default + Copy + As<usize> + As<u64> + MaybeSerializeDebug;
/// A function which is invoked when the free-balance has fallen below the existential deposit and
/// has been reduced to zero.
///
/// Gives a chance to clean up resources associated with the given account.
type OnFreeBalanceZero: OnFreeBalanceZero<Self::AccountId>;
/// Handler for when a new account is created.
type OnNewAccount: OnNewAccount<Self::AccountId>;
}
pub trait Trait<I: Instance = DefaultInstance>: system::Trait {
/// The balance of an account.
type Balance: Parameter + Member + SimpleArithmetic + Codec + Default + Copy + As<usize> + As<u64> + MaybeSerializeDebug;
@@ -54,12 +70,24 @@ pub trait Trait<I: Instance = DefaultInstance>: system::Trait {
/// Handler for when a new account is created.
type OnNewAccount: OnNewAccount<Self::AccountId>;
/// Handler for the unbalanced reduction when taking transaction fees.
type TransactionPayment: OnUnbalanced<NegativeImbalance<Self, I>>;
/// Handler for the unbalanced reduction when taking fees associated with balance
/// transfer (which may also include account creation).
type TransferPayment: OnUnbalanced<NegativeImbalance<Self, I>>;
/// Handler for the unbalanced reduction when removing a dust account.
type DustRemoval: OnUnbalanced<NegativeImbalance<Self, I>>;
/// The overarching event type.
type Event: From<Event<Self, I>> + Into<<Self as system::Trait>::Event>;
}
impl<T: Trait> ArithmeticType for Module<T> {
type Type = <T as Trait>::Balance;
impl<T: Trait<I>, I: Instance> Subtrait<I> for T {
type Balance = T::Balance;
type OnFreeBalanceZero = T::OnFreeBalanceZero;
type OnNewAccount = T::OnNewAccount;
}
decl_event!(
@@ -118,6 +146,10 @@ decl_storage! {
pub TransferFee get(transfer_fee) config(): T::Balance;
/// The fee required to create an account. At least as big as ReclaimRebate.
pub CreationFee get(creation_fee) config(): T::Balance;
/// The fee to be paid for making a transaction; the base.
pub TransactionBaseFee get(transaction_base_fee) config(): T::Balance;
/// The fee to be paid for making a transaction; the per-byte portion.
pub TransactionByteFee get(transaction_byte_fee) config(): T::Balance;
/// Information regarding the vesting of a given account.
pub Vesting get(vesting) build(|config: &GenesisConfig<T, I>| {
@@ -190,7 +222,7 @@ decl_module! {
) {
let transactor = ensure_signed(origin)?;
let dest = T::Lookup::lookup(dest)?;
Self::make_transfer(&transactor, &dest, value)?;
<Self as Currency<_>>::transfer(&transactor, &dest, value)?;
}
/// Set the balances of a given account.
@@ -209,7 +241,9 @@ decl_module! {
// For funding methods, see Currency trait
impl<T: Trait<I>, I: Instance> Module<T, I> {
/// Get the amount that is currently being vested and cannot be transfered out of this account.
// PUBLIC IMMUTABLES
/// Get the amount that is currently being vested and cannot be transferred out of this account.
pub fn vesting_balance(who: &T::AccountId) -> T::Balance {
if let Some(v) = Self::vesting(who) {
Self::free_balance(who).min(v.locked_at(<system::Module<T>>::block_number()))
@@ -218,11 +252,16 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
}
}
// PRIVATE MUTABLES
/// Set the free balance of an account to some new value.
///
/// Will enforce ExistentialDeposit law, anulling the account as needed.
/// Will enforce ExistentialDeposit law, annulling the account as needed.
/// In that case it will return `AccountKilled`.
pub fn set_reserved_balance(who: &T::AccountId, balance: T::Balance) -> UpdateBalanceOutcome {
///
/// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that
/// the caller will do this.
fn set_reserved_balance(who: &T::AccountId, balance: T::Balance) -> UpdateBalanceOutcome {
if balance < Self::existential_deposit() {
<ReservedBalance<T, I>>::insert(who, balance);
Self::on_reserved_too_low(who);
@@ -234,13 +273,16 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
}
/// Set the free balance of an account to some new value. Will enforce ExistentialDeposit
/// law anulling the account as needed.
/// law annulling the account as needed.
///
/// Doesn't do any preparatory work for creating a new account, so should only be used when it
/// is known that the account already exists.
///
/// Returns if the account was successfully updated or update has led to killing of the account.
pub fn set_free_balance(who: &T::AccountId, balance: T::Balance) -> UpdateBalanceOutcome {
///
/// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that
/// the caller will do this.
fn set_free_balance(who: &T::AccountId, balance: T::Balance) -> UpdateBalanceOutcome {
// Commented out for no - but consider it instructive.
// assert!(!Self::total_balance(who).is_zero());
if balance < Self::existential_deposit() {
@@ -253,92 +295,35 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
}
}
/// Set the free balance on an account to some new value.
///
/// Same as [`set_free_balance`], but will create a new account.
///
/// Returns if the account was successfully updated or update has led to killing of the account.
///
/// [`set_free_balance`]: #method.set_free_balance
pub fn set_free_balance_creating(who: &T::AccountId, balance: T::Balance) -> UpdateBalanceOutcome {
let ed = <Module<T, I>>::existential_deposit();
// If the balance is too low, then the account is reaped.
// NOTE: There are two balances for every account: `reserved_balance` and
// `free_balance`. This contract subsystem only cares about the latter: whenever
// the term "balance" is used *here* it should be assumed to mean "free balance"
// in the rest of the module.
// Free balance can never be less than ED. If that happens, it gets reduced to zero
// and the account information relevant to this subsystem is deleted (i.e. the
// account is reaped).
// NOTE: This is orthogonal to the `Bondage` value that an account has, a high
// value of which makes even the `free_balance` unspendable.
if balance < ed {
Self::set_free_balance(who, balance);
UpdateBalanceOutcome::AccountKilled
} else {
if !<FreeBalance<T, I>>::exists(who) {
Self::new_account(&who, balance);
}
Self::set_free_balance(who, balance);
UpdateBalanceOutcome::Updated
}
}
/// Transfer some liquid free balance to another staker.
pub fn make_transfer(transactor: &T::AccountId, dest: &T::AccountId, value: T::Balance) -> Result {
let from_balance = Self::free_balance(transactor);
let to_balance = Self::free_balance(dest);
let would_create = to_balance.is_zero();
let fee = if would_create { Self::creation_fee() } else { Self::transfer_fee() };
let liability = match value.checked_add(&fee) {
Some(l) => l,
None => return Err("got overflow after adding a fee to value"),
};
let new_from_balance = match from_balance.checked_sub(&liability) {
None => return Err("balance too low to send value"),
Some(b) => b,
};
if would_create && value < Self::existential_deposit() {
return Err("value too low to create account");
}
Self::ensure_account_can_withdraw(transactor, value, WithdrawReason::Transfer, new_from_balance)?;
// NOTE: total stake being stored in the same type means that this could never overflow
// but better to be safe than sorry.
let new_to_balance = match to_balance.checked_add(&value) {
Some(b) => b,
None => return Err("destination balance too high to receive value"),
};
if transactor != dest {
Self::set_free_balance(transactor, new_from_balance);
Self::decrease_total_stake_by(fee);
Self::set_free_balance_creating(dest, new_to_balance);
Self::deposit_event(RawEvent::Transfer(transactor.clone(), dest.clone(), value, fee));
}
Ok(())
}
/// Register a new account (with existential balance).
///
/// This just calls appropriate hooks. It doesn't (necessarily) make any state changes.
fn new_account(who: &T::AccountId, balance: T::Balance) {
T::OnNewAccount::on_new_account(&who);
Self::deposit_event(RawEvent::NewAccount(who.clone(), balance.clone()));
}
/// Unregister an account.
///
/// This just removes the nonce and leaves an event.
fn reap_account(who: &T::AccountId) {
<system::AccountNonce<T>>::remove(who);
Self::deposit_event(RawEvent::ReapedAccount(who.clone()));
}
/// Kill an account's free portion.
/// Account's free balance has dropped below existential deposit. Kill its
/// free side and the account completely if its reserved size is already dead.
///
/// Will maintain total issuance.
fn on_free_too_low(who: &T::AccountId) {
Self::decrease_total_stake_by(Self::free_balance(who));
<FreeBalance<T, I>>::remove(who);
let dust = <FreeBalance<T, I>>::take(who);
<Locks<T, I>>::remove(who);
// underflow should never happen, but if it does, there's not much we can do about it.
if !dust.is_zero() {
T::DustRemoval::on_unbalanced(NegativeImbalance(dust));
}
T::OnFreeBalanceZero::on_free_balance_zero(who);
if Self::reserved_balance(who).is_zero() {
@@ -346,34 +331,204 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
}
}
/// Kill an account's reserved portion.
/// Account's reserved balance has dropped below existential deposit. Kill its
/// reserved side and the account completely if its free size is already dead.
///
/// Will maintain total issuance.
fn on_reserved_too_low(who: &T::AccountId) {
Self::decrease_total_stake_by(Self::reserved_balance(who));
<ReservedBalance<T, I>>::remove(who);
let dust = <ReservedBalance<T, I>>::take(who);
// underflow should never happen, but it if does, there's nothing to be done here.
if !dust.is_zero() {
T::DustRemoval::on_unbalanced(NegativeImbalance(dust));
}
if Self::free_balance(who).is_zero() {
Self::reap_account(who);
}
}
}
/// Increase TotalIssuance by Value.
pub fn increase_total_stake_by(value: T::Balance) {
if let Some(v) = <Module<T, I>>::total_issuance().checked_add(&value) {
<TotalIssuance<T, I>>::put(v);
/// Opaque, move-only struct with private fields that serves as a token denoting that
/// funds have been created without any equal and opposite accounting.
#[must_use]
pub struct PositiveImbalance<T: Subtrait<I>, I: Instance=DefaultInstance>(T::Balance);
/// Opaque, move-only struct with private fields that serves as a token denoting that
/// funds have been destroyed without any equal and opposite accounting.
#[must_use]
pub struct NegativeImbalance<T: Subtrait<I>, I: Instance=DefaultInstance>(T::Balance);
impl<T: Trait<I>, I: Instance> Imbalance<T::Balance> for PositiveImbalance<T, I> {
type Opposite = NegativeImbalance<T, I>;
fn zero() -> Self {
Self(Zero::zero())
}
fn drop_zero(self) -> result::Result<(), Self> {
if self.0.is_zero() {
Ok(())
} else {
Err(self)
}
}
/// Decrease TotalIssuance by Value.
pub fn decrease_total_stake_by(value: T::Balance) {
if let Some(v) = <Module<T, I>>::total_issuance().checked_sub(&value) {
<TotalIssuance<T, I>>::put(v);
fn split(self, amount: T::Balance) -> (Self, Self) {
let first = self.0.min(amount);
let second = self.0 - first;
(Self(first), Self(second))
}
fn merge(self, other: Self) -> Self {
Self(self.0.saturating_add(other.0))
}
fn subsume(&mut self, other: Self) {
self.0 = self.0.saturating_add(other.0)
}
fn offset(self, other: Self::Opposite) -> result::Result<Self, Self::Opposite> {
if self.0 >= other.0 {
Ok(Self(self.0 - other.0))
} else {
Err(NegativeImbalance(other.0 - self.0))
}
}
fn peek(&self) -> T::Balance {
self.0.clone()
}
}
/// Returns `Ok` iff the account is able to make a withdrawal of the given amount
/// for the given reason.
///
/// `Err(...)` with the reason why not otherwise.
pub fn ensure_account_can_withdraw(
impl<T: Trait<I>, I: Instance> Imbalance<T::Balance> for NegativeImbalance<T, I> {
type Opposite = PositiveImbalance<T, I>;
fn zero() -> Self {
Self(Zero::zero())
}
fn drop_zero(self) -> result::Result<(), Self> {
if self.0.is_zero() {
Ok(())
} else {
Err(self)
}
}
fn split(self, amount: T::Balance) -> (Self, Self) {
let first = self.0.min(amount);
let second = self.0 - first;
(Self(first), Self(second))
}
fn merge(self, other: Self) -> Self {
Self(self.0.saturating_add(other.0))
}
fn subsume(&mut self, other: Self) {
self.0 = self.0.saturating_add(other.0)
}
fn offset(self, other: Self::Opposite) -> result::Result<Self, Self::Opposite> {
if self.0 >= other.0 {
Ok(Self(self.0 - other.0))
} else {
Err(PositiveImbalance(other.0 - self.0))
}
}
fn peek(&self) -> T::Balance {
self.0.clone()
}
}
// TODO: #2052
// Somewhat ugly hack in order to gain access to module's `increase_total_issuance_by`
// using only the Subtrait (which defines only the types that are not dependent
// on Positive/NegativeImbalance). Subtrait must be used otherwise we end up with a
// circular dependency with Trait having some types be dependent on PositiveImbalance<Trait>
// and PositiveImbalance itself depending back on Trait for its Drop impl (and thus
// its type declaration).
// This works as long as `increase_total_issuance_by` doesn't use the Imbalance
// types (basically for charging fees).
// This should eventually be refactored so that the three type items that do
// depend on the Imbalance type (TransactionPayment, TransferPayment, DustRemoval)
// are placed in their own SRML module.
struct ElevatedTrait<T: Subtrait<I>, I: Instance>(T, I);
impl<T: Subtrait<I>, I: Instance> Clone for ElevatedTrait<T, I> {
fn clone(&self) -> Self { unimplemented!() }
}
impl<T: Subtrait<I>, I: Instance> PartialEq for ElevatedTrait<T, I> {
fn eq(&self, _: &Self) -> bool { unimplemented!() }
}
impl<T: Subtrait<I>, I: Instance> Eq for ElevatedTrait<T, I> {}
impl<T: Subtrait<I>, I: Instance> system::Trait for ElevatedTrait<T, I> {
type Origin = T::Origin;
type Index = T::Index;
type BlockNumber = T::BlockNumber;
type Hash = T::Hash;
type Hashing = T::Hashing;
type Digest = T::Digest;
type AccountId = T::AccountId;
type Lookup = T::Lookup;
type Header = T::Header;
type Event = ();
type Log = T::Log;
}
impl<T: Subtrait<I>, I: Instance> Trait<I> for ElevatedTrait<T, I> {
type Balance = T::Balance;
type OnFreeBalanceZero = T::OnFreeBalanceZero;
type OnNewAccount = T::OnNewAccount;
type Event = ();
type TransactionPayment = ();
type TransferPayment = ();
type DustRemoval = ();
}
impl<T: Subtrait<I>, I: Instance> Drop for PositiveImbalance<T, I> {
/// Basic drop handler will just square up the total issuance.
fn drop(&mut self) {
<TotalIssuance<ElevatedTrait<T, I>, I>>::mutate(|v| *v = v.saturating_add(self.0));
}
}
impl<T: Subtrait<I>, I: Instance> Drop for NegativeImbalance<T, I> {
/// Basic drop handler will just square up the total issuance.
fn drop(&mut self) {
<TotalIssuance<ElevatedTrait<T, I>, I>>::mutate(|v| *v = v.saturating_sub(self.0));
}
}
impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I>
where
T::Balance: MaybeSerializeDebug
{
type Balance = T::Balance;
type PositiveImbalance = PositiveImbalance<T, I>;
type NegativeImbalance = NegativeImbalance<T, I>;
fn total_balance(who: &T::AccountId) -> Self::Balance {
Self::free_balance(who) + Self::reserved_balance(who)
}
fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool {
Self::free_balance(who) >= value
}
fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool {
Self::free_balance(who)
.checked_sub(&value)
.map_or(false, |new_balance|
Self::ensure_can_withdraw(who, value, WithdrawReason::Reserve, new_balance).is_ok()
)
}
fn total_issuance() -> Self::Balance {
<TotalIssuance<T, I>>::get()
}
fn minimum_balance() -> Self::Balance {
Self::existential_deposit()
}
fn free_balance(who: &T::AccountId) -> Self::Balance {
<FreeBalance<T, I>>::get(who)
}
fn reserved_balance(who: &T::AccountId) -> Self::Balance {
<ReservedBalance<T, I>>::get(who)
}
fn ensure_can_withdraw(
who: &T::AccountId,
_amount: T::Balance,
reason: WithdrawReason,
@@ -397,69 +552,137 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
Err("account liquidity restrictions prevent withdrawal")
}
}
}
impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I>
where
T::Balance: MaybeSerializeDebug
{
type Balance = T::Balance;
fn transfer(transactor: &T::AccountId, dest: &T::AccountId, value: Self::Balance) -> Result {
let from_balance = Self::free_balance(transactor);
let to_balance = Self::free_balance(dest);
let would_create = to_balance.is_zero();
let fee = if would_create { Self::creation_fee() } else { Self::transfer_fee() };
let liability = match value.checked_add(&fee) {
Some(l) => l,
None => return Err("got overflow after adding a fee to value"),
};
fn total_balance(who: &T::AccountId) -> Self::Balance {
Self::free_balance(who) + Self::reserved_balance(who)
let new_from_balance = match from_balance.checked_sub(&liability) {
None => return Err("balance too low to send value"),
Some(b) => b,
};
if would_create && value < Self::existential_deposit() {
return Err("value too low to create account");
}
Self::ensure_can_withdraw(transactor, value, WithdrawReason::Transfer, new_from_balance)?;
// NOTE: total stake being stored in the same type means that this could never overflow
// but better to be safe than sorry.
let new_to_balance = match to_balance.checked_add(&value) {
Some(b) => b,
None => return Err("destination balance too high to receive value"),
};
if transactor != dest {
Self::set_free_balance(transactor, new_from_balance);
if !<FreeBalance<T, I>>::exists(dest) {
Self::new_account(dest, new_to_balance);
}
Self::set_free_balance(dest, new_to_balance);
T::TransferPayment::on_unbalanced(NegativeImbalance(fee));
Self::deposit_event(RawEvent::Transfer(transactor.clone(), dest.clone(), value, fee));
}
Ok(())
}
fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool {
Self::free_balance(who) >= value
}
fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool {
Self::free_balance(who)
.checked_sub(&value)
.map_or(false, |new_balance|
Self::ensure_account_can_withdraw(who, value, WithdrawReason::Reserve, new_balance).is_ok()
)
}
fn total_issuance() -> Self::Balance {
<TotalIssuance<T, I>>::get()
}
fn minimum_balance() -> Self::Balance {
Self::existential_deposit()
}
fn free_balance(who: &T::AccountId) -> Self::Balance {
<FreeBalance<T, I>>::get(who)
}
fn reserved_balance(who: &T::AccountId) -> Self::Balance {
<ReservedBalance<T, I>>::get(who)
}
fn slash(who: &T::AccountId, value: Self::Balance) -> Option<Self::Balance> {
let free_balance = Self::free_balance(who);
let free_slash = cmp::min(free_balance, value);
Self::set_free_balance(who, free_balance - free_slash);
Self::decrease_total_stake_by(free_slash);
if free_slash < value {
Self::slash_reserved(who, value - free_slash)
fn withdraw(
who: &T::AccountId,
value: Self::Balance,
reason: WithdrawReason,
liveness: ExistenceRequirement,
) -> result::Result<Self::NegativeImbalance, &'static str> {
if let Some(new_balance) = Self::free_balance(who).checked_sub(&value) {
if liveness == ExistenceRequirement::KeepAlive && new_balance < Self::existential_deposit() {
return Err("payment would kill account")
}
Self::ensure_can_withdraw(who, value, reason, new_balance)?;
Self::set_free_balance(who, new_balance);
Ok(NegativeImbalance(value))
} else {
None
Err("too few free funds in account")
}
}
fn reward(who: &T::AccountId, value: Self::Balance) -> result::Result<(), &'static str> {
fn slash(
who: &T::AccountId,
value: Self::Balance
) -> (Self::NegativeImbalance, Self::Balance) {
let free_balance = Self::free_balance(who);
let free_slash = cmp::min(free_balance, value);
Self::set_free_balance(who, free_balance - free_slash);
let remaining_slash = value - free_slash;
if !remaining_slash.is_zero() {
let reserved_balance = Self::reserved_balance(who);
let reserved_slash = cmp::min(reserved_balance, remaining_slash);
Self::set_reserved_balance(who, reserved_balance - reserved_slash);
(NegativeImbalance(free_slash + reserved_slash), remaining_slash - reserved_slash)
} else {
(NegativeImbalance(value), Zero::zero())
}
}
fn deposit_into_existing(
who: &T::AccountId,
value: Self::Balance
) -> result::Result<Self::PositiveImbalance, &'static str> {
if Self::total_balance(who).is_zero() {
return Err("beneficiary account must pre-exist");
}
Self::set_free_balance(who, Self::free_balance(who) + value);
Self::increase_total_stake_by(value);
Ok(())
Ok(PositiveImbalance(value))
}
fn increase_free_balance_creating(who: &T::AccountId, value: Self::Balance) -> UpdateBalanceOutcome {
Self::set_free_balance_creating(who, Self::free_balance(who) + value)
fn deposit_creating(
who: &T::AccountId,
value: Self::Balance,
) -> Self::PositiveImbalance {
let (imbalance, _) = Self::ensure_free_balance_is(who, Self::free_balance(who) + value);
if let SignedImbalance::Positive(p) = imbalance {
p
} else {
// Impossible, but be defensive.
Self::PositiveImbalance::zero()
}
}
fn ensure_free_balance_is(who: &T::AccountId, balance: T::Balance) -> (
SignedImbalance<Self::Balance, Self::PositiveImbalance>,
UpdateBalanceOutcome
) {
let original = Self::free_balance(who);
let imbalance = if original <= balance {
SignedImbalance::Positive(PositiveImbalance(balance - original))
} else {
SignedImbalance::Negative(NegativeImbalance(original - balance))
};
// If the balance is too low, then the account is reaped.
// NOTE: There are two balances for every account: `reserved_balance` and
// `free_balance`. This contract subsystem only cares about the latter: whenever
// the term "balance" is used *here* it should be assumed to mean "free balance"
// in the rest of the module.
// Free balance can never be less than ED. If that happens, it gets reduced to zero
// and the account information relevant to this subsystem is deleted (i.e. the
// account is reaped).
// NOTE: This is orthogonal to the `Bondage` value that an account has, a high
// value of which makes even the `free_balance` unspendable.
let outcome = if balance < <Module<T, I>>::existential_deposit() {
Self::set_free_balance(who, balance);
UpdateBalanceOutcome::AccountKilled
} else {
if !<FreeBalance<T, I>>::exists(who) {
Self::new_account(&who, balance);
}
Self::set_free_balance(who, balance);
UpdateBalanceOutcome::Updated
};
(imbalance, outcome)
}
fn reserve(who: &T::AccountId, value: Self::Balance) -> result::Result<(), &'static str> {
@@ -468,41 +691,36 @@ where
return Err("not enough free funds")
}
let new_balance = b - value;
Self::ensure_account_can_withdraw(who, value, WithdrawReason::Reserve, new_balance)?;
Self::ensure_can_withdraw(who, value, WithdrawReason::Reserve, new_balance)?;
Self::set_reserved_balance(who, Self::reserved_balance(who) + value);
Self::set_free_balance(who, new_balance);
Ok(())
}
fn unreserve(who: &T::AccountId, value: Self::Balance) -> Option<Self::Balance> {
fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance {
let b = Self::reserved_balance(who);
let actual = cmp::min(b, value);
Self::set_free_balance(who, Self::free_balance(who) + actual);
Self::set_reserved_balance(who, b - actual);
if actual == value {
None
} else {
Some(value - actual)
}
value - actual
}
fn slash_reserved(who: &T::AccountId, value: Self::Balance) -> Option<Self::Balance> {
fn slash_reserved(
who: &T::AccountId,
value: Self::Balance
) -> (Self::NegativeImbalance, Self::Balance) {
let b = Self::reserved_balance(who);
let slash = cmp::min(b, value);
// underflow should never happen, but it if does, there's nothing to be done here.
Self::set_reserved_balance(who, b - slash);
Self::decrease_total_stake_by(slash);
if value == slash {
None
} else {
Some(value - slash)
}
(NegativeImbalance(slash), value - slash)
}
fn repatriate_reserved(
slashed: &T::AccountId,
beneficiary: &T::AccountId,
value: Self::Balance
) -> result::Result<Option<Self::Balance>, &'static str> {
value: Self::Balance,
) -> result::Result<Self::Balance, &'static str> {
if Self::total_balance(beneficiary).is_zero() {
return Err("beneficiary account must pre-exist");
}
@@ -510,11 +728,7 @@ where
let slash = cmp::min(b, value);
Self::set_free_balance(beneficiary, Self::free_balance(beneficiary) + slash);
Self::set_reserved_balance(slashed, b - slash);
if value == slash {
Ok(None)
} else {
Ok(Some(value - slash))
}
Ok(value - slash)
}
}
@@ -592,26 +806,17 @@ where
}
}
impl<T: Trait<I>, I: Instance> TransferAsset<T::AccountId> for Module<T, I> {
type Amount = T::Balance;
fn transfer(from: &T::AccountId, to: &T::AccountId, amount: T::Balance) -> Result {
Self::make_transfer(from, to, amount)
}
fn withdraw(who: &T::AccountId, value: T::Balance, reason: WithdrawReason) -> Result {
let b = Self::free_balance(who);
ensure!(b >= value, "account has too few funds");
let new_balance = b - value;
Self::ensure_account_can_withdraw(who, value, reason, new_balance)?;
Self::set_free_balance(who, new_balance);
Self::decrease_total_stake_by(value);
Ok(())
}
fn deposit(who: &T::AccountId, value: T::Balance) -> Result {
Self::set_free_balance_creating(who, Self::free_balance(who) + value);
Self::increase_total_stake_by(value);
impl<T: Trait> MakePayment<T::AccountId> for Module<T> {
fn make_payment(transactor: &T::AccountId, encoded_len: usize) -> Result {
let encoded_len = <T::Balance as As<u64>>::sa(encoded_len as u64);
let transaction_fee = Self::transaction_base_fee() + Self::transaction_byte_fee() * encoded_len;
let imbalance = Self::withdraw(
transactor,
transaction_fee,
WithdrawReason::TransactionPayment,
ExistenceRequirement::KeepAlive
)?;
T::TransactionPayment::on_unbalanced(imbalance);
Ok(())
}
}
@@ -624,3 +829,4 @@ where
Self::total_balance(who).is_zero()
}
}
+19 -7
View File
@@ -50,9 +50,14 @@ impl Trait for Runtime {
type OnFreeBalanceZero = ();
type OnNewAccount = ();
type Event = ();
type TransactionPayment = ();
type DustRemoval = ();
type TransferPayment = ();
}
pub struct ExtBuilder {
transaction_base_fee: u64,
transaction_byte_fee: u64,
existential_deposit: u64,
transfer_fee: u64,
creation_fee: u64,
@@ -62,6 +67,8 @@ pub struct ExtBuilder {
impl Default for ExtBuilder {
fn default() -> Self {
Self {
transaction_base_fee: 0,
transaction_byte_fee: 0,
existential_deposit: 0,
transfer_fee: 0,
creation_fee: 0,
@@ -84,8 +91,16 @@ impl ExtBuilder {
self.creation_fee = creation_fee;
self
}
pub fn transaction_fees(mut self, base_fee: u64, byte_fee: u64) -> Self {
self.transaction_base_fee = base_fee;
self.transaction_byte_fee = byte_fee;
self
}
pub fn monied(mut self, monied: bool) -> Self {
self.monied = monied;
if self.existential_deposit == 0 {
self.existential_deposit = 1;
}
self
}
pub fn vesting(mut self, vesting: bool) -> Self {
@@ -94,16 +109,13 @@ impl ExtBuilder {
}
pub fn build(self) -> runtime_io::TestExternalities<Blake2Hasher> {
let mut t = system::GenesisConfig::<Runtime>::default().build_storage().unwrap().0;
let balance_factor = if self.existential_deposit > 0 {
256
} else {
1
};
t.extend(GenesisConfig::<Runtime> {
transaction_base_fee: self.transaction_base_fee,
transaction_byte_fee: self.transaction_byte_fee,
balances: if self.monied {
vec![(1, 10 * balance_factor), (2, 20 * balance_factor), (3, 30 * balance_factor), (4, 40 * balance_factor)]
vec![(1, 10 * self.existential_deposit), (2, 20 * self.existential_deposit), (3, 30 * self.existential_deposit), (4, 40 * self.existential_deposit)]
} else {
vec![(10, balance_factor), (20, balance_factor)]
vec![]
},
existential_deposit: self.existential_deposit,
transfer_fee: self.transfer_fee,
+94 -101
View File
@@ -23,7 +23,7 @@ use mock::{Balances, ExtBuilder, Runtime, System};
use runtime_io::with_externalities;
use srml_support::{
assert_noop, assert_ok, assert_err,
traits::{LockableCurrency, LockIdentifier, WithdrawReason, WithdrawReasons, Currency, TransferAsset}
traits::{LockableCurrency, LockIdentifier, WithdrawReason, WithdrawReasons, Currency, MakePayment}
};
const ID_1: LockIdentifier = *b"1 ";
@@ -32,123 +32,123 @@ const ID_3: LockIdentifier = *b"3 ";
#[test]
fn basic_locking_should_work() {
with_externalities(&mut ExtBuilder::default().monied(true).build(), || {
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).build(), || {
assert_eq!(Balances::free_balance(&1), 10);
Balances::set_lock(ID_1, &1, 9, u64::max_value(), WithdrawReasons::all());
assert_noop!(<Balances as TransferAsset<_>>::transfer(&1, &2, 5), "account liquidity restrictions prevent withdrawal");
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 5), "account liquidity restrictions prevent withdrawal");
});
}
#[test]
fn partial_locking_should_work() {
with_externalities(&mut ExtBuilder::default().monied(true).build(), || {
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).build(), || {
Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all());
assert_ok!(<Balances as TransferAsset<_>>::transfer(&1, &2, 1));
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
});
}
#[test]
fn lock_removal_should_work() {
with_externalities(&mut ExtBuilder::default().monied(true).build(), || {
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).build(), || {
Balances::set_lock(ID_1, &1, u64::max_value(), u64::max_value(), WithdrawReasons::all());
Balances::remove_lock(ID_1, &1);
assert_ok!(<Balances as TransferAsset<_>>::transfer(&1, &2, 1));
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
});
}
#[test]
fn lock_replacement_should_work() {
with_externalities(&mut ExtBuilder::default().monied(true).build(), || {
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).build(), || {
Balances::set_lock(ID_1, &1, u64::max_value(), u64::max_value(), WithdrawReasons::all());
Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all());
assert_ok!(<Balances as TransferAsset<_>>::transfer(&1, &2, 1));
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
});
}
#[test]
fn double_locking_should_work() {
with_externalities(&mut ExtBuilder::default().monied(true).build(), || {
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).build(), || {
Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all());
Balances::set_lock(ID_2, &1, 5, u64::max_value(), WithdrawReasons::all());
assert_ok!(<Balances as TransferAsset<_>>::transfer(&1, &2, 1));
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
});
}
#[test]
fn combination_locking_should_work() {
with_externalities(&mut ExtBuilder::default().monied(true).build(), || {
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).build(), || {
Balances::set_lock(ID_1, &1, u64::max_value(), 0, WithdrawReasons::none());
Balances::set_lock(ID_2, &1, 0, u64::max_value(), WithdrawReasons::none());
Balances::set_lock(ID_3, &1, 0, 0, WithdrawReasons::all());
assert_ok!(<Balances as TransferAsset<_>>::transfer(&1, &2, 1));
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
});
}
#[test]
fn lock_value_extension_should_work() {
with_externalities(&mut ExtBuilder::default().monied(true).build(), || {
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).build(), || {
Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all());
assert_noop!(<Balances as TransferAsset<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
Balances::extend_lock(ID_1, &1, 2, u64::max_value(), WithdrawReasons::all());
assert_noop!(<Balances as TransferAsset<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
Balances::extend_lock(ID_1, &1, 8, u64::max_value(), WithdrawReasons::all());
assert_noop!(<Balances as TransferAsset<_>>::transfer(&1, &2, 3), "account liquidity restrictions prevent withdrawal");
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 3), "account liquidity restrictions prevent withdrawal");
});
}
#[test]
fn lock_reasons_should_work() {
with_externalities(&mut ExtBuilder::default().monied(true).build(), || {
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).transaction_fees(0, 1).build(), || {
Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::Transfer.into());
assert_noop!(<Balances as TransferAsset<_>>::transfer(&1, &2, 1), "account liquidity restrictions prevent withdrawal");
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 1), "account liquidity restrictions prevent withdrawal");
assert_ok!(<Balances as Currency<_>>::reserve(&1, 1));
assert_ok!(<Balances as TransferAsset<_>>::withdraw(&1, 1, WithdrawReason::TransactionPayment));
assert_ok!(<Balances as MakePayment<_>>::make_payment(&1, 1));
Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::Reserve.into());
assert_ok!(<Balances as TransferAsset<_>>::transfer(&1, &2, 1));
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
assert_noop!(<Balances as Currency<_>>::reserve(&1, 1), "account liquidity restrictions prevent withdrawal");
assert_ok!(<Balances as TransferAsset<_>>::withdraw(&1, 1, WithdrawReason::TransactionPayment));
assert_ok!(<Balances as MakePayment<_>>::make_payment(&1, 1));
Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::TransactionPayment.into());
assert_ok!(<Balances as TransferAsset<_>>::transfer(&1, &2, 1));
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
assert_ok!(<Balances as Currency<_>>::reserve(&1, 1));
assert_noop!(<Balances as TransferAsset<_>>::withdraw(&1, 1, WithdrawReason::TransactionPayment), "account liquidity restrictions prevent withdrawal");
assert_noop!(<Balances as MakePayment<_>>::make_payment(&1, 1), "account liquidity restrictions prevent withdrawal");
});
}
#[test]
fn lock_block_number_should_work() {
with_externalities(&mut ExtBuilder::default().monied(true).build(), || {
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).build(), || {
Balances::set_lock(ID_1, &1, 10, 2, WithdrawReasons::all());
assert_noop!(<Balances as TransferAsset<_>>::transfer(&1, &2, 1), "account liquidity restrictions prevent withdrawal");
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 1), "account liquidity restrictions prevent withdrawal");
System::set_block_number(2);
assert_ok!(<Balances as TransferAsset<_>>::transfer(&1, &2, 1));
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
});
}
#[test]
fn lock_block_number_extension_should_work() {
with_externalities(&mut ExtBuilder::default().monied(true).build(), || {
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).build(), || {
Balances::set_lock(ID_1, &1, 10, 2, WithdrawReasons::all());
assert_noop!(<Balances as TransferAsset<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
Balances::extend_lock(ID_1, &1, 10, 1, WithdrawReasons::all());
assert_noop!(<Balances as TransferAsset<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
System::set_block_number(2);
Balances::extend_lock(ID_1, &1, 10, 8, WithdrawReasons::all());
assert_noop!(<Balances as TransferAsset<_>>::transfer(&1, &2, 3), "account liquidity restrictions prevent withdrawal");
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 3), "account liquidity restrictions prevent withdrawal");
});
}
#[test]
fn lock_reasons_extension_should_work() {
with_externalities(&mut ExtBuilder::default().monied(true).build(), || {
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).build(), || {
Balances::set_lock(ID_1, &1, 10, 10, WithdrawReason::Transfer.into());
assert_noop!(<Balances as TransferAsset<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
Balances::extend_lock(ID_1, &1, 10, 10, WithdrawReasons::none());
assert_noop!(<Balances as TransferAsset<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
Balances::extend_lock(ID_1, &1, 10, 10, WithdrawReason::Reserve.into());
assert_noop!(<Balances as TransferAsset<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
});
}
@@ -168,7 +168,7 @@ fn default_indexing_on_new_accounts_should_not_work2() {
"value too low to create account"
);
assert_eq!(Balances::is_dead_account(&5), true); // account 5 should not exist
assert_eq!(Balances::free_balance(&1), 256 * 10);
assert_eq!(Balances::free_balance(&1), 100);
},
);
}
@@ -196,7 +196,7 @@ fn reserved_balance_should_prevent_reclaim_count() {
assert_eq!(Balances::total_balance(&5), 256 * 1 + 0x69);
assert_eq!(Balances::is_dead_account(&5), false);
assert_eq!(Balances::slash(&2, 256 * 18 + 2), None); // account 2 gets slashed
assert!(Balances::slash(&2, 256 * 18 + 2).1.is_zero()); // account 2 gets slashed
assert_eq!(Balances::total_balance(&2), 0); // "reserve" account reduced to 255 (below ED) so account deleted
assert_eq!(System::account_nonce(&2), 0); // nonce zero
assert_eq!(Balances::is_dead_account(&2), true);
@@ -213,7 +213,7 @@ fn reserved_balance_should_prevent_reclaim_count() {
fn reward_should_work() {
with_externalities(&mut ExtBuilder::default().monied(true).build(), || {
assert_eq!(Balances::total_balance(&1), 10);
assert_ok!(Balances::reward(&1, 10));
assert_ok!(Balances::deposit_into_existing(&1, 10).map(drop));
assert_eq!(Balances::total_balance(&1), 20);
assert_eq!(<TotalIssuance<Runtime>>::get(), 110);
});
@@ -223,17 +223,17 @@ fn reward_should_work() {
fn dust_account_removal_should_work() {
with_externalities(
&mut ExtBuilder::default()
.existential_deposit(256 * 10)
.existential_deposit(100)
.monied(true)
.build(),
|| {
System::inc_account_nonce(&2);
assert_eq!(System::account_nonce(&2), 1);
assert_eq!(Balances::total_balance(&2), 256 * 20);
assert_eq!(Balances::total_balance(&2), 2000);
assert_ok!(Balances::transfer(Some(2).into(), 5, 256 * 10 + 1)); // index 1 (account 2) becomes zombie
assert_ok!(Balances::transfer(Some(2).into(), 5, 1901)); // index 1 (account 2) becomes zombie
assert_eq!(Balances::total_balance(&2), 0);
assert_eq!(Balances::total_balance(&5), 256 * 10 + 1);
assert_eq!(Balances::total_balance(&5), 1901);
assert_eq!(System::account_nonce(&2), 0);
},
);
@@ -243,17 +243,17 @@ fn dust_account_removal_should_work() {
fn dust_account_removal_should_work2() {
with_externalities(
&mut ExtBuilder::default()
.existential_deposit(256 * 10)
.existential_deposit(100)
.creation_fee(50)
.monied(true)
.build(),
|| {
System::inc_account_nonce(&2);
assert_eq!(System::account_nonce(&2), 1);
assert_eq!(Balances::total_balance(&2), 256 * 20);
assert_ok!(Balances::transfer(Some(2).into(), 5, 256 * 10)); // index 1 (account 2) becomes zombie for 256*10 + 50(fee) < 256 * 10 (ext_deposit)
assert_eq!(Balances::total_balance(&2), 2000);
assert_ok!(Balances::transfer(Some(2).into(), 5, 1851)); // index 1 (account 2) becomes zombie for 256*10 + 50(fee) < 256 * 10 (ext_deposit)
assert_eq!(Balances::total_balance(&2), 0);
assert_eq!(Balances::total_balance(&5), 256 * 10);
assert_eq!(Balances::total_balance(&5), 1851);
assert_eq!(System::account_nonce(&2), 0);
},
);
@@ -262,7 +262,7 @@ fn dust_account_removal_should_work2() {
#[test]
fn balance_works() {
with_externalities(&mut ExtBuilder::default().build(), || {
Balances::set_free_balance(&1, 42);
let _ = Balances::deposit_creating(&1, 42);
assert_eq!(Balances::free_balance(&1), 42);
assert_eq!(Balances::reserved_balance(&1), 0);
assert_eq!(Balances::total_balance(&1), 42);
@@ -275,8 +275,7 @@ fn balance_works() {
#[test]
fn balance_transfer_works() {
with_externalities(&mut ExtBuilder::default().build(), || {
Balances::set_free_balance(&1, 111);
Balances::increase_total_stake_by(111);
let _ = Balances::deposit_creating(&1, 111);
assert_ok!(Balances::transfer(Some(1).into(), 2, 69));
assert_eq!(Balances::total_balance(&1), 42);
assert_eq!(Balances::total_balance(&2), 69);
@@ -286,7 +285,7 @@ fn balance_transfer_works() {
#[test]
fn reserving_balance_should_work() {
with_externalities(&mut ExtBuilder::default().build(), || {
Balances::set_free_balance(&1, 111);
let _ = Balances::deposit_creating(&1, 111);
assert_eq!(Balances::total_balance(&1), 111);
assert_eq!(Balances::free_balance(&1), 111);
@@ -303,7 +302,7 @@ fn reserving_balance_should_work() {
#[test]
fn balance_transfer_when_reserved_should_not_work() {
with_externalities(&mut ExtBuilder::default().build(), || {
Balances::set_free_balance(&1, 111);
let _ = Balances::deposit_creating(&1, 111);
assert_ok!(Balances::reserve(&1, 69));
assert_noop!(Balances::transfer(Some(1).into(), 2, 69), "balance too low to send value");
});
@@ -312,7 +311,7 @@ fn balance_transfer_when_reserved_should_not_work() {
#[test]
fn deducting_balance_should_work() {
with_externalities(&mut ExtBuilder::default().build(), || {
Balances::set_free_balance(&1, 111);
let _ = Balances::deposit_creating(&1, 111);
assert_ok!(Balances::reserve(&1, 69));
assert_eq!(Balances::free_balance(&1), 42);
});
@@ -321,7 +320,7 @@ fn deducting_balance_should_work() {
#[test]
fn refunding_balance_should_work() {
with_externalities(&mut ExtBuilder::default().build(), || {
Balances::set_free_balance(&1, 42);
let _ = Balances::deposit_creating(&1, 42);
Balances::set_reserved_balance(&1, 69);
Balances::unreserve(&1, 69);
assert_eq!(Balances::free_balance(&1), 111);
@@ -332,33 +331,31 @@ fn refunding_balance_should_work() {
#[test]
fn slashing_balance_should_work() {
with_externalities(&mut ExtBuilder::default().build(), || {
Balances::set_free_balance(&1, 111);
Balances::increase_total_stake_by(111);
let _ = Balances::deposit_creating(&1, 111);
assert_ok!(Balances::reserve(&1, 69));
assert!(Balances::slash(&1, 69).is_none());
assert!(Balances::slash(&1, 69).1.is_zero());
assert_eq!(Balances::free_balance(&1), 0);
assert_eq!(Balances::reserved_balance(&1), 42);
assert_eq!(<TotalIssuance<Runtime>>::get(), 44);
assert_eq!(<TotalIssuance<Runtime>>::get(), 42);
});
}
#[test]
fn slashing_incomplete_balance_should_work() {
with_externalities(&mut ExtBuilder::default().build(), || {
Balances::set_free_balance(&1, 42);
Balances::increase_total_stake_by(42);
let _ = Balances::deposit_creating(&1, 42);
assert_ok!(Balances::reserve(&1, 21));
assert!(Balances::slash(&1, 69).is_some());
assert_eq!(Balances::slash(&1, 69).1, 27);
assert_eq!(Balances::free_balance(&1), 0);
assert_eq!(Balances::reserved_balance(&1), 0);
assert_eq!(<TotalIssuance<Runtime>>::get(), 2);
assert_eq!(<TotalIssuance<Runtime>>::get(), 0);
});
}
#[test]
fn unreserving_balance_should_work() {
with_externalities(&mut ExtBuilder::default().build(), || {
Balances::set_free_balance(&1, 111);
let _ = Balances::deposit_creating(&1, 111);
assert_ok!(Balances::reserve(&1, 111));
Balances::unreserve(&1, 42);
assert_eq!(Balances::reserved_balance(&1), 69);
@@ -369,36 +366,34 @@ fn unreserving_balance_should_work() {
#[test]
fn slashing_reserved_balance_should_work() {
with_externalities(&mut ExtBuilder::default().build(), || {
Balances::set_free_balance(&1, 111);
Balances::increase_total_stake_by(111);
let _ = Balances::deposit_creating(&1, 111);
assert_ok!(Balances::reserve(&1, 111));
assert!(Balances::slash_reserved(&1, 42).is_none());
assert_eq!(Balances::slash_reserved(&1, 42).1, 0);
assert_eq!(Balances::reserved_balance(&1), 69);
assert_eq!(Balances::free_balance(&1), 0);
assert_eq!(<TotalIssuance<Runtime>>::get(), 71);
assert_eq!(<TotalIssuance<Runtime>>::get(), 69);
});
}
#[test]
fn slashing_incomplete_reserved_balance_should_work() {
with_externalities(&mut ExtBuilder::default().build(), || {
Balances::set_free_balance(&1, 111);
Balances::increase_total_stake_by(111);
let _ = Balances::deposit_creating(&1, 111);
assert_ok!(Balances::reserve(&1, 42));
assert!(Balances::slash_reserved(&1, 69).is_some());
assert_eq!(Balances::slash_reserved(&1, 69).1, 27);
assert_eq!(Balances::free_balance(&1), 69);
assert_eq!(Balances::reserved_balance(&1), 0);
assert_eq!(<TotalIssuance<Runtime>>::get(), 71);
assert_eq!(<TotalIssuance<Runtime>>::get(), 69);
});
}
#[test]
fn transferring_reserved_balance_should_work() {
with_externalities(&mut ExtBuilder::default().build(), || {
Balances::set_free_balance(&1, 110);
Balances::set_free_balance(&2, 1);
let _ = Balances::deposit_creating(&1, 110);
let _ = Balances::deposit_creating(&2, 1);
assert_ok!(Balances::reserve(&1, 110));
assert_ok!(Balances::repatriate_reserved(&1, &2, 41), None);
assert_ok!(Balances::repatriate_reserved(&1, &2, 41), 0);
assert_eq!(Balances::reserved_balance(&1), 69);
assert_eq!(Balances::free_balance(&1), 0);
assert_eq!(Balances::reserved_balance(&2), 0);
@@ -409,7 +404,7 @@ fn transferring_reserved_balance_should_work() {
#[test]
fn transferring_reserved_balance_to_nonexistent_should_fail() {
with_externalities(&mut ExtBuilder::default().build(), || {
Balances::set_free_balance(&1, 111);
let _ = Balances::deposit_creating(&1, 111);
assert_ok!(Balances::reserve(&1, 111));
assert_noop!(Balances::repatriate_reserved(&1, &2, 42), "beneficiary account must pre-exist");
});
@@ -418,10 +413,10 @@ fn transferring_reserved_balance_to_nonexistent_should_fail() {
#[test]
fn transferring_incomplete_reserved_balance_should_work() {
with_externalities(&mut ExtBuilder::default().build(), || {
Balances::set_free_balance(&1, 110);
Balances::set_free_balance(&2, 1);
let _ = Balances::deposit_creating(&1, 110);
let _ = Balances::deposit_creating(&2, 1);
assert_ok!(Balances::reserve(&1, 41));
assert!(Balances::repatriate_reserved(&1, &2, 69).unwrap().is_some());
assert_ok!(Balances::repatriate_reserved(&1, &2, 69), 28);
assert_eq!(Balances::reserved_balance(&1), 0);
assert_eq!(Balances::free_balance(&1), 69);
assert_eq!(Balances::reserved_balance(&2), 0);
@@ -450,27 +445,27 @@ fn account_removal_on_free_too_low() {
with_externalities(
&mut ExtBuilder::default().existential_deposit(100).build(),
|| {
// Setup two accounts with free balance above the exsistential threshold.
{
Balances::set_free_balance(&1, 110);
Balances::increase_total_stake_by(110);
assert_eq!(<TotalIssuance<Runtime>>::get(), 0);
Balances::set_free_balance(&2, 110);
Balances::increase_total_stake_by(110);
// Setup two accounts with free balance above the existential threshold.
let _ = Balances::deposit_creating(&1, 110);
let _ = Balances::deposit_creating(&2, 110);
assert_eq!(<TotalIssuance<Runtime>>::get(), 732);
}
assert_eq!(Balances::free_balance(&1), 110);
assert_eq!(Balances::free_balance(&2), 110);
assert_eq!(<TotalIssuance<Runtime>>::get(), 220);
// Transfer funds from account 1 of such amount that after this transfer
// the balance of account 1 will be below the exsistential threshold.
// the balance of account 1 will be below the existential threshold.
// This should lead to the removal of all balance of this account.
assert_ok!(Balances::transfer(Some(1).into(), 2, 20));
// Verify free balance removal of account 1.
assert_eq!(Balances::free_balance(&1), 0);
assert_eq!(Balances::free_balance(&2), 130);
// Verify that TotalIssuance tracks balance removal when free balance is too low.
assert_eq!(<TotalIssuance<Runtime>>::get(), 642);
assert_eq!(<TotalIssuance<Runtime>>::get(), 130);
},
);
}
@@ -495,7 +490,7 @@ fn transfer_overflow_isnt_exploitable() {
fn check_vesting_status() {
with_externalities(
&mut ExtBuilder::default()
.existential_deposit(10)
.existential_deposit(256)
.monied(true)
.vesting(true)
.build(),
@@ -545,10 +540,10 @@ fn unvested_balance_should_not_transfer() {
|| {
assert_eq!(System::block_number(), 1);
let user1_free_balance = Balances::free_balance(&1);
assert_eq!(user1_free_balance, 256 * 10); // Account 1 has free balance
assert_eq!(Balances::vesting_balance(&1), user1_free_balance - 256); // Account 1 has only 256 units vested at block 1
assert_eq!(user1_free_balance, 100); // Account 1 has free balance
assert_eq!(Balances::vesting_balance(&1), 90); // Account 1 has only 10 units vested at block 1
assert_noop!(
Balances::transfer(Some(1).into(), 2, 256 * 2),
Balances::transfer(Some(1).into(), 2, 11),
"vesting balance too high to send value"
); // Account 1 cannot send more than vested amount
}
@@ -564,13 +559,11 @@ fn vested_balance_should_transfer() {
.vesting(true)
.build(),
|| {
System::set_block_number(5);
assert_eq!(System::block_number(), 5);
assert_eq!(System::block_number(), 1);
let user1_free_balance = Balances::free_balance(&1);
assert_eq!(user1_free_balance, 256 * 10); // Account 1 has free balance
assert_eq!(Balances::vesting_balance(&1), user1_free_balance - 256 * 5); // Account 1 has 256 * 5 units vested at block 5
assert_ok!(Balances::transfer(Some(1).into(), 2, 256 * 2)); // Account 1 can now send vested value
assert_eq!(user1_free_balance, 100); // Account 1 has free balance
assert_eq!(Balances::vesting_balance(&1), 90); // Account 1 has only 10 units vested at block 1
assert_ok!(Balances::transfer(Some(1).into(), 2, 10));
}
);
}
@@ -585,12 +578,12 @@ fn extra_balance_should_transfer() {
.build(),
|| {
assert_eq!(System::block_number(), 1);
assert_ok!(Balances::transfer(Some(3).into(), 1, 256 * 10));
assert_ok!(Balances::transfer(Some(3).into(), 1, 100));
let user1_free_balance = Balances::free_balance(&1);
assert_eq!(user1_free_balance, 256 * 20); // Account 1 has 2560 more free balance than normal
assert_eq!(user1_free_balance, 200); // Account 1 has 100 more free balance than normal
assert_eq!(Balances::vesting_balance(&1), 256 * 10 - 256); // Account 1 has 256 units vested at block 1
assert_ok!(Balances::transfer(Some(1).into(), 2, 256 * 5)); // Account 1 can send extra units gained
assert_eq!(Balances::vesting_balance(&1), 90); // Account 1 has 90 units vested at block 1
assert_ok!(Balances::transfer(Some(1).into(), 2, 105)); // Account 1 can send extra units gained
}
);
}