mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-21 14:25:41 +00:00
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:
committed by
Robert Habermeier
parent
f9e224e7b8
commit
dcd77a147c
+396
-190
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user