// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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 Demo 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 Demo. If not, see .
//! Staking manager: Handles balances and periodically determines the best set of validators.
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "std")]
extern crate serde;
#[cfg(feature = "std")]
#[macro_use]
extern crate serde_derive;
#[cfg(test)]
extern crate wabt;
#[macro_use]
extern crate substrate_runtime_support as runtime_support;
#[cfg_attr(feature = "std", macro_use)]
extern crate substrate_runtime_std as rstd;
extern crate substrate_codec as codec;
extern crate substrate_primitives;
extern crate substrate_runtime_contract as contract;
extern crate substrate_runtime_io as runtime_io;
extern crate substrate_runtime_primitives as primitives;
extern crate substrate_runtime_consensus as consensus;
extern crate substrate_runtime_sandbox as sandbox;
extern crate substrate_runtime_session as session;
extern crate substrate_runtime_system as system;
#[cfg(test)] use std::fmt::Debug;
use rstd::prelude::*;
use rstd::{cmp, result};
use rstd::cell::RefCell;
use rstd::collections::btree_map::{BTreeMap, Entry};
use codec::{Input, Slicable};
use runtime_support::{StorageValue, StorageMap, Parameter};
use runtime_support::dispatch::Result;
use primitives::traits::{Zero, One, Bounded, RefInto, SimpleArithmetic, Executable, MakePayment,
As, AuxLookup, Hashing as HashingT, Member};
use address::Address as RawAddress;
pub mod address;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
/// 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 = RawAddress<::AccountId, ::AccountIndex>;
#[cfg(test)]
#[derive(Debug, PartialEq, Clone)]
pub enum LockStatus {
Liquid,
LockedUntil(BlockNumber),
Staked,
}
#[cfg(not(test))]
#[derive(PartialEq, Clone)]
pub enum LockStatus {
Liquid,
LockedUntil(BlockNumber),
Staked,
}
pub trait ContractAddressFor {
fn contract_address_for(code: &[u8], origin: &AccountId) -> AccountId;
}
impl ContractAddressFor for Hashing where
Hashing: HashingT,
AccountId: Sized + Slicable + From,
Hashing::Output: Slicable
{
fn contract_address_for(code: &[u8], origin: &AccountId) -> AccountId {
let mut dest_pre = Hashing::hash(code).encode();
origin.using_encoded(|s| dest_pre.extend(s));
AccountId::from(Hashing::hash(&dest_pre))
}
}
pub trait Trait: system::Trait + session::Trait {
/// The balance of an account.
type Balance: Parameter + SimpleArithmetic + Slicable + Default + Copy + As + As;
/// Function type to get the contract address given the creator.
type DetermineContractAddress: ContractAddressFor;
/// Type used for storing an account's index; implies the maximum number of accounts the system
/// can hold.
type AccountIndex: Parameter + Member + Slicable + SimpleArithmetic + As + As + As + As + Copy;
}
decl_module! {
pub struct Module;
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub enum Call where aux: T::PublicAux {
fn transfer(aux, dest: RawAddress, value: T::Balance) -> Result = 0;
fn stake(aux) -> Result = 1;
fn unstake(aux) -> Result = 2;
}
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub enum PrivCall {
fn set_sessions_per_era(new: T::BlockNumber) -> Result = 0;
fn set_bonding_duration(new: T::BlockNumber) -> Result = 1;
fn set_validator_count(new: u32) -> Result = 2;
fn force_new_era() -> Result = 3;
}
}
decl_storage! {
trait Store for Module;
// The length of the bonding duration in eras.
pub BondingDuration get(bonding_duration): b"sta:loc" => required T::BlockNumber;
// The length of a staking era in sessions.
pub ValidatorCount get(validator_count): b"sta:vac" => required u32;
// The length of a staking era in sessions.
pub SessionsPerEra get(sessions_per_era): b"sta:spe" => required T::BlockNumber;
// The total amount of stake on the system.
pub TotalStake get(total_stake): b"sta:tot" => required T::Balance;
// The fee to be paid for making a transaction; the base.
pub TransactionBaseFee get(transaction_base_fee): b"sta:basefee" => required T::Balance;
// The fee to be paid for making a transaction; the per-byte portion.
pub TransactionByteFee get(transaction_byte_fee): b"sta:bytefee" => required T::Balance;
// The minimum amount allowed to keep an account open.
pub ExistentialDeposit get(existential_deposit): b"sta:existential_deposit" => required T::Balance;
// The amount credited to a destination's account whose index was reclaimed.
pub ReclaimRebate get(reclaim_rebate): b"sta:reclaim_rebate" => required T::Balance;
// The fee required to make a transfer.
pub TransferFee get(transfer_fee): b"sta:transfer_fee" => required T::Balance;
// The fee required to create an account. At least as big as ReclaimRebate.
pub CreationFee get(creation_fee): b"sta:creation_fee" => required T::Balance;
// The fee required to create a contract. At least as big as ReclaimRebate.
pub ContractFee get(contract_fee): b"sta:contract_fee" => required T::Balance;
// The current era index.
pub CurrentEra get(current_era): b"sta:era" => required T::BlockNumber;
// All the accounts with a desire to stake.
pub Intentions: b"sta:wil:" => default Vec;
// The next value of sessions per era.
pub NextSessionsPerEra get(next_sessions_per_era): b"sta:nse" => T::BlockNumber;
// The block number at which the era length last changed.
pub LastEraLengthChange get(last_era_length_change): b"sta:lec" => default T::BlockNumber;
// The next free enumeration set.
pub NextEnumSet get(next_enum_set): b"sta:next_enum" => required T::AccountIndex;
// The enumeration sets.
pub EnumSet get(enum_set): b"sta:enum_set" => default map [ T::AccountIndex => Vec ];
// 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, `Bondage`, `StorageOf`, `CodeOf` and `FreeBalance`.
//
// `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): b"sta:bal:" => 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 for staking.
//
// 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): b"sta:lbo:" => default map [ T::AccountId => T::Balance ];
// The block at which the `who`'s funds become entirely liquid.
pub Bondage get(bondage): b"sta:bon:" => default map [ T::AccountId => T::BlockNumber ];
// The code associated with an account.
pub CodeOf: b"sta:cod:" => default map [ T::AccountId => Vec ]; // TODO Vec values should be optimised to not do a length prefix.
// The storage items associated with an account/key.
// TODO: keys should also be able to take AsRef to ensure Vecs can be passed as &[u8]
// TODO: This will need to be stored as a double-map, with `T::AccountId` using the usual XX hash
// function, and then the output of this concatenated onto a separate blake2 hash of the `Vec`
// key. We will then need a `remove_prefix` in addition to `set_storage` which removes all
// storage items with a particular prefix otherwise we'll suffer leakage with the removal
// of smart contracts.
// pub StorageOf: b"sta:sto:" => map [ T::AccountId => map(blake2) Vec => Vec ];
pub StorageOf: b"sta:sto:" => map [ (T::AccountId, Vec) => Vec ];
}
enum NewAccountOutcome {
NoHint,
GoodHint,
BadHint,
}
impl Module {
// PUBLIC IMMUTABLES
/// The length of a staking era in blocks.
pub fn era_length() -> T::BlockNumber {
Self::sessions_per_era() * >::length()
}
/// The combined balance of `who`.
pub fn voting_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 let LockStatus::Liquid = Self::unlock_block(who) {
Self::free_balance(who) >= value
} else {
false
}
}
/// The block at which the `who`'s funds become entirely liquid.
pub fn unlock_block(who: &T::AccountId) -> LockStatus {
match Self::bondage(who) {
i if i == T::BlockNumber::max_value() => LockStatus::Staked,
i if i <= >::block_number() => LockStatus::Liquid,
i => LockStatus::LockedUntil(i),
}
}
/// Create a smart-contract account.
pub fn create(aux: &T::PublicAux, code: &[u8], value: T::Balance) -> Result {
// commit anything that made it this far to storage
if let Some(commit) = Self::effect_create(aux.ref_into(), code, value, &DirectAccountDb)? {
>::merge(&mut DirectAccountDb, commit);
}
Ok(())
}
// PUBLIC DISPATCH
/// Transfer some unlocked staking balance to another staker.
/// TODO: probably want to state gas-limit and gas-price.
fn transfer(aux: &T::PublicAux, dest: Address, value: T::Balance) -> Result {
let dest = Self::lookup(dest)?;
// commit anything that made it this far to storage
if let Some(commit) = Self::effect_transfer(aux.ref_into(), &dest, value, &DirectAccountDb)? {
>::merge(&mut DirectAccountDb, commit);
}
Ok(())
}
/// Declare the desire to stake for the transactor.
///
/// Effects will be felt at the beginning of the next era.
fn stake(aux: &T::PublicAux) -> Result {
let mut intentions = >::get();
// can't be in the list twice.
ensure!(intentions.iter().find(|&t| t == aux.ref_into()).is_none(), "Cannot stake if already staked.");
intentions.push(aux.ref_into().clone());
>::put(intentions);
>::insert(aux.ref_into(), T::BlockNumber::max_value());
Ok(())
}
/// Retract the desire to stake for the transactor.
///
/// Effects will be felt at the beginning of the next era.
fn unstake(aux: &T::PublicAux) -> Result {
let mut intentions = >::get();
let position = intentions.iter().position(|t| t == aux.ref_into()).ok_or("Cannot unstake if not already staked.")?;
intentions.swap_remove(position);
>::put(intentions);
>::insert(aux.ref_into(), Self::current_era() + Self::bonding_duration());
Ok(())
}
// PRIV DISPATCH
/// Set the number of sessions in an era.
fn set_sessions_per_era(new: T::BlockNumber) -> Result {
>::put(&new);
Ok(())
}
/// The length of the bonding duration in eras.
fn set_bonding_duration(new: T::BlockNumber) -> Result {
>::put(&new);
Ok(())
}
/// The length of a staking era in sessions.
fn set_validator_count(new: u32) -> Result {
>::put(&new);
Ok(())
}
/// Force there to be a new era. This also forces a new session immediately after.
fn force_new_era() -> Result {
Self::new_era();
>::rotate_session();
Ok(())
}
// PUBLIC MUTABLES (DANGEROUS)
/// Set the free balance of an account to some new value. Will enforce ExistentialDeposit law,
/// anulling the account as needed.
pub fn set_reserved_balance(who: &T::AccountId, balance: T::Balance) -> bool {
if balance < Self::existential_deposit() {
Self::on_reserved_too_low(who);
false
} else {
>::insert(who, balance);
true
}
}
/// 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.
pub fn set_free_balance(who: &T::AccountId, balance: T::Balance) -> bool {
// Commented out for no - but consider it instructive.
// assert!(!Self::voting_balance(who).is_zero());
if balance < Self::existential_deposit() {
Self::on_free_too_low(who);
false
} else {
>::insert(who, balance);
true
}
}
/// 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 retutned. Full completion is given by `None`.
pub fn slash(who: &T::AccountId, value: T::Balance) -> Option {
let free_balance = Self::free_balance(who);
let free_slash = cmp::min(free_balance, value);
Self::set_free_balance(who, free_balance - free_slash);
if free_slash < value {
Self::slash_reserved(who, value - free_slash)
} else {
None
}
}
/// 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")
}
if Self::unlock_block(who) != LockStatus::Liquid {
return Err("free funds are still bonded")
}
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 retutned. Full completion is given by `None`.
/// NOTE: This is different to `reserve`.
pub fn unreserve(who: &T::AccountId, value: T::Balance) -> Option {
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 retutned. Full completion is given by `None`.
pub fn slash_reserved(who: &T::AccountId, value: T::Balance) -> Option {
let b = Self::reserved_balance(who);
let slash = cmp::min(b, value);
Self::set_reserved_balance(who, b - 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 retutned. Full completion is given by `Ok(None)`.
pub fn transfer_reserved(
slashed: &T::AccountId,
beneficiary: &T::AccountId,
value: T::Balance
) -> result::Result