mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-21 13:15:41 +00:00
Phase 1 of repo reorg (#719)
* Remove unneeded script * Rename Substrate Demo -> Substrate * Rename demo -> node * Build wasm from last rename. * Merge ed25519 into substrate-primitives * Minor tweak * Rename substrate -> core * Move substrate-runtime-support to core/runtime/support * Rename/move substrate-runtime-version * Move codec up a level * Rename substrate-codec -> parity-codec * Move environmental up a level * Move pwasm-* up to top, ready for removal * Remove requirement of s-r-support from s-r-primitives * Move core/runtime/primitives into core/runtime-primitives * Remove s-r-support dep from s-r-version * Remove dep of s-r-support from bft * Remove dep of s-r-support from node/consensus * Sever all other core deps from s-r-support * Forgot the no_std directive * Rename non-SRML modules to sr-* to avoid match clashes * Move runtime/* to srml/* * Rename substrate-runtime-* -> srml-* * Move srml to top-level
This commit is contained in:
committed by
Arkadiy Paronyan
parent
8fe5aa4c81
commit
1e01162505
@@ -0,0 +1,111 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Address type that is union of index and id for an account.
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use std::fmt;
|
||||
use super::{Member, Decode, Encode, As, Input, Output};
|
||||
|
||||
/// A vetted and verified extrinsic from the external world.
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug, Hash))]
|
||||
pub enum Address<AccountId, AccountIndex> where
|
||||
AccountId: Member,
|
||||
AccountIndex: Member,
|
||||
{
|
||||
/// It's an account ID (pubkey).
|
||||
#[cfg_attr(feature = "std", serde(deserialize_with="AccountId::deserialize"))]
|
||||
Id(AccountId),
|
||||
/// It's an account index.
|
||||
#[cfg_attr(feature = "std", serde(deserialize_with="AccountIndex::deserialize"))]
|
||||
Index(AccountIndex),
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<AccountId, AccountIndex> fmt::Display for Address<AccountId, AccountIndex> where
|
||||
AccountId: Member,
|
||||
AccountIndex: Member,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<AccountId, AccountIndex> From<AccountId> for Address<AccountId, AccountIndex> where
|
||||
AccountId: Member,
|
||||
AccountIndex: Member,
|
||||
{
|
||||
fn from(a: AccountId) -> Self {
|
||||
Address::Id(a)
|
||||
}
|
||||
}
|
||||
|
||||
fn need_more_than<T: PartialOrd>(a: T, b: T) -> Option<T> {
|
||||
if a < b { Some(a) } else { None }
|
||||
}
|
||||
|
||||
impl<AccountId, AccountIndex> Decode for Address<AccountId, AccountIndex> where
|
||||
AccountId: Member + Decode,
|
||||
AccountIndex: Member + Decode + PartialOrd<AccountIndex> + Ord + As<u32> + As<u16> + As<u8> + Copy,
|
||||
{
|
||||
fn decode<I: Input>(input: &mut I) -> Option<Self> {
|
||||
Some(match input.read_byte()? {
|
||||
x @ 0x00...0xef => Address::Index(As::sa(x)),
|
||||
0xfc => Address::Index(As::sa(need_more_than(0xef, u16::decode(input)?)?)),
|
||||
0xfd => Address::Index(As::sa(need_more_than(0xffff, u32::decode(input)?)?)),
|
||||
0xfe => Address::Index(need_more_than(As::sa(0xffffffffu32), Decode::decode(input)?)?),
|
||||
0xff => Address::Id(Decode::decode(input)?),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<AccountId, AccountIndex> Encode for Address<AccountId, AccountIndex> where
|
||||
AccountId: Member + Encode,
|
||||
AccountIndex: Member + Encode + PartialOrd<AccountIndex> + Ord + As<u32> + As<u16> + As<u8> + Copy,
|
||||
{
|
||||
fn encode_to<T: Output>(&self, dest: &mut T) {
|
||||
match *self {
|
||||
Address::Id(ref i) => {
|
||||
dest.push_byte(255);
|
||||
dest.push(i);
|
||||
}
|
||||
Address::Index(i) if i > As::sa(0xffffffffu32) => {
|
||||
dest.push_byte(254);
|
||||
dest.push(&i);
|
||||
}
|
||||
Address::Index(i) if i > As::sa(0xffffu32) => {
|
||||
dest.push_byte(253);
|
||||
dest.push(&As::<u32>::as_(i));
|
||||
}
|
||||
Address::Index(i) if i >= As::sa(0xf0u32) => {
|
||||
dest.push_byte(252);
|
||||
dest.push(&As::<u16>::as_(i));
|
||||
}
|
||||
Address::Index(i) => dest.push_byte(As::<u8>::as_(i)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<AccountId, AccountIndex> Default for Address<AccountId, AccountIndex> where
|
||||
AccountId: Member + Default,
|
||||
AccountIndex: Member,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Address::Id(Default::default())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Build a balances genesis block.
|
||||
|
||||
#![cfg(feature = "std")]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use rstd::prelude::*;
|
||||
use codec::Encode;
|
||||
use runtime_support::{StorageValue, StorageMap};
|
||||
use primitives::traits::{Zero, As};
|
||||
use substrate_primitives::Blake2Hasher;
|
||||
use {runtime_io, primitives};
|
||||
use super::{Trait, ENUM_SET_SIZE, EnumSet, NextEnumSet, CreationFee, TransferFee,
|
||||
ReclaimRebate, ExistentialDeposit, TransactionByteFee, TransactionBaseFee, TotalIssuance,
|
||||
FreeBalance};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GenesisConfig<T: Trait> {
|
||||
pub balances: Vec<(T::AccountId, T::Balance)>,
|
||||
pub transaction_base_fee: T::Balance,
|
||||
pub transaction_byte_fee: T::Balance,
|
||||
pub transfer_fee: T::Balance,
|
||||
pub creation_fee: T::Balance,
|
||||
pub reclaim_rebate: T::Balance,
|
||||
pub existential_deposit: T::Balance,
|
||||
}
|
||||
|
||||
impl<T: Trait> Default for GenesisConfig<T> {
|
||||
fn default() -> Self {
|
||||
GenesisConfig {
|
||||
balances: vec![],
|
||||
transaction_base_fee: T::Balance::sa(0),
|
||||
transaction_byte_fee: T::Balance::sa(0),
|
||||
transfer_fee: T::Balance::sa(0),
|
||||
creation_fee: T::Balance::sa(0),
|
||||
existential_deposit: T::Balance::sa(0),
|
||||
reclaim_rebate: T::Balance::sa(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> primitives::BuildStorage for GenesisConfig<T> {
|
||||
fn build_storage(self) -> ::std::result::Result<HashMap<Vec<u8>, Vec<u8>>, String> {
|
||||
let total_issuance: T::Balance = self.balances.iter().fold(Zero::zero(), |acc, &(_, n)| acc + n);
|
||||
|
||||
let mut r: runtime_io::TestExternalities<Blake2Hasher> = map![
|
||||
Self::hash(<NextEnumSet<T>>::key()).to_vec() => T::AccountIndex::sa(self.balances.len() / ENUM_SET_SIZE).encode(),
|
||||
Self::hash(<TransactionBaseFee<T>>::key()).to_vec() => self.transaction_base_fee.encode(),
|
||||
Self::hash(<TransactionByteFee<T>>::key()).to_vec() => self.transaction_byte_fee.encode(),
|
||||
Self::hash(<TransferFee<T>>::key()).to_vec() => self.transfer_fee.encode(),
|
||||
Self::hash(<CreationFee<T>>::key()).to_vec() => self.creation_fee.encode(),
|
||||
Self::hash(<ExistentialDeposit<T>>::key()).to_vec() => self.existential_deposit.encode(),
|
||||
Self::hash(<ReclaimRebate<T>>::key()).to_vec() => self.reclaim_rebate.encode(),
|
||||
Self::hash(<TotalIssuance<T>>::key()).to_vec() => total_issuance.encode()
|
||||
];
|
||||
|
||||
let ids: Vec<_> = self.balances.iter().map(|x| x.0.clone()).collect();
|
||||
for i in 0..(ids.len() + ENUM_SET_SIZE - 1) / ENUM_SET_SIZE {
|
||||
r.insert(Self::hash(&<EnumSet<T>>::key_for(T::AccountIndex::sa(i))).to_vec(),
|
||||
ids[i * ENUM_SET_SIZE..ids.len().min((i + 1) * ENUM_SET_SIZE)].to_owned().encode());
|
||||
}
|
||||
for (who, value) in self.balances.into_iter() {
|
||||
r.insert(Self::hash(&<FreeBalance<T>>::key_for(who)).to_vec(), value.encode());
|
||||
}
|
||||
Ok(r.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,660 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Balances: Handles balances.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
extern crate serde;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
#[macro_use]
|
||||
extern crate srml_support as runtime_support;
|
||||
|
||||
#[cfg_attr(feature = "std", macro_use)]
|
||||
extern crate sr_std as rstd;
|
||||
|
||||
#[macro_use]
|
||||
extern crate parity_codec_derive;
|
||||
|
||||
extern crate parity_codec as codec;
|
||||
extern crate substrate_primitives;
|
||||
extern crate sr_io as runtime_io;
|
||||
extern crate sr_primitives as primitives;
|
||||
extern crate srml_system as system;
|
||||
|
||||
use rstd::prelude::*;
|
||||
use rstd::{cmp, result};
|
||||
use codec::{Encode, Decode, Codec, Input, Output};
|
||||
use runtime_support::{StorageValue, StorageMap, Parameter};
|
||||
use runtime_support::dispatch::Result;
|
||||
use primitives::traits::{Zero, One, SimpleArithmetic, OnFinalise, MakePayment,
|
||||
As, Lookup, Member, CheckedAdd, CheckedSub};
|
||||
use address::Address as RawAddress;
|
||||
use system::{ensure_signed, ensure_root};
|
||||
|
||||
mod mock;
|
||||
|
||||
pub mod address;
|
||||
mod tests;
|
||||
mod genesis_config;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub use genesis_config::GenesisConfig;
|
||||
|
||||
/// Number of account IDs stored per enum set.
|
||||
const ENUM_SET_SIZE: usize = 64;
|
||||
|
||||
/// The byte to identify intention to reclaim an existing account index.
|
||||
const RECLAIM_INDEX_MAGIC: usize = 0x69;
|
||||
|
||||
pub type Address<T> = RawAddress<<T as system::Trait>::AccountId, <T as Trait>::AccountIndex>;
|
||||
|
||||
/// The account with the given id was killed.
|
||||
pub trait OnFreeBalanceZero<AccountId> {
|
||||
/// The account was the given id was killed.
|
||||
fn on_free_balance_zero(who: &AccountId);
|
||||
}
|
||||
|
||||
impl<AccountId> OnFreeBalanceZero<AccountId> for () {
|
||||
fn on_free_balance_zero(_who: &AccountId) {}
|
||||
}
|
||||
impl<
|
||||
AccountId,
|
||||
X: OnFreeBalanceZero<AccountId>,
|
||||
Y: OnFreeBalanceZero<AccountId>,
|
||||
> OnFreeBalanceZero<AccountId> for (X, Y) {
|
||||
fn on_free_balance_zero(who: &AccountId) {
|
||||
X::on_free_balance_zero(who);
|
||||
Y::on_free_balance_zero(who);
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for a hook to get called when some balance has been minted, causing dilution.
|
||||
pub trait OnDilution<Balance> {
|
||||
/// Some `portion` of the total balance just "grew" by `minted`. `portion` is the pre-growth
|
||||
/// amount (it doesn't take account of the recent growth).
|
||||
fn on_dilution(minted: Balance, portion: Balance);
|
||||
}
|
||||
|
||||
impl<Balance> OnDilution<Balance> for () {
|
||||
fn on_dilution(_minted: Balance, _portion: Balance) {}
|
||||
}
|
||||
|
||||
/// Determinator for whether a given account is able to transfer balance.
|
||||
pub trait EnsureAccountLiquid<AccountId> {
|
||||
/// Returns `Ok` iff the account is able to transfer funds normally. `Err(...)`
|
||||
/// with the reason why not otherwise.
|
||||
fn ensure_account_liquid(who: &AccountId) -> Result;
|
||||
}
|
||||
|
||||
impl<AccountId> EnsureAccountLiquid<AccountId> for () {
|
||||
fn ensure_account_liquid(_who: &AccountId) -> Result { Ok(()) }
|
||||
}
|
||||
|
||||
pub trait Trait: system::Trait {
|
||||
/// The balance of an account.
|
||||
type Balance: Parameter + SimpleArithmetic + Codec + Default + Copy + As<Self::AccountIndex> + As<usize> + As<u64>;
|
||||
/// Type used for storing an account's index; implies the maximum number of accounts the system
|
||||
/// can hold.
|
||||
type AccountIndex: Parameter + Member + Codec + SimpleArithmetic + As<u8> + As<u16> + As<u32> + As<u64> + As<usize> + Copy;
|
||||
/// 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>;
|
||||
|
||||
/// A function that returns true iff a given account can transfer its funds to another account.
|
||||
type EnsureAccountLiquid: EnsureAccountLiquid<Self::AccountId>;
|
||||
|
||||
/// The overarching event type.
|
||||
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
fn transfer(origin, dest: RawAddress<T::AccountId, T::AccountIndex>, value: T::Balance) -> Result;
|
||||
fn set_balance(origin, who: RawAddress<T::AccountId, T::AccountIndex>, free: T::Balance, reserved: T::Balance) -> Result;
|
||||
}
|
||||
}
|
||||
|
||||
decl_event!(
|
||||
pub enum Event<T> with RawEvent<AccountId, AccountIndex, Balance>
|
||||
where <T as system::Trait>::AccountId, <T as Trait>::AccountIndex, <T as Trait>::Balance {
|
||||
/// A new account was created.
|
||||
NewAccount(AccountId, AccountIndex, NewAccountOutcome),
|
||||
/// An account was reaped.
|
||||
ReapedAccount(AccountId),
|
||||
/// Transfer succeeded (from, to, value, fees).
|
||||
Transfer(AccountId, AccountId, Balance, Balance),
|
||||
}
|
||||
);
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Balances {
|
||||
/// The total amount of stake on the system.
|
||||
pub TotalIssuance get(total_issuance): required T::Balance;
|
||||
/// The minimum amount allowed to keep an account open.
|
||||
pub ExistentialDeposit get(existential_deposit): required T::Balance;
|
||||
/// The amount credited to a destination's account whose index was reclaimed.
|
||||
pub ReclaimRebate get(reclaim_rebate): required T::Balance;
|
||||
/// The fee required to make a transfer.
|
||||
pub TransferFee get(transfer_fee): required T::Balance;
|
||||
/// The fee required to create an account. At least as big as ReclaimRebate.
|
||||
pub CreationFee get(creation_fee): required T::Balance;
|
||||
|
||||
/// The next free enumeration set.
|
||||
pub NextEnumSet get(next_enum_set): required T::AccountIndex;
|
||||
/// The enumeration sets.
|
||||
pub EnumSet get(enum_set): default map [ T::AccountIndex => Vec<T::AccountId> ];
|
||||
|
||||
/// The 'free' balance of a given account.
|
||||
///
|
||||
/// This is the only balance that matters in terms of most operations on tokens. It is
|
||||
/// alone used to determine the balance when in the contract execution environment. When this
|
||||
/// balance falls below the value of `ExistentialDeposit`, then the 'current account' is
|
||||
/// deleted: specifically `FreeBalance`. Furthermore, `OnFreeBalanceZero` callback
|
||||
/// is invoked, giving a chance to external modules to cleanup data associated with
|
||||
/// the deleted account.
|
||||
///
|
||||
/// `system::AccountNonce` is also deleted if `ReservedBalance` is also zero (it also gets
|
||||
/// collapsed to zero if it ever becomes less than `ExistentialDeposit`.
|
||||
pub FreeBalance get(free_balance): default map [ T::AccountId => T::Balance ];
|
||||
|
||||
/// The amount of the balance of a given account that is exterally reserved; this can still get
|
||||
/// slashed, but gets slashed last of all.
|
||||
///
|
||||
/// This balance is a 'reserve' balance that other subsystems use in order to set aside tokens
|
||||
/// that are still 'owned' by the account holder, but which are unspendable. (This is different
|
||||
/// and wholly unrelated to the `Bondage` system used in the staking module.)
|
||||
///
|
||||
/// When this balance falls below the value of `ExistentialDeposit`, then this 'reserve account'
|
||||
/// is deleted: specifically, `ReservedBalance`.
|
||||
///
|
||||
/// `system::AccountNonce` is also deleted if `FreeBalance` is also zero (it also gets
|
||||
/// collapsed to zero if it ever becomes less than `ExistentialDeposit`.
|
||||
pub ReservedBalance get(reserved_balance): default map [ T::AccountId => T::Balance ];
|
||||
|
||||
|
||||
// Payment stuff.
|
||||
|
||||
/// The fee to be paid for making a transaction; the base.
|
||||
pub TransactionBaseFee get(transaction_base_fee): required T::Balance;
|
||||
/// The fee to be paid for making a transaction; the per-byte portion.
|
||||
pub TransactionByteFee get(transaction_byte_fee): required T::Balance;
|
||||
}
|
||||
}
|
||||
|
||||
/// Whatever happened about the hint given when creating the new account.
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
#[derive(Encode, Decode, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum NewAccountOutcome {
|
||||
NoHint,
|
||||
GoodHint,
|
||||
BadHint,
|
||||
}
|
||||
|
||||
/// Outcome of a balance update.
|
||||
pub enum UpdateBalanceOutcome {
|
||||
/// Account balance was simply updated.
|
||||
Updated,
|
||||
/// The update has led to killing of the account.
|
||||
AccountKilled,
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
|
||||
/// Deposit one of this module's events.
|
||||
fn deposit_event(event: Event<T>) {
|
||||
<system::Module<T>>::deposit_event(<T as Trait>::Event::from(event).into());
|
||||
}
|
||||
|
||||
// PUBLIC IMMUTABLES
|
||||
|
||||
/// The combined balance of `who`.
|
||||
pub fn total_balance(who: &T::AccountId) -> T::Balance {
|
||||
Self::free_balance(who) + Self::reserved_balance(who)
|
||||
}
|
||||
|
||||
/// Some result as `slash(who, value)` (but without the side-effects) assuming there are no
|
||||
/// balance changes in the meantime and only the reserved balance is not taken into account.
|
||||
pub fn can_slash(who: &T::AccountId, value: T::Balance) -> bool {
|
||||
Self::free_balance(who) >= value
|
||||
}
|
||||
|
||||
/// Same result as `reserve(who, value)` (but without the side-effects) assuming there
|
||||
/// are no balance changes in the meantime.
|
||||
pub fn can_reserve(who: &T::AccountId, value: T::Balance) -> bool {
|
||||
if T::EnsureAccountLiquid::ensure_account_liquid(who).is_ok() {
|
||||
Self::free_balance(who) >= value
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Lookup an T::AccountIndex to get an Id, if there's one there.
|
||||
pub fn lookup_index(index: T::AccountIndex) -> Option<T::AccountId> {
|
||||
let enum_set_size = Self::enum_set_size();
|
||||
let set = Self::enum_set(index / enum_set_size);
|
||||
let i: usize = (index % enum_set_size).as_();
|
||||
set.get(i).map(|x| x.clone())
|
||||
}
|
||||
|
||||
/// `true` if the account `index` is ready for reclaim.
|
||||
pub fn can_reclaim(try_index: T::AccountIndex) -> bool {
|
||||
let enum_set_size = Self::enum_set_size();
|
||||
let try_set = Self::enum_set(try_index / enum_set_size);
|
||||
let i = (try_index % enum_set_size).as_();
|
||||
i < try_set.len() && Self::total_balance(&try_set[i]).is_zero()
|
||||
}
|
||||
|
||||
/// Lookup an address to get an Id, if there's one there.
|
||||
pub fn lookup_address(a: address::Address<T::AccountId, T::AccountIndex>) -> Option<T::AccountId> {
|
||||
match a {
|
||||
address::Address::Id(i) => Some(i),
|
||||
address::Address::Index(i) => Self::lookup_index(i),
|
||||
}
|
||||
}
|
||||
|
||||
// PUBLIC DISPATCH
|
||||
|
||||
/// Transfer some liquid free balance to another staker.
|
||||
pub fn transfer(origin: T::Origin, dest: Address<T>, value: T::Balance) -> Result {
|
||||
let transactor = ensure_signed(origin)?;
|
||||
|
||||
let dest = Self::lookup(dest)?;
|
||||
let from_balance = Self::free_balance(&transactor);
|
||||
let would_create = from_balance.is_zero();
|
||||
let fee = if would_create { Self::creation_fee() } else { Self::transfer_fee() };
|
||||
let liability = value + fee;
|
||||
|
||||
let new_from_balance = match from_balance.checked_sub(&liability) {
|
||||
Some(b) => b,
|
||||
None => return Err("balance too low to send value"),
|
||||
};
|
||||
if would_create && value < Self::existential_deposit() {
|
||||
return Err("value too low to create account");
|
||||
}
|
||||
T::EnsureAccountLiquid::ensure_account_liquid(&transactor)?;
|
||||
|
||||
let to_balance = Self::free_balance(&dest);
|
||||
// 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, dest, value, fee));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the balances of a given account.
|
||||
fn set_balance(origin: T::Origin, who: Address<T>, free: T::Balance, reserved: T::Balance) -> Result {
|
||||
ensure_root(origin)?;
|
||||
let who = Self::lookup(who)?;
|
||||
Self::set_free_balance(&who, free);
|
||||
Self::set_reserved_balance(&who, reserved);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// PUBLIC MUTABLES (DANGEROUS)
|
||||
|
||||
/// Set the free balance of an account to some new value.
|
||||
///
|
||||
/// Will enforce ExistentialDeposit law, anulling the account as needed.
|
||||
/// In that case it will return `AccountKilled`.
|
||||
pub fn set_reserved_balance(who: &T::AccountId, balance: T::Balance) -> UpdateBalanceOutcome {
|
||||
if balance < Self::existential_deposit() {
|
||||
<ReservedBalance<T>>::insert(who, balance);
|
||||
Self::on_reserved_too_low(who);
|
||||
UpdateBalanceOutcome::AccountKilled
|
||||
} else {
|
||||
<ReservedBalance<T>>::insert(who, balance);
|
||||
UpdateBalanceOutcome::Updated
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the free balance of an account to some new value. Will enforce ExistentialDeposit
|
||||
/// law anulling 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 {
|
||||
// Commented out for no - but consider it instructive.
|
||||
// assert!(!Self::total_balance(who).is_zero());
|
||||
if balance < Self::existential_deposit() {
|
||||
<FreeBalance<T>>::insert(who, balance);
|
||||
Self::on_free_too_low(who);
|
||||
UpdateBalanceOutcome::AccountKilled
|
||||
} else {
|
||||
<FreeBalance<T>>::insert(who, balance);
|
||||
UpdateBalanceOutcome::Updated
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>>::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.
|
||||
// TODO: enforce this for the other balance-altering functions.
|
||||
if balance < ed {
|
||||
Self::set_free_balance(who, balance);
|
||||
UpdateBalanceOutcome::AccountKilled
|
||||
} else {
|
||||
if !<FreeBalance<T>>::exists(who) {
|
||||
let outcome = Self::new_account(&who, balance);
|
||||
let credit = match outcome {
|
||||
NewAccountOutcome::GoodHint => balance + <Module<T>>::reclaim_rebate(),
|
||||
_ => balance,
|
||||
};
|
||||
Self::set_free_balance(who, credit);
|
||||
Self::increase_total_stake_by(credit - balance);
|
||||
} else {
|
||||
Self::set_free_balance(who, balance);
|
||||
}
|
||||
|
||||
UpdateBalanceOutcome::Updated
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds up to `value` to the free balance of `who`. If `who` doesn't exist, it is created.
|
||||
///
|
||||
/// This is a sensitive function since it circumvents any fees associated with account
|
||||
/// setup. Ensure it is only called by trusted code.
|
||||
///
|
||||
/// NOTE: This assumes that the total stake remains unchanged after this operation. If
|
||||
/// you mean to actually mint value into existence, then use `reward` instead.
|
||||
pub fn increase_free_balance_creating(who: &T::AccountId, value: T::Balance) -> UpdateBalanceOutcome {
|
||||
Self::set_free_balance_creating(who, Self::free_balance(who) + value)
|
||||
}
|
||||
|
||||
/// Deducts up to `value` from the combined balance of `who`, preferring to deduct from the
|
||||
/// free balance. This function cannot fail.
|
||||
///
|
||||
/// As much funds up to `value` will be deducted as possible. If this is less than `value`,
|
||||
/// then `Some(remaining)` will be returned. Full completion is given by `None`.
|
||||
pub fn slash(who: &T::AccountId, value: T::Balance) -> Option<T::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)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds up to `value` to the free balance of `who`.
|
||||
///
|
||||
/// If `who` doesn't exist, nothing is done and an Err returned.
|
||||
pub fn reward(who: &T::AccountId, value: T::Balance) -> Result {
|
||||
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(())
|
||||
}
|
||||
|
||||
/// Moves `value` from balance to reserved balance.
|
||||
///
|
||||
/// If the free balance is lower than `value`, then no funds will be moved and an `Err` will
|
||||
/// be returned to notify of this. This is different behaviour to `unreserve`.
|
||||
pub fn reserve(who: &T::AccountId, value: T::Balance) -> Result {
|
||||
let b = Self::free_balance(who);
|
||||
if b < value {
|
||||
return Err("not enough free funds")
|
||||
}
|
||||
T::EnsureAccountLiquid::ensure_account_liquid(who)?;
|
||||
Self::set_reserved_balance(who, Self::reserved_balance(who) + value);
|
||||
Self::set_free_balance(who, b - value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Moves up to `value` from reserved balance to balance. This function cannot fail.
|
||||
///
|
||||
/// As much funds up to `value` will be deducted as possible. If this is less than `value`,
|
||||
/// then `Some(remaining)` will be returned. Full completion is given by `None`.
|
||||
/// NOTE: This is different to `reserve`.
|
||||
pub fn unreserve(who: &T::AccountId, value: T::Balance) -> Option<T::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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Deducts up to `value` from reserved balance of `who`. This function cannot fail.
|
||||
///
|
||||
/// As much funds up to `value` will be deducted as possible. If this is less than `value`,
|
||||
/// then `Some(remaining)` will be returned. Full completion is given by `None`.
|
||||
pub fn slash_reserved(who: &T::AccountId, value: T::Balance) -> Option<T::Balance> {
|
||||
let b = Self::reserved_balance(who);
|
||||
let slash = cmp::min(b, value);
|
||||
Self::set_reserved_balance(who, b - slash);
|
||||
Self::decrease_total_stake_by(slash);
|
||||
if value == slash {
|
||||
None
|
||||
} else {
|
||||
Some(value - slash)
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves up to `value` from reserved balance of account `slashed` to free balance of account
|
||||
/// `beneficiary`. `beneficiary` must exist for this to succeed. If it does not, `Err` will be
|
||||
/// returned.
|
||||
///
|
||||
/// As much funds up to `value` will be moved as possible. If this is less than `value`, then
|
||||
/// `Ok(Some(remaining))` will be returned. Full completion is given by `Ok(None)`.
|
||||
pub fn repatriate_reserved(
|
||||
slashed: &T::AccountId,
|
||||
beneficiary: &T::AccountId,
|
||||
value: T::Balance
|
||||
) -> result::Result<Option<T::Balance>, &'static str> {
|
||||
if Self::total_balance(beneficiary).is_zero() {
|
||||
return Err("beneficiary account must pre-exist");
|
||||
}
|
||||
let b = Self::reserved_balance(slashed);
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
fn enum_set_size() -> T::AccountIndex {
|
||||
T::AccountIndex::sa(ENUM_SET_SIZE)
|
||||
}
|
||||
|
||||
/// Register a new account (with existential balance).
|
||||
fn new_account(who: &T::AccountId, balance: T::Balance) -> NewAccountOutcome {
|
||||
let enum_set_size = Self::enum_set_size();
|
||||
let next_set_index = Self::next_enum_set();
|
||||
let reclaim_index_magic = T::AccountIndex::sa(RECLAIM_INDEX_MAGIC);
|
||||
let reclaim_index_modulus = T::AccountIndex::sa(256usize);
|
||||
let quantization = T::AccountIndex::sa(256usize);
|
||||
|
||||
// A little easter-egg for reclaiming dead indexes..
|
||||
let ret = {
|
||||
// we quantise the number of accounts so it stays constant over a reasonable
|
||||
// period of time.
|
||||
let quantized_account_count: T::AccountIndex = (next_set_index * enum_set_size / quantization + One::one()) * quantization;
|
||||
// then modify the starting balance to be modulo this to allow it to potentially
|
||||
// identify an account index for reuse.
|
||||
let maybe_try_index = balance % <T::Balance as As<T::AccountIndex>>::sa(quantized_account_count * reclaim_index_modulus);
|
||||
let maybe_try_index = As::<T::AccountIndex>::as_(maybe_try_index);
|
||||
|
||||
// this identifier must end with magic byte 0x69 to trigger this check (a minor
|
||||
// optimisation to ensure we don't check most unintended account creations).
|
||||
if maybe_try_index % reclaim_index_modulus == reclaim_index_magic {
|
||||
// reuse is probably intended. first, remove magic byte.
|
||||
let try_index = maybe_try_index / reclaim_index_modulus;
|
||||
|
||||
// then check to see if this balance identifies a dead account index.
|
||||
let set_index = try_index / enum_set_size;
|
||||
let mut try_set = Self::enum_set(set_index);
|
||||
let item_index = (try_index % enum_set_size).as_();
|
||||
if item_index < try_set.len() {
|
||||
if Self::total_balance(&try_set[item_index]).is_zero() {
|
||||
// yup - this index refers to a dead account. can be reused.
|
||||
try_set[item_index] = who.clone();
|
||||
<EnumSet<T>>::insert(set_index, try_set);
|
||||
|
||||
Self::deposit_event(RawEvent::NewAccount(who.clone(), try_index, NewAccountOutcome::GoodHint));
|
||||
|
||||
return NewAccountOutcome::GoodHint
|
||||
}
|
||||
}
|
||||
NewAccountOutcome::BadHint
|
||||
} else {
|
||||
NewAccountOutcome::NoHint
|
||||
}
|
||||
};
|
||||
|
||||
// insert normally as a back up
|
||||
let mut set_index = next_set_index;
|
||||
// defensive only: this loop should never iterate since we keep NextEnumSet up to date later.
|
||||
let mut set = loop {
|
||||
let set = Self::enum_set(set_index);
|
||||
if set.len() < ENUM_SET_SIZE {
|
||||
break set;
|
||||
}
|
||||
set_index += One::one();
|
||||
};
|
||||
|
||||
let index = T::AccountIndex::sa(set_index.as_() * ENUM_SET_SIZE + set.len());
|
||||
|
||||
// update set.
|
||||
set.push(who.clone());
|
||||
|
||||
// keep NextEnumSet up to date
|
||||
if set.len() == ENUM_SET_SIZE {
|
||||
<NextEnumSet<T>>::put(set_index + One::one());
|
||||
}
|
||||
|
||||
// write set.
|
||||
<EnumSet<T>>::insert(set_index, set);
|
||||
|
||||
Self::deposit_event(RawEvent::NewAccount(who.clone(), index, ret));
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
fn reap_account(who: &T::AccountId) {
|
||||
<system::AccountNonce<T>>::remove(who);
|
||||
Self::deposit_event(RawEvent::ReapedAccount(who.clone()));
|
||||
}
|
||||
|
||||
/// Kill an account's free portion.
|
||||
fn on_free_too_low(who: &T::AccountId) {
|
||||
Self::decrease_total_stake_by(Self::free_balance(who));
|
||||
<FreeBalance<T>>::remove(who);
|
||||
|
||||
T::OnFreeBalanceZero::on_free_balance_zero(who);
|
||||
|
||||
if Self::reserved_balance(who).is_zero() {
|
||||
Self::reap_account(who);
|
||||
}
|
||||
}
|
||||
|
||||
/// Kill an account's reserved portion.
|
||||
fn on_reserved_too_low(who: &T::AccountId) {
|
||||
Self::decrease_total_stake_by(Self::reserved_balance(who));
|
||||
<ReservedBalance<T>>::remove(who);
|
||||
|
||||
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>>::total_issuance().checked_add(&value) {
|
||||
<TotalIssuance<T>>::put(v);
|
||||
}
|
||||
}
|
||||
/// Decrease TotalIssuance by Value.
|
||||
pub fn decrease_total_stake_by(value: T::Balance) {
|
||||
if let Some(v) = <Module<T>>::total_issuance().checked_sub(&value) {
|
||||
<TotalIssuance<T>>::put(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> OnFinalise<T::BlockNumber> for Module<T> {
|
||||
fn on_finalise(_n: T::BlockNumber) {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> Lookup for Module<T> {
|
||||
type Source = address::Address<T::AccountId, T::AccountIndex>;
|
||||
type Target = T::AccountId;
|
||||
fn lookup(a: Self::Source) -> result::Result<Self::Target, &'static str> {
|
||||
match a {
|
||||
address::Address::Id(i) => Ok(i),
|
||||
address::Address::Index(i) => <Module<T>>::lookup_index(i).ok_or("invalid account index"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> MakePayment<T::AccountId> for Module<T> {
|
||||
fn make_payment(transactor: &T::AccountId, encoded_len: usize) -> Result {
|
||||
let b = Self::free_balance(transactor);
|
||||
let transaction_fee = Self::transaction_base_fee() + Self::transaction_byte_fee() * <T::Balance as As<u64>>::sa(encoded_len as u64);
|
||||
if b < transaction_fee + Self::existential_deposit() {
|
||||
return Err("not enough funds for transaction fee");
|
||||
}
|
||||
Self::set_free_balance(transactor, b - transaction_fee);
|
||||
Self::decrease_total_stake_by(transaction_fee);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Test utilities
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use primitives::BuildStorage;
|
||||
use primitives::testing::{Digest, Header};
|
||||
use substrate_primitives::{H256, Blake2Hasher};
|
||||
use runtime_io;
|
||||
use {GenesisConfig, Module, Trait, system};
|
||||
|
||||
impl_outer_origin!{
|
||||
pub enum Origin for Runtime {}
|
||||
}
|
||||
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
pub struct Runtime;
|
||||
impl system::Trait for Runtime {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = ::primitives::traits::BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
}
|
||||
impl Trait for Runtime {
|
||||
type Balance = u64;
|
||||
type AccountIndex = u64;
|
||||
type OnFreeBalanceZero = ();
|
||||
type EnsureAccountLiquid = ();
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
pub fn new_test_ext(ext_deposit: u64, monied: bool) -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
let mut t = system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
|
||||
let balance_factor = if ext_deposit > 0 {
|
||||
256
|
||||
} else {
|
||||
1
|
||||
};
|
||||
t.extend(GenesisConfig::<Runtime>{
|
||||
balances: if monied {
|
||||
vec![(1, 10 * balance_factor), (2, 20 * balance_factor), (3, 30 * balance_factor), (4, 40 * balance_factor)]
|
||||
} else {
|
||||
vec![(10, balance_factor), (20, balance_factor)]
|
||||
},
|
||||
transaction_base_fee: 0,
|
||||
transaction_byte_fee: 0,
|
||||
existential_deposit: ext_deposit,
|
||||
transfer_fee: 0,
|
||||
creation_fee: 0,
|
||||
reclaim_rebate: 0,
|
||||
}.build_storage().unwrap());
|
||||
t.into()
|
||||
}
|
||||
|
||||
pub type System = system::Module<Runtime>;
|
||||
pub type Balances = Module<Runtime>;
|
||||
@@ -0,0 +1,324 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tests for the module.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use runtime_io::with_externalities;
|
||||
use mock::{Balances, System, Runtime, new_test_ext};
|
||||
|
||||
#[test]
|
||||
fn reward_should_work() {
|
||||
with_externalities(&mut new_test_ext(0, true), || {
|
||||
assert_eq!(Balances::total_balance(&1), 10);
|
||||
assert_ok!(Balances::reward(&1, 10));
|
||||
assert_eq!(Balances::total_balance(&1), 20);
|
||||
assert_eq!(<TotalIssuance<Runtime>>::get(), 110);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indexing_lookup_should_work() {
|
||||
with_externalities(&mut new_test_ext(10, true), || {
|
||||
assert_eq!(Balances::lookup_index(0), Some(1));
|
||||
assert_eq!(Balances::lookup_index(1), Some(2));
|
||||
assert_eq!(Balances::lookup_index(2), Some(3));
|
||||
assert_eq!(Balances::lookup_index(3), Some(4));
|
||||
assert_eq!(Balances::lookup_index(4), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_indexing_on_new_accounts_should_work() {
|
||||
with_externalities(&mut new_test_ext(10, true), || {
|
||||
assert_eq!(Balances::lookup_index(4), None);
|
||||
assert_ok!(Balances::transfer(Some(1).into(), 5.into(), 10));
|
||||
assert_eq!(Balances::lookup_index(4), Some(5));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dust_account_removal_should_work() {
|
||||
with_externalities(&mut new_test_ext(256 * 10, true), || {
|
||||
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.into(), 256 * 10 + 1)); // index 1 (account 2) becomes zombie
|
||||
assert_eq!(Balances::total_balance(&2), 0);
|
||||
assert_eq!(Balances::total_balance(&5), 256 * 10 + 1);
|
||||
assert_eq!(System::account_nonce(&2), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reclaim_indexing_on_new_accounts_should_work() {
|
||||
with_externalities(&mut new_test_ext(256 * 1, true), || {
|
||||
assert_eq!(Balances::lookup_index(1), Some(2));
|
||||
assert_eq!(Balances::lookup_index(4), None);
|
||||
assert_eq!(Balances::total_balance(&2), 256 * 20);
|
||||
|
||||
assert_ok!(Balances::transfer(Some(2).into(), 5.into(), 256 * 20)); // account 2 becomes zombie freeing index 1 for reclaim)
|
||||
assert_eq!(Balances::total_balance(&2), 0);
|
||||
|
||||
assert_ok!(Balances::transfer(Some(5).into(), 6.into(), 256 * 1 + 0x69)); // account 6 takes index 1.
|
||||
assert_eq!(Balances::total_balance(&6), 256 * 1 + 0x69);
|
||||
assert_eq!(Balances::lookup_index(1), Some(6));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserved_balance_should_prevent_reclaim_count() {
|
||||
with_externalities(&mut new_test_ext(256 * 1, true), || {
|
||||
System::inc_account_nonce(&2);
|
||||
assert_eq!(Balances::lookup_index(1), Some(2));
|
||||
assert_eq!(Balances::lookup_index(4), None);
|
||||
assert_eq!(Balances::total_balance(&2), 256 * 20);
|
||||
|
||||
assert_ok!(Balances::reserve(&2, 256 * 19 + 1)); // account 2 becomes mostly reserved
|
||||
assert_eq!(Balances::free_balance(&2), 0); // "free" account deleted."
|
||||
assert_eq!(Balances::total_balance(&2), 256 * 19 + 1); // reserve still exists.
|
||||
assert_eq!(System::account_nonce(&2), 1);
|
||||
|
||||
assert_ok!(Balances::transfer(Some(4).into(), 5.into(), 256 * 1 + 0x69)); // account 4 tries to take index 1 for account 5.
|
||||
assert_eq!(Balances::total_balance(&5), 256 * 1 + 0x69);
|
||||
assert_eq!(Balances::lookup_index(1), Some(2)); // but fails.
|
||||
assert_eq!(System::account_nonce(&2), 1);
|
||||
|
||||
assert_eq!(Balances::slash(&2, 256 * 18 + 2), None); // account 2 gets slashed
|
||||
assert_eq!(Balances::total_balance(&2), 0); // "free" account deleted."
|
||||
assert_eq!(System::account_nonce(&2), 0);
|
||||
|
||||
assert_ok!(Balances::transfer(Some(4).into(), 6.into(), 256 * 1 + 0x69)); // account 4 tries to take index 1 again for account 6.
|
||||
assert_eq!(Balances::total_balance(&6), 256 * 1 + 0x69);
|
||||
assert_eq!(Balances::lookup_index(1), Some(6)); // and succeeds.
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn balance_works() {
|
||||
with_externalities(&mut new_test_ext(0, false), || {
|
||||
Balances::set_free_balance(&1, 42);
|
||||
assert_eq!(Balances::free_balance(&1), 42);
|
||||
assert_eq!(Balances::reserved_balance(&1), 0);
|
||||
assert_eq!(Balances::total_balance(&1), 42);
|
||||
assert_eq!(Balances::free_balance(&2), 0);
|
||||
assert_eq!(Balances::reserved_balance(&2), 0);
|
||||
assert_eq!(Balances::total_balance(&2), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn balance_transfer_works() {
|
||||
with_externalities(&mut new_test_ext(0, false), || {
|
||||
Balances::set_free_balance(&1, 111);
|
||||
Balances::increase_total_stake_by(111);
|
||||
assert_ok!(Balances::transfer(Some(1).into(), 2.into(), 69));
|
||||
assert_eq!(Balances::total_balance(&1), 42);
|
||||
assert_eq!(Balances::total_balance(&2), 69);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserving_balance_should_work() {
|
||||
with_externalities(&mut new_test_ext(0, false), || {
|
||||
Balances::set_free_balance(&1, 111);
|
||||
|
||||
assert_eq!(Balances::total_balance(&1), 111);
|
||||
assert_eq!(Balances::free_balance(&1), 111);
|
||||
assert_eq!(Balances::reserved_balance(&1), 0);
|
||||
|
||||
assert_ok!(Balances::reserve(&1, 69));
|
||||
|
||||
assert_eq!(Balances::total_balance(&1), 111);
|
||||
assert_eq!(Balances::free_balance(&1), 42);
|
||||
assert_eq!(Balances::reserved_balance(&1), 69);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn balance_transfer_when_reserved_should_not_work() {
|
||||
with_externalities(&mut new_test_ext(0, false), || {
|
||||
Balances::set_free_balance(&1, 111);
|
||||
assert_ok!(Balances::reserve(&1, 69));
|
||||
assert_noop!(Balances::transfer(Some(1).into(), 2.into(), 69), "balance too low to send value");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deducting_balance_should_work() {
|
||||
with_externalities(&mut new_test_ext(0, false), || {
|
||||
Balances::set_free_balance(&1, 111);
|
||||
assert_ok!(Balances::reserve(&1, 69));
|
||||
assert_eq!(Balances::free_balance(&1), 42);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refunding_balance_should_work() {
|
||||
with_externalities(&mut new_test_ext(0, false), || {
|
||||
Balances::set_free_balance(&1, 42);
|
||||
Balances::set_reserved_balance(&1, 69);
|
||||
Balances::unreserve(&1, 69);
|
||||
assert_eq!(Balances::free_balance(&1), 111);
|
||||
assert_eq!(Balances::reserved_balance(&1), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slashing_balance_should_work() {
|
||||
with_externalities(&mut new_test_ext(0, false), || {
|
||||
Balances::set_free_balance(&1, 111);
|
||||
Balances::increase_total_stake_by(111);
|
||||
assert_ok!(Balances::reserve(&1, 69));
|
||||
assert!(Balances::slash(&1, 69).is_none());
|
||||
assert_eq!(Balances::free_balance(&1), 0);
|
||||
assert_eq!(Balances::reserved_balance(&1), 42);
|
||||
assert_eq!(<TotalIssuance<Runtime>>::get(), 44);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slashing_incomplete_balance_should_work() {
|
||||
with_externalities(&mut new_test_ext(0, false), || {
|
||||
Balances::set_free_balance(&1, 42);
|
||||
Balances::increase_total_stake_by(42);
|
||||
assert_ok!(Balances::reserve(&1, 21));
|
||||
assert!(Balances::slash(&1, 69).is_some());
|
||||
assert_eq!(Balances::free_balance(&1), 0);
|
||||
assert_eq!(Balances::reserved_balance(&1), 0);
|
||||
assert_eq!(<TotalIssuance<Runtime>>::get(), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unreserving_balance_should_work() {
|
||||
with_externalities(&mut new_test_ext(0, false), || {
|
||||
Balances::set_free_balance(&1, 111);
|
||||
assert_ok!(Balances::reserve(&1, 111));
|
||||
Balances::unreserve(&1, 42);
|
||||
assert_eq!(Balances::reserved_balance(&1), 69);
|
||||
assert_eq!(Balances::free_balance(&1), 42);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slashing_reserved_balance_should_work() {
|
||||
with_externalities(&mut new_test_ext(0, false), || {
|
||||
Balances::set_free_balance(&1, 111);
|
||||
Balances::increase_total_stake_by(111);
|
||||
assert_ok!(Balances::reserve(&1, 111));
|
||||
assert!(Balances::slash_reserved(&1, 42).is_none());
|
||||
assert_eq!(Balances::reserved_balance(&1), 69);
|
||||
assert_eq!(Balances::free_balance(&1), 0);
|
||||
assert_eq!(<TotalIssuance<Runtime>>::get(), 71);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slashing_incomplete_reserved_balance_should_work() {
|
||||
with_externalities(&mut new_test_ext(0, false), || {
|
||||
Balances::set_free_balance(&1, 111);
|
||||
Balances::increase_total_stake_by(111);
|
||||
assert_ok!(Balances::reserve(&1, 42));
|
||||
assert!(Balances::slash_reserved(&1, 69).is_some());
|
||||
assert_eq!(Balances::free_balance(&1), 69);
|
||||
assert_eq!(Balances::reserved_balance(&1), 0);
|
||||
assert_eq!(<TotalIssuance<Runtime>>::get(), 71);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transferring_reserved_balance_should_work() {
|
||||
with_externalities(&mut new_test_ext(0, false), || {
|
||||
Balances::set_free_balance(&1, 110);
|
||||
Balances::set_free_balance(&2, 1);
|
||||
assert_ok!(Balances::reserve(&1, 110));
|
||||
assert_ok!(Balances::repatriate_reserved(&1, &2, 41), None);
|
||||
assert_eq!(Balances::reserved_balance(&1), 69);
|
||||
assert_eq!(Balances::free_balance(&1), 0);
|
||||
assert_eq!(Balances::reserved_balance(&2), 0);
|
||||
assert_eq!(Balances::free_balance(&2), 42);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transferring_reserved_balance_to_nonexistent_should_fail() {
|
||||
with_externalities(&mut new_test_ext(0, false), || {
|
||||
Balances::set_free_balance(&1, 111);
|
||||
assert_ok!(Balances::reserve(&1, 111));
|
||||
assert_noop!(Balances::repatriate_reserved(&1, &2, 42), "beneficiary account must pre-exist");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transferring_incomplete_reserved_balance_should_work() {
|
||||
with_externalities(&mut new_test_ext(0, false), || {
|
||||
Balances::set_free_balance(&1, 110);
|
||||
Balances::set_free_balance(&2, 1);
|
||||
assert_ok!(Balances::reserve(&1, 41));
|
||||
assert!(Balances::repatriate_reserved(&1, &2, 69).unwrap().is_some());
|
||||
assert_eq!(Balances::reserved_balance(&1), 0);
|
||||
assert_eq!(Balances::free_balance(&1), 69);
|
||||
assert_eq!(Balances::reserved_balance(&2), 0);
|
||||
assert_eq!(Balances::free_balance(&2), 42);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transferring_too_high_value_should_not_panic() {
|
||||
with_externalities(&mut new_test_ext(0, false), || {
|
||||
<FreeBalance<Runtime>>::insert(1, u64::max_value());
|
||||
<FreeBalance<Runtime>>::insert(2, 1);
|
||||
|
||||
assert_err!(
|
||||
Balances::transfer(Some(1).into(), 2.into(), u64::max_value()),
|
||||
"destination balance too high to receive value"
|
||||
);
|
||||
|
||||
assert_eq!(Balances::free_balance(&1), u64::max_value());
|
||||
assert_eq!(Balances::free_balance(&2), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_removal_on_free_too_low() {
|
||||
with_externalities(&mut new_test_ext(100, false), || {
|
||||
// Setup two accounts with free balance above the exsistential threshold.
|
||||
{
|
||||
Balances::set_free_balance(&1, 110);
|
||||
Balances::increase_total_stake_by(110);
|
||||
|
||||
Balances::set_free_balance(&2, 110);
|
||||
Balances::increase_total_stake_by(110);
|
||||
|
||||
assert_eq!(<TotalIssuance<Runtime>>::get(), 732);
|
||||
}
|
||||
|
||||
// Transfer funds from account 1 of such amount that after this transfer
|
||||
// the balance of account 1 will be below the exsistential threshold.
|
||||
// This should lead to the removal of all balance of this account.
|
||||
assert_ok!(Balances::transfer(Some(1).into(), 2.into(), 20));
|
||||
|
||||
// Verify free balance removal of account 1.
|
||||
assert_eq!(Balances::free_balance(&1), 0);
|
||||
|
||||
// Verify that TotalIssuance tracks balance removal when free balance is too low.
|
||||
assert_eq!(<TotalIssuance<Runtime>>::get(), 642);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user