Stash/controller model for staking (#1782)

* First steps to stash/controller separation

* More drafting

* More drafting

* Finish draft.

* Optimisation

* Remove accidental commit

* Make it build.

* Fix linked map for traits.

* Fix Option<_> variant.

*  Improve naming a tad

* Rebuild runtime

* Builds!

* First test.

* Bump RT version

* Minor fix

* Update Mock

* adds the correct reward testcase (+staking eras which was already ok)

* fixes the basic staking testcase to work properly (along with a small fix in the module)

* New logic to avoid controller transferring stash.

* Fix some build issues.

* adding some comments to tests

* Fix impls.

* adds a few more lines to explain the test case

* More fixes.

* gets the basic test up and running again

* Fix rest of build

* Rebuild wasm

* Fix docs.

* fix staking test with new chnages

* updating some tests, pending questions

* More working tests

* adds double staking test

* Docs

* remove invalid slashing test

* Payee stuff.

* Fix build

* Docs

* Fix test

* Fix a couple of tests

* Layout plan for finishing tests before Pragmen

* Add some working tests

* re-build staking and reward tests

* Add more tests

* fix offline grace test

* Nominator should have payee checked for cleanup

* adds more nomination tets

* adds validator prefs tests

* Fix and clean up some TODOs

* Fix a couple of issues

* Fix tests

* noting warnings from tests

* final fix of local tests

* Fix slot_stake bug

* Half baked test

* Add logic to limit `unstake_threshold` set in storage

* Make sure to check before writing!

Almost forgot this one

* Move a couple of comments

* fix last broken slot_stake test

* Ignore broken test
This commit is contained in:
Gav Wood
2019-03-02 14:31:48 +01:00
committed by GitHub
parent ff331e7fdc
commit 828cd9580a
24 changed files with 1871 additions and 910 deletions
+1
View File
@@ -3062,6 +3062,7 @@ dependencies = [
"sr-primitives 0.1.0",
"sr-std 0.1.0",
"srml-consensus 0.1.0",
"srml-session 0.1.0",
"srml-staking 0.1.0",
"srml-support 0.1.0",
"srml-system 0.1.0",
@@ -133,6 +133,8 @@ fn two_nodes_transfer_lots_of_packets() {
}
#[test]
#[ignore]
// TODO: remove ignore once this test it fixed. #1777
fn many_nodes_connectivity() {
// Creates many nodes, then make sure that they are all connected to each other.
// Note: if you increase this number, keep in mind that there's a limit to the number of
+5 -56
View File
@@ -27,9 +27,9 @@ use substrate_primitives;
use substrate_primitives::Blake2Hasher;
use crate::codec::{Codec, Encode, HasCompact};
pub use integer_sqrt::IntegerSquareRoot;
pub use num_traits::{Zero, One, Bounded};
pub use num_traits::ops::checked::{
CheckedAdd, CheckedSub, CheckedMul, CheckedDiv, CheckedShl, CheckedShr,
pub use num_traits::{
Zero, One, Bounded, CheckedAdd, CheckedSub, CheckedMul, CheckedDiv,
CheckedShl, CheckedShr, Saturating
};
use rstd::ops::{
Add, Sub, Mul, Div, Rem, AddAssign, SubAssign, MulAssign, DivAssign,
@@ -126,59 +126,6 @@ pub trait BlockNumberToHash {
}
}
/// Charge bytes fee trait
pub trait ChargeBytesFee<AccountId> {
/// Charge fees from `transactor` for an extrinsic (transaction) of encoded length
/// `encoded_len` bytes. Return Ok iff the payment was successful.
fn charge_base_bytes_fee(transactor: &AccountId, encoded_len: usize) -> Result<(), &'static str>;
}
/// Charge fee trait
pub trait ChargeFee<AccountId>: ChargeBytesFee<AccountId> {
/// The type of fee amount.
type Amount;
/// Charge `amount` of fees from `transactor`. Return Ok iff the payment was successful.
fn charge_fee(transactor: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
/// Refund `amount` of previous charged fees from `transactor`. Return Ok iff the refund was successful.
fn refund_fee(transactor: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
}
/// Transfer fungible asset trait
pub trait TransferAsset<AccountId> {
/// The type of asset amount.
type Amount;
/// Transfer asset from `from` account to `to` account with `amount` of asset.
fn transfer(from: &AccountId, to: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
/// Remove asset from `who` account by deducing `amount` in the account balances.
fn remove_from(who: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
/// Add asset to `who` account by increasing `amount` in the account balances.
fn add_to(who: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
}
impl<T> ChargeBytesFee<T> for () {
fn charge_base_bytes_fee(_: &T, _: usize) -> Result<(), &'static str> { Ok(()) }
}
impl<T> ChargeFee<T> for () {
type Amount = ();
fn charge_fee(_: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
fn refund_fee(_: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
}
impl<T> TransferAsset<T> for () {
type Amount = ();
fn transfer(_: &T, _: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
fn remove_from(_: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
fn add_to(_: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
}
/// Extensible conversion trait. Generic over both source and destination types.
pub trait Convert<A, B> {
/// Make conversion.
@@ -236,6 +183,7 @@ pub trait SimpleArithmetic:
CheckedSub +
CheckedMul +
CheckedDiv +
Saturating +
PartialOrd<Self> + Ord +
HasCompact
{}
@@ -253,6 +201,7 @@ impl<T:
CheckedSub +
CheckedMul +
CheckedDiv +
Saturating +
PartialOrd<Self> + Ord +
HasCompact
> SimpleArithmetic for T {}
+1
View File
@@ -1282,6 +1282,7 @@ dependencies = [
"serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
"sr-primitives 0.1.0",
"sr-std 0.1.0",
"srml-session 0.1.0",
"srml-staking 0.1.0",
"srml-support 0.1.0",
"srml-system 0.1.0",
+73 -35
View File
@@ -16,7 +16,7 @@
//! Substrate chain configurations.
use primitives::{Ed25519AuthorityId, ed25519};
use primitives::{Ed25519AuthorityId as AuthorityId, ed25519};
use node_primitives::AccountId;
use node_runtime::{ConsensusConfig, CouncilSeatsConfig, CouncilVotingConfig, DemocracyConfig,
SessionConfig, StakingConfig, TimestampConfig, BalancesConfig, TreasuryConfig,
@@ -39,14 +39,26 @@ pub fn dried_danta_config() -> Result<ChainSpec, String> {
}
fn staging_testnet_config_genesis() -> GenesisConfig {
let initial_authorities = vec![
// stash, controller, session-key
let initial_authorities: Vec<(AccountId, AccountId, AuthorityId)> = vec![(
hex!["fbecf7767fc63a6f9fa8094bbc5751d7269cd8e619cfdd9edfbe1fbc716b173e"].into(), // 5Hm2GcbuUct7sWX8d56zRktxr9D9Lw5hTFjSUhUoVHwFNmYW TODO: change once we switch to sr25519
hex!["6ed35e632190b9c795f019030e6c5cff1508655db28c83577e0a4366c9bd5773"].into(), // 5Ea1uyGz6H5WHZhWvPDxxLXWyiUkzWDwx54Hcn8LJ5dbFawH TODO: change once we switch to sr25519
hex!["82c39b31a2b79a90f8e66e7a77fdb85a4ed5517f2ae39f6a80565e8ecae85cf5"].into(),
),(
hex!["30b76ef977b84a575992ef52f561db315221123c68074269d3d51ce211c4a3dc"].into(), // 5DAaeTwVuyUmTyLBR5vKEDWeDJ75nhLutDuCJH58it7EHDM2 TODO: change once we switch to sr25519
hex!["a270edf24cb2a472b0e913fc43bfd4da0ef337cc715eaf94073d5198f7659f0c"].into(), // 5FjhAKgzpuzt1dYWE7H7Jb1sEHSuG5hcyZdPtfX829gmFVXh TODO: change once we switch to sr25519
hex!["4de37a07567ebcbf8c64568428a835269a566723687058e017b6d69db00a77e7"].into(),
),(
hex!["7b9e79c1bfc71ad0c4389565c01e79269dc512cb9bd856489671662481355417"].into(), // 5ErnpkRUbmM3WdbQwnVwfZeYs3iKmggEQceyB9db9ft18dSn TODO: change once we switch to sr25519
hex!["9ffec660c4d328306cf5e38faf4b132fb5c9f38287af95d9b25629fc29de3945"].into(), // 5FgV9vxNpdCXMUmHCLQcsN4mUUUG6ZpFuvAMrm5X4BUnFhie TODO: change once we switch to sr25519
hex!["063d7787ebca768b7445dfebe7d62cbb1625ff4dba288ea34488da266dd6dca5"].into(),
),(
hex!["7e58b096b95c4b3b271f27fedd9f2c51edd48b9d37046240e601180c9dcc8c27"].into(), // 5EvNEhYYd4b9giczuCo2o8bfLZoKW9jnTeUukfL1NWsAAeEx TODO: change once we switch to sr25519
hex!["36dfc933bb0848d8addf16a961369b2e122633a5819a19e43c8142381a1280e3"].into(), // 5DJevPKpz4EEvmSpK7W6KemS3i5JYPq5FEuEewgRY2cZCxNg TODO: change once we switch to sr25519
hex!["8101764f45778d4980dadaceee6e8af2517d3ab91ac9bec9cd1714fa5994081c"].into(),
];
let endowed_accounts = vec![
hex!["f295940fa750df68a686fcf4abd4111c8a9c5a5a5a83c4c8639c451a94a7adfd"].into(),
)];
let endowed_accounts: Vec<AccountId> = vec![
hex!["f295940fa750df68a686fcf4abd4111c8a9c5a5a5a83c4c8639c451a94a7adfd"].into(), // 5HYmsxGRAmZMjyZYmf7uGPL2YDQGHEt6NjGrfUuxNEgeGBRN TODO: change once we switch to sr25519
];
const MILLICENTS: u128 = 1_000_000_000;
const CENTS: u128 = 1_000 * MILLICENTS; // assume this is worth about a cent.
@@ -57,29 +69,37 @@ fn staging_testnet_config_genesis() -> GenesisConfig {
const HOURS: u64 = MINUTES * 60;
const DAYS: u64 = HOURS * 24;
const ENDOWMENT: u128 = 10_000_000 * DOLLARS;
const STASH: u128 = 100 * DOLLARS;
GenesisConfig {
consensus: Some(ConsensusConfig {
code: include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm").to_vec(), // FIXME change once we have #1252
authorities: initial_authorities.clone(),
authorities: initial_authorities.iter().map(|x| x.2.clone()).collect(),
}),
system: None,
balances: Some(BalancesConfig {
balances: endowed_accounts.iter().map(|&k| (k, 10_000_000 * DOLLARS)).collect(),
balances: endowed_accounts.iter()
.map(|&k| (k, ENDOWMENT))
.chain(initial_authorities.iter().map(|x| (x.0.clone(), STASH)))
.collect(),
existential_deposit: 1 * DOLLARS,
transfer_fee: 1 * CENTS,
creation_fee: 1 * CENTS,
vesting: vec![],
}),
indices: Some(IndicesConfig {
ids: endowed_accounts.clone(),
ids: endowed_accounts.iter().cloned()
.chain(initial_authorities.iter().map(|x| x.0.clone()))
.collect::<Vec<_>>(),
}),
session: Some(SessionConfig {
validators: initial_authorities.iter().cloned().map(Into::into).collect(),
validators: initial_authorities.iter().map(|x| x.1.into()).collect(),
session_length: 5 * MINUTES,
keys: initial_authorities.iter().map(|x| (x.1.clone(), x.2.clone())).collect::<Vec<_>>(),
}),
staking: Some(StakingConfig {
current_era: 0,
intentions: initial_authorities.iter().cloned().map(Into::into).collect(),
offline_slash: Perbill::from_billionths(1_000_000),
session_reward: Perbill::from_billionths(2_065),
current_offline_slash: 0,
@@ -89,7 +109,8 @@ fn staging_testnet_config_genesis() -> GenesisConfig {
bonding_duration: 60 * MINUTES,
offline_slash_grace: 4,
minimum_validator_count: 4,
invulnerables: initial_authorities.iter().cloned().map(Into::into).collect(),
stakers: initial_authorities.iter().map(|x| (x.0.into(), x.1.into(), STASH)).collect(),
invulnerables: initial_authorities.iter().map(|x| x.1.into()).collect(),
}),
democracy: Some(DemocracyConfig {
launch_period: 10 * MINUTES, // 1 day per public referendum
@@ -137,7 +158,7 @@ fn staging_testnet_config_genesis() -> GenesisConfig {
key: endowed_accounts[0].clone(),
}),
grandpa: Some(GrandpaConfig {
authorities: initial_authorities.clone().into_iter().map(|k| (k, 1)).collect(),
authorities: initial_authorities.iter().map(|x| (x.2.clone(), 1)).collect(),
}),
fees: Some(FeesConfig {
transaction_base_fee: 1 * CENTS,
@@ -162,52 +183,68 @@ pub fn staging_testnet_config() -> ChainSpec {
}
/// Helper function to generate AuthorityID from seed
pub fn get_authority_id_from_seed(seed: &str) -> Ed25519AuthorityId {
pub fn get_account_id_from_seed(seed: &str) -> AccountId {
let padded_seed = pad_seed(seed);
// NOTE from ed25519 impl:
// prefer pkcs#8 unless security doesn't matter -- this is used primarily for tests.
ed25519::Pair::from_seed(&padded_seed).public().0.into()
}
/// Helper function to generate stash, controller and session key from seed
pub fn get_authority_keys_from_seed(seed: &str) -> (AccountId, AccountId, AuthorityId) {
let padded_seed = pad_seed(seed);
// NOTE from ed25519 impl:
// prefer pkcs#8 unless security doesn't matter -- this is used primarily for tests.
(
get_account_id_from_seed(&format!("{}-stash", seed)),
get_account_id_from_seed(seed),
ed25519::Pair::from_seed(&padded_seed).public().0.into()
)
}
/// Helper function to create GenesisConfig for testing
pub fn testnet_genesis(
initial_authorities: Vec<Ed25519AuthorityId>,
initial_authorities: Vec<(AccountId, AccountId, AuthorityId)>,
root_key: AccountId,
endowed_accounts: Option<Vec<Ed25519AuthorityId>>,
endowed_accounts: Option<Vec<AccountId>>,
) -> GenesisConfig {
let endowed_accounts = endowed_accounts.unwrap_or_else(|| {
let endowed_accounts: Vec<AccountId> = endowed_accounts.unwrap_or_else(|| {
vec![
get_authority_id_from_seed("Alice"),
get_authority_id_from_seed("Bob"),
get_authority_id_from_seed("Charlie"),
get_authority_id_from_seed("Dave"),
get_authority_id_from_seed("Eve"),
get_authority_id_from_seed("Ferdie"),
get_account_id_from_seed("Alice"),
get_account_id_from_seed("Bob"),
get_account_id_from_seed("Charlie"),
get_account_id_from_seed("Dave"),
get_account_id_from_seed("Eve"),
get_account_id_from_seed("Ferdie"),
]
});
const STASH: u128 = 1 << 20;
const ENDOWMENT: u128 = 1 << 20;
GenesisConfig {
consensus: Some(ConsensusConfig {
code: include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm").to_vec(),
authorities: initial_authorities.clone(),
authorities: initial_authorities.iter().map(|x| x.2.clone()).collect(),
}),
system: None,
indices: Some(IndicesConfig {
ids: endowed_accounts.iter().map(|x| x.0.into()).collect(),
ids: endowed_accounts.clone(),
}),
balances: Some(BalancesConfig {
existential_deposit: 500,
transfer_fee: 0,
creation_fee: 0,
balances: endowed_accounts.iter().map(|&k| (k.into(), (1 << 60))).collect(),
balances: endowed_accounts.iter().map(|&k| (k.into(), ENDOWMENT)).collect(),
vesting: vec![],
}),
session: Some(SessionConfig {
validators: initial_authorities.iter().cloned().map(Into::into).collect(),
validators: initial_authorities.iter().map(|x| x.1.into()).collect(),
session_length: 10,
keys: initial_authorities.iter().map(|x| (x.1.clone(), x.2.clone())).collect::<Vec<_>>(),
}),
staking: Some(StakingConfig {
current_era: 0,
intentions: initial_authorities.iter().cloned().map(Into::into).collect(),
minimum_validator_count: 1,
validator_count: 2,
sessions_per_era: 5,
@@ -217,7 +254,8 @@ pub fn testnet_genesis(
current_offline_slash: 0,
current_session_reward: 0,
offline_slash_grace: 0,
invulnerables: initial_authorities.iter().cloned().map(Into::into).collect(),
stakers: initial_authorities.iter().map(|x| (x.0.into(), x.1.into(), STASH)).collect(),
invulnerables: initial_authorities.iter().map(|x| x.1.into()).collect(),
}),
democracy: Some(DemocracyConfig {
launch_period: 9,
@@ -228,7 +266,7 @@ pub fn testnet_genesis(
}),
council_seats: Some(CouncilSeatsConfig {
active_council: endowed_accounts.iter()
.filter(|a| initial_authorities.iter().find(|&b| a.0 == b.0).is_none())
.filter(|&endowed| initial_authorities.iter().find(|&(_, controller, _)| controller == endowed).is_none())
.map(|a| (a.clone().into(), 1000000)).collect(),
candidacy_bond: 10,
voter_bond: 2,
@@ -267,7 +305,7 @@ pub fn testnet_genesis(
key: root_key,
}),
grandpa: Some(GrandpaConfig {
authorities: initial_authorities.clone().into_iter().map(|k| (k, 1)).collect(),
authorities: initial_authorities.iter().map(|x| (x.2.clone(), 1)).collect(),
}),
fees: Some(FeesConfig {
transaction_base_fee: 1,
@@ -279,9 +317,9 @@ pub fn testnet_genesis(
fn development_config_genesis() -> GenesisConfig {
testnet_genesis(
vec![
get_authority_id_from_seed("Alice"),
get_authority_keys_from_seed("Alice"),
],
get_authority_id_from_seed("Alice").into(),
get_account_id_from_seed("Alice").into(),
None,
)
}
@@ -294,10 +332,10 @@ pub fn development_config() -> ChainSpec {
fn local_testnet_genesis() -> GenesisConfig {
testnet_genesis(
vec![
get_authority_id_from_seed("Alice"),
get_authority_id_from_seed("Bob"),
get_authority_keys_from_seed("Alice"),
get_authority_keys_from_seed("Bob"),
],
get_authority_id_from_seed("Alice").into(),
get_account_id_from_seed("Alice").into(),
None,
)
}
+52 -33
View File
@@ -66,6 +66,18 @@ mod tests {
AccountId::from(Keyring::Charlie.to_raw_public())
}
fn dave() -> AccountId {
AccountId::from(Keyring::Dave.to_raw_public())
}
fn eve() -> AccountId {
AccountId::from(Keyring::Eve.to_raw_public())
}
fn ferdie() -> AccountId {
AccountId::from(Keyring::Ferdie.to_raw_public())
}
fn sign(xt: CheckedExtrinsic) -> UncheckedExtrinsic {
match xt.signed {
Some((signed, index)) => {
@@ -258,12 +270,16 @@ mod tests {
..Default::default()
}),
indices: Some(IndicesConfig {
ids: vec![alice(), charlie()],
ids: vec![alice(), bob(), charlie(), dave(), eve(), ferdie()],
}),
balances: Some(BalancesConfig {
balances: vec![
(alice(), 111),
(bob(), 100),
(charlie(), 100_000_000),
(dave(), 100),
(eve(), 100),
(ferdie(), 100),
],
existential_deposit: 0,
transfer_fee: 0,
@@ -273,11 +289,16 @@ mod tests {
session: Some(SessionConfig {
session_length: 2,
validators: vec![Keyring::One.to_raw_public().into(), Keyring::Two.to_raw_public().into(), three],
keys: vec![
(alice(), keyring::ed25519::Keyring::Alice.to_raw_public().into()),
(bob(), keyring::ed25519::Keyring::Bob.to_raw_public().into()),
(charlie(), keyring::ed25519::Keyring::Charlie.to_raw_public().into())
]
}),
staking: Some(StakingConfig {
sessions_per_era: 2,
current_era: 0,
intentions: vec![alice(), bob(), Keyring::Charlie.to_raw_public().into()],
stakers: vec![(dave(), alice(), 111), (eve(), bob(), 100), (ferdie(), charlie(), 100)],
validator_count: 3,
minimum_validator_count: 0,
bonding_duration: 0,
@@ -286,7 +307,7 @@ mod tests {
current_offline_slash: 0,
current_session_reward: 0,
offline_slash_grace: 0,
invulnerables: vec![alice(), bob(), Keyring::Charlie.to_raw_public().into()],
invulnerables: vec![alice(), bob(), charlie()],
}),
democracy: Some(Default::default()),
council_seats: Some(Default::default()),
@@ -297,9 +318,9 @@ mod tests {
sudo: Some(Default::default()),
grandpa: Some(GrandpaConfig {
authorities: vec![ // set these so no GRANDPA events fire when session changes
(Keyring::Alice.to_raw_public().into(), 1),
(Keyring::Bob.to_raw_public().into(), 1),
(Keyring::Charlie.to_raw_public().into(), 1),
(keyring::ed25519::Keyring::Alice.to_raw_public().into(), 1),
(keyring::ed25519::Keyring::Bob.to_raw_public().into(), 1),
(keyring::ed25519::Keyring::Charlie.to_raw_public().into(), 1),
],
}),
fees: Some(FeesConfig {
@@ -353,7 +374,7 @@ mod tests {
).0.unwrap();
}
let correct_header = match Executor::new(None).call::<_, NeverNativeValue, fn() -> _>(
let header = match Executor::new(None).call::<_, NeverNativeValue, fn() -> _>(
env,
"BlockBuilder_finalise_block",
&[0u8;0],
@@ -364,9 +385,8 @@ mod tests {
NativeOrEncoded::Encoded(h) => Header::decode(&mut &h[..]).unwrap(),
};
let hash = correct_header.blake2_256();
(Block { header: correct_header, extrinsics }.encode(), hash.into())
let hash = header.blake2_256();
(Block { header, extrinsics }.encode(), hash.into())
}
fn changes_trie_block() -> (Vec<u8>, Hash) {
@@ -471,24 +491,15 @@ mod tests {
).0.unwrap();
runtime_io::with_externalities(&mut t, || {
assert_eq!(Balances::total_balance(&alice()), 41);
assert_eq!(Balances::total_balance(&bob()), 69);
// block1 transfers from alice 69 to bob.
// -1 is the default fee
assert_eq!(Balances::total_balance(&alice()), 111 - 69 - 1);
assert_eq!(Balances::total_balance(&bob()), 100 + 69);
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: Event::system(system::Event::ExtrinsicSuccess)
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: Event::indices(indices::RawEvent::NewAccountIndex(bob(), 2))
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: Event::balances(balances::RawEvent::NewAccount(
bob().into(),
69
))
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: Event::balances(balances::RawEvent::Transfer(
@@ -530,8 +541,11 @@ mod tests {
).0.unwrap();
runtime_io::with_externalities(&mut t, || {
assert_eq!(Balances::total_balance(&alice()), 30);
assert_eq!(Balances::total_balance(&bob()), 78);
// bob sends 5, alice sends 15 | bob += 10, alice -= 10
// 111 - 69 - 1 - 10 - 1 = 30
assert_eq!(Balances::total_balance(&alice()), 111 - 69 - 1 - 10 - 1);
// 100 + 69 + 10 - 1 = 178
assert_eq!(Balances::total_balance(&bob()), 100 + 69 + 10 - 1);
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
@@ -571,10 +585,10 @@ mod tests {
phase: Phase::Finalization,
event: Event::session(session::RawEvent::NewSession(1))
},
EventRecord {
phase: Phase::Finalization,
event: Event::staking(staking::RawEvent::Reward(0))
},
// EventRecord {
// phase: Phase::Finalization,
// event: Event::staking(staking::RawEvent::Reward(0))
// },
EventRecord {
phase: Phase::Finalization,
event: Event::grandpa(::grandpa::RawEvent::NewAuthorities(vec![
@@ -616,15 +630,20 @@ mod tests {
WasmExecutor::new().call(&mut t, 8, COMPACT_CODE, "Core_execute_block", &block1.0).unwrap();
runtime_io::with_externalities(&mut t, || {
assert_eq!(Balances::total_balance(&alice()), 41);
assert_eq!(Balances::total_balance(&bob()), 69);
// block1 transfers from alice 69 to bob.
// -1 is the default fee
assert_eq!(Balances::total_balance(&alice()), 111 - 69 - 1);
assert_eq!(Balances::total_balance(&bob()), 100 + 69);
});
WasmExecutor::new().call(&mut t, 8, COMPACT_CODE, "Core_execute_block", &block2.0).unwrap();
runtime_io::with_externalities(&mut t, || {
assert_eq!(Balances::total_balance(&alice()), 30);
assert_eq!(Balances::total_balance(&bob()), 78);
// bob sends 5, alice sends 15 | bob += 10, alice -= 10
// 111 - 69 - 1 - 10 - 1 = 30
assert_eq!(Balances::total_balance(&alice()), 111 - 69 - 1 - 10 - 1);
// 100 + 69 + 10 - 1 = 178
assert_eq!(Balances::total_balance(&bob()), 100 + 69 + 10 - 1);
});
}
+2 -2
View File
@@ -60,8 +60,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("node"),
impl_name: create_runtime_str!("substrate-node"),
authoring_version: 10,
spec_version: 30,
impl_version: 34,
spec_version: 31,
impl_version: 31,
apis: RUNTIME_API_VERSIONS,
};
+1
View File
@@ -1305,6 +1305,7 @@ dependencies = [
"serde 1.0.81 (registry+https://github.com/rust-lang/crates.io-index)",
"sr-primitives 0.1.0",
"sr-std 0.1.0",
"srml-session 0.1.0",
"srml-staking 0.1.0",
"srml-support 0.1.0",
"srml-system 0.1.0",
+1
View File
@@ -16,6 +16,7 @@ srml-support = { path = "../support", default-features = false }
system = { package = "srml-system", path = "../system", default-features = false }
timestamp = { package = "srml-timestamp", path = "../timestamp", default-features = false }
staking = { package = "srml-staking", path = "../staking", default-features = false }
session = { package = "srml-session", path = "../session", default-features = false }
[dev-dependencies]
lazy_static = "1.0"
+1 -1
View File
@@ -209,7 +209,7 @@ pub struct StakingSlasher<T>(::rstd::marker::PhantomData<T>);
impl<T: staking::Trait + Trait> HandleReport for StakingSlasher<T> {
fn handle_report(report: AuraReport) {
let validators = staking::Module::<T>::validators();
let validators = session::Module::<T>::validators();
report.punish(
validators.len(),
+21 -42
View File
@@ -29,10 +29,13 @@ use rstd::{cmp, result};
use parity_codec::Codec;
use parity_codec_derive::{Encode, Decode};
use srml_support::{StorageValue, StorageMap, Parameter, decl_event, decl_storage, decl_module, ensure};
use srml_support::traits::{UpdateBalanceOutcome, Currency, EnsureAccountLiquid, OnFreeBalanceZero, ArithmeticType};
use srml_support::traits::{
UpdateBalanceOutcome, Currency, EnsureAccountLiquid, OnFreeBalanceZero, TransferAsset, WithdrawReason, ArithmeticType
};
use srml_support::dispatch::Result;
use primitives::traits::{Zero, SimpleArithmetic,
As, StaticLookup, Member, CheckedAdd, CheckedSub, MaybeSerializeDebug, TransferAsset};
use primitives::traits::{
Zero, SimpleArithmetic, As, StaticLookup, Member, CheckedAdd, CheckedSub, MaybeSerializeDebug
};
use system::{IsDeadAccount, OnNewAccount, ensure_signed};
mod mock;
@@ -52,7 +55,7 @@ pub trait Trait: system::Trait {
type OnNewAccount: OnNewAccount<Self::AccountId>;
/// A function that returns true iff a given account can transfer its funds to another account.
type EnsureAccountLiquid: EnsureAccountLiquid<Self::AccountId>;
type EnsureAccountLiquid: EnsureAccountLiquid<Self::AccountId, Self::Balance>;
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
@@ -272,34 +275,6 @@ impl<T: Trait> Module<T> {
}
}
/// 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)
}
/// Substrates `value` from the free balance of `who`. If the whole amount cannot be
/// deducted, an error is returned.
///
/// NOTE: This assumes that the total stake remains unchanged after this operation. If
/// you mean to actually burn value out of existence, then use `slash` instead.
pub fn decrease_free_balance(
who: &T::AccountId,
value: T::Balance
) -> result::Result<UpdateBalanceOutcome, &'static str> {
T::EnsureAccountLiquid::ensure_account_liquid(who)?;
let b = Self::free_balance(who);
if b < value {
return Err("account has too few funds")
}
Ok(Self::set_free_balance(who, b - value))
}
/// 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);
@@ -320,7 +295,7 @@ impl<T: Trait> Module<T> {
if would_create && value < Self::existential_deposit() {
return Err("value too low to create account");
}
T::EnsureAccountLiquid::ensure_account_liquid(transactor)?;
T::EnsureAccountLiquid::ensure_account_can_withdraw(transactor, value, WithdrawReason::Transfer)?;
// NOTE: total stake being stored in the same type means that this could never overflow
// but better to be safe than sorry.
@@ -402,23 +377,27 @@ where
}
fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool {
if T::EnsureAccountLiquid::ensure_account_liquid(who).is_ok() {
if T::EnsureAccountLiquid::ensure_account_can_withdraw(who, value, WithdrawReason::Reserve).is_ok() {
Self::free_balance(who) >= value
} else {
false
}
}
fn total_issuance() -> Self:: Balance {
Self::total_issuance()
fn total_issuance() -> Self::Balance {
<TotalIssuance<T>>::get()
}
fn minimum_balance() -> Self::Balance {
Self::existential_deposit()
}
fn free_balance(who: &T::AccountId) -> Self::Balance {
Self::free_balance(who)
<FreeBalance<T>>::get(who)
}
fn reserved_balance(who: &T::AccountId) -> Self::Balance {
Self::reserved_balance(who)
<ReservedBalance<T>>::get(who)
}
fn slash(who: &T::AccountId, value: Self::Balance) -> Option<Self::Balance> {
@@ -451,7 +430,7 @@ where
if b < value {
return Err("not enough free funds")
}
T::EnsureAccountLiquid::ensure_account_liquid(who)?;
T::EnsureAccountLiquid::ensure_account_can_withdraw(who, value, WithdrawReason::Reserve)?;
Self::set_reserved_balance(who, Self::reserved_balance(who) + value);
Self::set_free_balance(who, b - value);
Ok(())
@@ -508,8 +487,8 @@ impl<T: Trait> TransferAsset<T::AccountId> for Module<T> {
Self::make_transfer(from, to, amount)
}
fn remove_from(who: &T::AccountId, value: T::Balance) -> Result {
T::EnsureAccountLiquid::ensure_account_liquid(who)?;
fn withdraw(who: &T::AccountId, value: T::Balance, reason: WithdrawReason) -> Result {
T::EnsureAccountLiquid::ensure_account_can_withdraw(who, value, reason)?;
let b = Self::free_balance(who);
ensure!(b >= value, "account has too few funds");
Self::set_free_balance(who, b - value);
@@ -517,7 +496,7 @@ impl<T: Trait> TransferAsset<T::AccountId> for Module<T> {
Ok(())
}
fn add_to(who: &T::AccountId, value: T::Balance) -> Result {
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);
Ok(())
-11
View File
@@ -154,17 +154,6 @@ fn balance_transfer_works() {
});
}
#[test]
fn balance_reduction_works() {
with_externalities(&mut ExtBuilder::default().build(), || {
Balances::set_free_balance(&1, 111);
Balances::increase_total_stake_by(111);
assert_ok!(Balances::decrease_free_balance(&1, 69).map(|_| ()));
assert_eq!(Balances::total_balance(&1), 42);
assert_noop!(Balances::decrease_free_balance(&1, 69).map(|_| ()), "account has too few funds");
});
}
#[test]
fn reserving_balance_should_work() {
with_externalities(&mut ExtBuilder::default().build(), || {
+17 -4
View File
@@ -24,7 +24,7 @@ use primitives::traits::{Zero, As};
use parity_codec_derive::{Encode, Decode};
use srml_support::{StorageValue, StorageMap, Parameter, Dispatchable, IsSubType};
use srml_support::{decl_module, decl_storage, decl_event, ensure};
use srml_support::traits::{Currency, OnFreeBalanceZero, EnsureAccountLiquid, ArithmeticType};
use srml_support::traits::{Currency, OnFreeBalanceZero, EnsureAccountLiquid, WithdrawReason, ArithmeticType};
use srml_support::dispatch::Result;
use system::ensure_signed;
@@ -415,12 +415,25 @@ impl<T: Trait> OnFreeBalanceZero<T::AccountId> for Module<T> {
}
}
impl<T: Trait> EnsureAccountLiquid<T::AccountId> for Module<T> {
impl<T: Trait> EnsureAccountLiquid<T::AccountId, BalanceOf<T>> for Module<T> {
fn ensure_account_liquid(who: &T::AccountId) -> Result {
if Self::bondage(who) <= <system::Module<T>>::block_number() {
if Self::bondage(who) > <system::Module<T>>::block_number() {
Err("stash accounts are not liquid")
} else {
Ok(())
}
}
fn ensure_account_can_withdraw(
who: &T::AccountId,
_value: BalanceOf<T>,
reason: WithdrawReason,
) -> Result {
if reason == WithdrawReason::TransactionPayment
|| Self::bondage(who) <= <system::Module<T>>::block_number()
{
Ok(())
} else {
Err("cannot transfer illiquid funds")
Err("cannot transfer voting funds")
}
}
}
+2 -2
View File
@@ -23,8 +23,8 @@ use rstd::prelude::*;
use rstd::marker::PhantomData;
use rstd::result;
use primitives::traits::{self, Header, Zero, One, Checkable, Applyable, CheckEqual, OnFinalise,
OnInitialise, ChargeBytesFee, Hash, As, Digest};
use srml_support::Dispatchable;
OnInitialise, Hash, As, Digest};
use srml_support::{Dispatchable, traits::ChargeBytesFee};
use parity_codec::{Codec, Encode};
use system::extrinsics_root;
use primitives::{ApplyOutcome, ApplyError};
+7 -5
View File
@@ -19,10 +19,12 @@
// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]
use srml_support::{dispatch::Result, traits::ArithmeticType, StorageMap, decl_event, decl_storage, decl_module};
use srml_support::{
dispatch::Result, StorageMap, decl_event, decl_storage, decl_module,
traits::{ArithmeticType, ChargeBytesFee, ChargeFee, TransferAsset, WithdrawReason}
};
use runtime_primitives::traits::{
As, ChargeBytesFee, ChargeFee,
TransferAsset, CheckedAdd, CheckedSub, CheckedMul, Zero
As, CheckedAdd, CheckedSub, CheckedMul, Zero
};
use system;
@@ -96,7 +98,7 @@ impl<T: Trait> ChargeFee<T::AccountId> for Module<T> {
let current_fee = Self::current_transaction_fee(extrinsic_index);
let new_fee = current_fee.checked_add(&amount).ok_or_else(|| "fee got overflow after charge")?;
T::TransferAsset::remove_from(transactor, amount)?;
T::TransferAsset::withdraw(transactor, amount, WithdrawReason::TransactionPayment)?;
<CurrentTransactionFee<T>>::insert(extrinsic_index, new_fee);
Ok(())
@@ -107,7 +109,7 @@ impl<T: Trait> ChargeFee<T::AccountId> for Module<T> {
let current_fee = Self::current_transaction_fee(extrinsic_index);
let new_fee = current_fee.checked_sub(&amount).ok_or_else(|| "fee got underflow after refund")?;
T::TransferAsset::add_to(transactor, amount)?;
T::TransferAsset::deposit(transactor, amount)?;
<CurrentTransactionFee<T>>::insert(extrinsic_index, new_fee);
Ok(())
+7 -4
View File
@@ -20,12 +20,15 @@
use runtime_primitives::BuildStorage;
use runtime_primitives::{
traits::{IdentityLookup, BlakeTwo256, TransferAsset},
traits::{IdentityLookup, BlakeTwo256},
testing::{Digest, DigestItem, Header},
};
use primitives::{H256, Blake2Hasher};
use runtime_io;
use srml_support::{impl_outer_origin, impl_outer_event, traits::ArithmeticType};
use srml_support::{
impl_outer_origin, impl_outer_event,
traits::{ArithmeticType, TransferAsset, WithdrawReason}
};
use crate::{GenesisConfig, Module, Trait, system};
impl_outer_origin!{
@@ -48,8 +51,8 @@ impl<AccountId> TransferAsset<AccountId> for TransferAssetMock {
type Amount = u64;
fn transfer(_: &AccountId, _: &AccountId, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
fn remove_from(_: &AccountId, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
fn add_to(_: &AccountId, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
fn withdraw(_: &AccountId, _: Self::Amount, _: WithdrawReason) -> Result<(), &'static str> { Ok(()) }
fn deposit(_: &AccountId, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
}
impl ArithmeticType for TransferAssetMock {
+7 -1
View File
@@ -110,10 +110,15 @@ decl_storage! {
/// Block at which the session length last changed.
LastLengthChange: Option<T::BlockNumber>;
/// The next key for a given validator.
NextKeyFor: map T::AccountId => Option<T::SessionKey>;
NextKeyFor build(|config: &GenesisConfig<T>| {
config.keys.clone()
}): map T::AccountId => Option<T::SessionKey>;
/// The next session length.
NextSessionLength: Option<T::BlockNumber>;
}
add_extra_genesis {
config(keys): Vec<(T::AccountId, T::SessionKey)>;
}
}
impl<T: Trait> Module<T> {
@@ -270,6 +275,7 @@ mod tests {
t.extend(GenesisConfig::<Test>{
session_length: 2,
validators: vec![1, 2, 3],
keys: vec![],
}.build_storage().unwrap().0);
runtime_io::TestExternalities::new(t)
}
+62
View File
@@ -0,0 +1,62 @@
# Module Summary, Description and Specification
## Staking
The staking module is the means by which a set of network maintainers (known as "authorities" in some contexts and "validators" in others) are chosen based upon those who voluntarily place funds under deposit. Under deposit, those funds are rewarded under normal operation but are held at pain of "slash" (expropriation) should they be found not to bee discharhing their duties properly.
### Vocabulary
- Staking: The process of locking up funds for some time, placing them at risk of slashing (loss) in order to become a rewarded maintainer of the network.
- Validating: The process of running a node to actively maintain the network, either by producing blocks or guaranteeing finality of the chain.
- Nominating: The process of placing staked funds behind one or more validators in order to share in any reward, and punishment, they take.
- Stash account: The account holding an owner's funds used for staking.
- Controller account: The account which controls am owner's funds for staking.
- Era: A (whole) number of sessions which is the period that the validator set (and each validator's active nominator set) is recalculated and where rewards are paid out.
- Slash: The punishment of a staker by reducing their funds.
### Goals
The staking system in Substrate NPoS is designed to achieve three goals:
- It should be possible to stake funds that are controlled by a cold wallet.
- It should be possible to withdraw some, or deposit more, funds without interrupting the role of t.
- It should be possible to switch between roles (nominator, validator, idle) with minimal overhead.
### Stash account
To achieve these goals, Substrate NPoS distinguishes the act of staking from the act of declaring the role (nominating or validating) desired. An owner of funds wanting to validate or nominate must first deposit some or all of an account's balance to be managed by the staking system. When they do this, we call it *staking* and we say the funds are *under management* and *bonded*. A transaction-dispatchable call `bond` is provided for this. Once an account has funds bonded, those funds may no longer be transfered out or deducted in any way, including for transaction fees payment. If all funds of the account are thus used, then the account is effectively locked since it is unable to pay for any transactions.
Since the funds under management may be entirely frozen, and quite possibly controlled only by an offline cold wallet device, another account is used to control the staking activity of these funds. At the point of staking an account, this account is declared. Whereas the account holding the funds under management is known as the *stash*, the second account that controls the staking activity is called the *controller* account. Once staked, the stash account has no further transactional interaction with the staking module; all transactions regarding the staking activity of the stash are signed with the controller account. If there are unmanaged funds, then non-staking transactions may still be issued from the stash account, such as transfering funds out with the balances module.
### Controller account
Once the stash account's funds are committed under management of the staking system, then the controller account takes over. Three operations allow the owner to control their role in the staking system, switching between idle (no role at all) with the `chill` call; switching to a validator role with the `validate` call; and finally switching to the nominator role with `nominate`. In the case of the latter, the set of validators they are happy to entrust their stake to is also provided. The effect of these operations is felt at the next point that the nominator/validator set is recalculated, which will usually be at the end of the current era.
Three further operations are provided for the fund management: two for withdrawing funds that are under management of the staking system `unbond` and `withdraw_unbonded`, and another for introducing additional funds under management, `bond_extra`. Regarding the withdrawal of funds, the funds become inactive in the staking system from the era following the `unbond` call, however they may not be transfered out of the account (by a normal transfer operation using the stash key) until the bonding period has ended. At that point, the `withdraw_unbonded` must be called before the funds are free to bee used.
Funds deposited into the stash account will not automatically be introduced under management of the staking system: They may be retransfered out of the stash account normally until they enter under management. If there is a desire to bring such funds not yet under managment into the staking system, a separate transaction calling `bond_extra` must be issued to do this.
### Validating
A `validate` transaction takes a parameter of type `ValidatorPrefs`; this encodes a set of options available to validators. There are two options here: the `unstake_threshold` and `validator_payment`. The former allows a validator to control how long they acrue punishment for being offline before they are finally removed from the validator list and have the slash deducted. There is a tradeoff between being removed from the validator set early and thus missing out on an era's rewards and risking a much more substantial punishment as the slash amount increases exponentially with each offline infraction.
The latter option, `validator_payment`, allows a validator to reserve some amount of funds for themselves before the rest is shared out, *pro rata* amongst itself and the nominators. By "default", this is zero which means the validator and nominators partake in the rewards equally. However, by raising this, the validator may reserve some of the funds for themselves, making them a less attractive financial proposal compared to other less "greedy" validators. This allows over-subscribed validators to monetise their reputation and provides a macroeconomic mechanism of redistributing nominations between different validators.
### Nonminating
A `nominate` transaction take a single parameter which is the set of validator identities the nominator approves of their stake backing. Nomination does not allow control of *how much* of the stake backs any individual validator. If a staker wishes to have such fine-grained control, they could split their funds between several accounts and stake each individually to effect such a arrangement.
At the beginning of each era, each staker's funds is automatically allocated between some or all of each of their nominated validators, possibly with some (or, in extremis all) left unallocated. Only the portion of their stake that is allocated generates rewards and is at risk of slashing.
When an era begins, a basic usage of the Phragmén method gives an initial allocation. Over some initial period (perhaps one session) in the era, third-parties may submit their own solutions (typically determined by running Phragmén more extensively) in order to further optimise the allocation between nominators and validators. At the end of the initial period, the allocation is fixed for the rest of the era. During the initial period, any slashing uses the initial, suboptimal allocations.
### Rewards & Payouts
At the end of each successful session, a reward is accrued according to the overall timeliness of blocks. If the session's aveerage block period was optimal, then the maximum reward is accrued; the fewer blocks producted, the lower the reward. At the end of each era, each validator is paid this overall reward into an account of their choosing. Nominator portions are distributed *pro rata* for the amount of stake backing that validator and according to the validator's preferences.
There are three possible payment destinations or `Payee`s and this is set during the call to `bond` and may be updated by dispatching a `set_payee` transaction. The `Controller` payee places rewards into the controller account. The `Stash` and `Staked` targets both place rewards into the stash account, but the latter also places the rewards immediately under management.
### Slashing
Slashing happens when a validator has misbehaved in some way. Funds may be slashed up until the point they are withdrawn from management (using the `withdraw_unbonded` call). Digests of validator and nominator arrangements are recorded in order to ensure that historical misbehaviour can be properly attributed to stakes and punished.
For a slash on some validator balance and associated nominator balances, the validator balance is reduced at preference. If the slash amount is greater than that which the validator has at stake, then the nominators balances are reduced pro rata for the remainder.
+521 -305
View File
@@ -20,15 +20,17 @@
#![cfg_attr(not(feature = "std"), no_std)]
use rstd::{prelude::*, cmp};
use rstd::{prelude::*, result};
use parity_codec::HasCompact;
use parity_codec_derive::{Encode, Decode};
use srml_support::{Parameter, StorageValue, StorageMap, dispatch::Result};
use srml_support::{StorageValue, StorageMap, EnumerableStorageMap, dispatch::Result};
use srml_support::{decl_module, decl_event, decl_storage, ensure};
use srml_support::traits::{Currency, OnDilution, EnsureAccountLiquid, OnFreeBalanceZero, ArithmeticType};
use srml_support::traits::{
Currency, OnDilution, EnsureAccountLiquid, OnFreeBalanceZero, WithdrawReason, ArithmeticType
};
use session::OnSessionChange;
use primitives::Perbill;
use primitives::traits::{Zero, One, Bounded, As, StaticLookup};
use primitives::traits::{Zero, One, As, StaticLookup, Saturating};
use system::ensure_signed;
mod mock;
@@ -37,13 +39,25 @@ mod tests;
const RECENT_OFFLINE_COUNT: usize = 32;
const DEFAULT_MINIMUM_VALIDATOR_COUNT: u32 = 4;
const MAX_NOMINATIONS: usize = 16;
const MAX_UNSTAKE_THRESHOLD: u32 = 10;
#[derive(PartialEq, Clone)]
#[cfg_attr(test, derive(Debug))]
pub enum LockStatus<BlockNumber: Parameter> {
Liquid,
LockedUntil(BlockNumber),
Bonded,
/// A destination account for payment.
#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug))]
pub enum RewardDestination {
/// Pay into the stash account, increasing the amount at stake accordingly.
Staked,
/// Pay into the stash account, not increasing the amount at stake.
Stash,
/// Pay into the controller account.
Controller,
}
impl Default for RewardDestination {
fn default() -> Self {
RewardDestination::Staked
}
}
/// Preference of what happens on a slash event.
@@ -53,7 +67,7 @@ pub struct ValidatorPrefs<Balance: HasCompact> {
/// Validator should ensure this many more slashes than is necessary before being unstaked.
#[codec(compact)]
pub unstake_threshold: u32,
// Reward that validator takes up-front; only the rest is split between themselves and nominators.
/// Reward that validator takes up-front; only the rest is split between themselves and nominators.
#[codec(compact)]
pub validator_payment: Balance,
}
@@ -67,6 +81,79 @@ impl<B: Default + HasCompact + Copy> Default for ValidatorPrefs<B> {
}
}
/// Just a Balance/BlockNumber tuple to encode when a chunk of funds will be unlocked.
#[derive(PartialEq, Eq, Clone, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct UnlockChunk<Balance: HasCompact, BlockNumber: HasCompact> {
/// Amount of funds to be unlocked.
#[codec(compact)]
value: Balance,
/// Era number at which point it'll be unlocked.
#[codec(compact)]
era: BlockNumber,
}
/// The ledger of a (bonded) stash.
#[derive(PartialEq, Eq, Clone, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct StakingLedger<AccountId, Balance: HasCompact, BlockNumber: HasCompact> {
/// The stash account whose balance is actually locked and at stake.
pub stash: AccountId,
/// The total amount of the stash's balance that we are currently accounting for.
/// It's just `active` plus all the `unlocking` balances.
#[codec(compact)]
pub total: Balance,
/// The total amount of the stash's balance that will be at stake in any forthcoming
/// rounds.
#[codec(compact)]
pub active: Balance,
/// Any balance that is becoming free, which may eventually be transferred out
/// of the stash (assuming it doesn't get slashed first).
pub unlocking: Vec<UnlockChunk<Balance, BlockNumber>>,
}
impl<AccountId, Balance: HasCompact + Copy + Saturating, BlockNumber: HasCompact + PartialOrd> StakingLedger<AccountId, Balance, BlockNumber> {
/// Remove entries from `unlocking` that are sufficiently old and reduce the
/// total by the sum of their balances.
fn consolidate_unlocked(self, current_era: BlockNumber) -> Self {
let mut total = self.total;
let unlocking = self.unlocking.into_iter()
.filter(|chunk| if chunk.era > current_era {
true
} else {
total = total.saturating_sub(chunk.value);
false
})
.collect();
Self { total, active: self.active, stash: self.stash, unlocking }
}
}
/// The amount of exposure (to slashing) than an individual nominator has.
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct IndividualExposure<AccountId, Balance: HasCompact> {
/// Which nominator.
who: AccountId,
/// Amount of funds exposed.
#[codec(compact)]
value: Balance,
}
/// A snapshot of the stake backing a single validator in the system.
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct Exposure<AccountId, Balance: HasCompact> {
/// The total balance backing this validator.
#[codec(compact)]
pub total: Balance,
/// The validator's own stash that is exposed.
#[codec(compact)]
pub own: Balance,
/// The portions of nominators stashes that are exposed.
pub others: Vec<IndividualExposure<AccountId, Balance>>,
}
type BalanceOf<T> = <<T as Trait>::Currency as ArithmeticType>::Type;
pub trait Trait: system::Trait + session::Trait {
@@ -80,100 +167,268 @@ pub trait Trait: system::Trait + session::Trait {
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}
decl_storage! {
trait Store for Module<T: Trait> as Staking {
/// The ideal number of staking participants.
pub ValidatorCount get(validator_count) config(): u32;
/// Minimum number of staking participants before emergency conditions are imposed.
pub MinimumValidatorCount get(minimum_validator_count) config(): u32 = DEFAULT_MINIMUM_VALIDATOR_COUNT;
/// The length of a staking era in sessions.
pub SessionsPerEra get(sessions_per_era) config(): T::BlockNumber = T::BlockNumber::sa(1000);
/// Maximum reward, per validator, that is provided per acceptable session.
pub SessionReward get(session_reward) config(): Perbill = Perbill::from_billionths(60);
/// Slash, per validator that is taken for the first time they are found to be offline.
pub OfflineSlash get(offline_slash) config(): Perbill = Perbill::from_millionths(1000); // Perbill::from_fraction() is only for std, so use from_millionths().
/// Number of instances of offline reports before slashing begins for validators.
pub OfflineSlashGrace get(offline_slash_grace) config(): u32;
/// The length of the bonding duration in blocks.
pub BondingDuration get(bonding_duration) config(): T::BlockNumber = T::BlockNumber::sa(1000);
// TODO: remove once Alex/CC updated #1785
pub Invulerables get(invulerables): Vec<T::AccountId>;
/// Any validators that may never be slashed or forcibly kicked. It's a Vec since they're easy to initialise
/// and the performance hit is minimal (we expect no more than four invulnerables) and restricted to testnets.
pub Invulnerables get(invulnerables) config(): Vec<T::AccountId>;
/// Map from all locked "stash" accounts to the controller account.
pub Bonded get(bonded) build(|config: &GenesisConfig<T>| {
config.stakers.iter().map(|(stash, controller, _)| (stash.clone(), controller.clone())).collect::<Vec<_>>()
}): map T::AccountId => Option<T::AccountId>;
/// Map from all (unlocked) "controller" accounts to the info regarding the staking.
pub Ledger get(ledger) build(|config: &GenesisConfig<T>| {
config.stakers.iter().map(|(stash, controller, value)| (
controller.clone(),
StakingLedger {
stash: stash.clone(),
total: *value,
active: *value,
unlocking: Vec::<UnlockChunk<BalanceOf<T>, T::BlockNumber>>::new(),
},
)).collect::<Vec<_>>()
}): map T::AccountId => Option<StakingLedger<T::AccountId, BalanceOf<T>, T::BlockNumber>>;
/// Where the reward payment should be made.
pub Payee get(payee): map T::AccountId => RewardDestination;
/// The set of keys are all controllers that want to validate.
///
/// The values are the preferences that a validator has.
pub Validators get(validators) build(|config: &GenesisConfig<T>| {
config.stakers.iter().map(|(_stash, controller, _value)| (
controller.clone(),
ValidatorPrefs::<BalanceOf<T>>::default(),
)).collect::<Vec<_>>()
}): linked_map T::AccountId => ValidatorPrefs<BalanceOf<T>>;
/// The set of keys are all controllers that want to nominate.
///
/// The value are the nominations.
pub Nominators get(nominators): linked_map T::AccountId => Vec<T::AccountId>;
/// Nominators for a particular account that is in action right now. You can't iterate through validators here,
/// but you can find them in the `sessions` module.
pub Stakers get(stakers) build(|config: &GenesisConfig<T>| {
config.stakers.iter().map(|(_stash, controller, value)| (
controller.clone(),
Exposure {
total: *value,
own: *value,
others: Vec::<IndividualExposure<T::AccountId, _>>::new(),
},
)).collect::<Vec<_>>()
}): map T::AccountId => Exposure<T::AccountId, BalanceOf<T>>;
// The historical validators and their nominations for a given era. Stored as a trie root of the mapping
// `T::AccountId` => `Exposure<T::AccountId, BalanceOf<T>>`, which is just the contents of `Stakers`,
// under a key that is the `era`.
//
// Every era change, this will be appended with the trie root of the contents of `Stakers`, and the oldest
// entry removed down to a specific number of entries (probably around 90 for a 3 month history).
// pub HistoricalStakers get(historical_stakers): map T::BlockNumber => Option<H256>;
/// The current era index.
pub CurrentEra get(current_era) config(): T::BlockNumber;
/// Maximum reward, per validator, that is provided per acceptable session.
pub CurrentSessionReward get(current_session_reward) config(): BalanceOf<T>;
/// Slash, per validator that is taken for the first time they are found to be offline.
pub CurrentOfflineSlash get(current_offline_slash) config(): BalanceOf<T>;
/// The accumulated reward for the current era. Reset to zero at the beginning of the era and
/// increased for every successfully finished session.
pub CurrentEraReward get(current_era_reward): BalanceOf<T>;
/// The next value of sessions per era.
pub NextSessionsPerEra get(next_sessions_per_era): Option<T::BlockNumber>;
/// The session index at which the era length last changed.
pub LastEraLengthChange get(last_era_length_change): T::BlockNumber;
/// The amount of balance actively at stake for each validator slot, currently.
///
/// This is used to derive rewards and punishments.
pub SlotStake get(slot_stake) build(|config: &GenesisConfig<T>| {
config.stakers.iter().map(|&(_, _, value)| value).min().unwrap_or_default()
}): BalanceOf<T>;
/// The number of times a given validator has been reported offline. This gets decremented by one each era that passes.
pub SlashCount get(slash_count): map T::AccountId => u32;
/// We are forcing a new era.
pub ForcingNewEra get(forcing_new_era): Option<()>;
/// Most recent `RECENT_OFFLINE_COUNT` instances. (who it was, when it was reported, how many instances they were offline for).
pub RecentlyOffline get(recently_offline): Vec<(T::AccountId, T::BlockNumber, u32)>;
}
add_extra_genesis {
config(stakers): Vec<(T::AccountId, T::AccountId, BalanceOf<T>)>;
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
fn deposit_event<T>() = default;
/// Declare the desire to stake for the transactor.
/// Take the origin account as a stash and lock up `value` of its balance. `controller` will be the
/// account that controls it.
fn bond(origin, controller: <T::Lookup as StaticLookup>::Source, #[compact] value: BalanceOf<T>, payee: RewardDestination) {
let stash = ensure_signed(origin)?;
if <Bonded<T>>::exists(&stash) {
return Err("stash already bonded")
}
let controller = T::Lookup::lookup(controller)?;
// You're auto-bonded forever, here. We might improve this by only bonding when
// you actually validate/nominate.
<Bonded<T>>::insert(&stash, controller.clone());
let stash_balance = T::Currency::free_balance(&stash);
let value = value.min(stash_balance);
<Ledger<T>>::insert(&controller, StakingLedger { stash, total: value, active: value, unlocking: vec![] });
<Payee<T>>::insert(&controller, payee);
}
/// Add some extra amount that have appeared in the stash `free_balance` into the balance up for
/// staking.
///
/// Use this if there are additional funds in your stash account that you wish to bond.
///
/// NOTE: This call must be made by the controller, not the stash.
fn bond_extra(origin, max_additional: BalanceOf<T>) {
let controller = ensure_signed(origin)?;
let mut ledger = Self::ledger(&controller).ok_or("not a controller")?;
let stash_balance = T::Currency::free_balance(&ledger.stash);
if stash_balance > ledger.total {
let extra = (stash_balance - ledger.total).min(max_additional);
ledger.total += extra;
ledger.active += extra;
<Ledger<T>>::insert(&controller, ledger);
}
}
/// Schedule a portion of the stash to be unlocked ready for transfer out after the bond
/// period ends. If this leaves an amount actively bonded less than
/// T::Currency::existential_deposit(), then it is increased to the full amount.
///
/// Once the unlock period is done, you can call `withdraw_unbonded` to actually move
/// the funds out of management ready for transfer.
///
/// NOTE: This call must be made by the controller, not the stash.
///
/// See also `withdraw_unbonded`.
fn unbond(origin, #[compact] value: BalanceOf<T>) {
let controller = ensure_signed(origin)?;
let mut ledger = Self::ledger(&controller).ok_or("not a controller")?;
let mut value = value.min(ledger.active);
if !value.is_zero() {
ledger.active -= value;
// Avoid there being a dust balance left in the staking system.
let ed = T::Currency::minimum_balance();
if ledger.active < ed {
value += ledger.active;
ledger.active = Zero::zero();
}
let era = Self::current_era() + Self::bonding_duration();
ledger.unlocking.push(UnlockChunk { value, era });
<Ledger<T>>::insert(&controller, ledger);
}
}
/// Remove any unlocked chunks from the `unlocking` queue from our management.
///
/// This essentially frees up that balance to be used by the stash account to do
/// whatever it wants.
///
/// NOTE: This call must be made by the controller, not the stash.
///
/// See also `unbond`.
fn withdraw_unbonded(origin) {
let controller = ensure_signed(origin)?;
let ledger = Self::ledger(&controller).ok_or("not a controller")?;
<Ledger<T>>::insert(&controller, ledger.consolidate_unlocked(Self::current_era()));
}
/// Declare the desire to validate for the origin controller.
///
/// Effects will be felt at the beginning of the next era.
fn stake(origin) {
let who = ensure_signed(origin)?;
ensure!(Self::nominating(&who).is_none(), "Cannot stake if already nominating.");
let mut intentions = <Intentions<T>>::get();
// can't be in the list twice.
ensure!(intentions.iter().find(|&t| t == &who).is_none(), "Cannot stake if already staked.");
<Bondage<T>>::insert(&who, T::BlockNumber::max_value());
intentions.push(who);
<Intentions<T>>::put(intentions);
///
/// NOTE: This call must be made by the controller, not the stash.
fn validate(origin, prefs: ValidatorPrefs<BalanceOf<T>>) {
let controller = ensure_signed(origin)?;
let _ledger = Self::ledger(&controller).ok_or("not a controller")?;
ensure!(prefs.unstake_threshold <= MAX_UNSTAKE_THRESHOLD, "unstake threshold too large");
<Nominators<T>>::remove(&controller);
<Validators<T>>::insert(controller, prefs);
}
/// Retract the desire to stake for the transactor.
/// Declare the desire to nominate `targets` for the origin controller.
///
/// Effects will be felt at the beginning of the next era.
fn unstake(origin, #[compact] intentions_index: u32) -> Result {
let who = ensure_signed(origin)?;
// unstake fails in degenerate case of having too few existing staked parties
if Self::intentions().len() <= Self::minimum_validator_count() as usize {
return Err("cannot unstake when there are too few staked participants")
}
Self::apply_unstake(&who, intentions_index as usize)
///
/// NOTE: This call must be made by the controller, not the stash.
fn nominate(origin, targets: Vec<<T::Lookup as StaticLookup>::Source>) {
let controller = ensure_signed(origin)?;
let _ledger = Self::ledger(&controller).ok_or("not a controller")?;
ensure!(!targets.is_empty(), "targets cannot be empty");
let targets = targets.into_iter()
.take(MAX_NOMINATIONS)
.map(T::Lookup::lookup)
.collect::<result::Result<Vec<T::AccountId>, &'static str>>()?;
<Validators<T>>::remove(&controller);
<Nominators<T>>::insert(controller, targets);
}
fn nominate(origin, target: <T::Lookup as StaticLookup>::Source) {
let who = ensure_signed(origin)?;
let target = T::Lookup::lookup(target)?;
ensure!(Self::nominating(&who).is_none(), "Cannot nominate if already nominating.");
ensure!(Self::intentions().iter().find(|&t| t == &who).is_none(), "Cannot nominate if already staked.");
// update nominators_for
let mut t = Self::nominators_for(&target);
t.push(who.clone());
<NominatorsFor<T>>::insert(&target, t);
// update nominating
<Nominating<T>>::insert(&who, &target);
// Update bondage
<Bondage<T>>::insert(&who, T::BlockNumber::max_value());
}
/// Will panic if called when source isn't currently nominating target.
/// Updates Nominating, NominatorsFor and NominationBalance.
fn unnominate(origin, #[compact] target_index: u32) {
let source = ensure_signed(origin)?;
let target_index = target_index as usize;
let target = <Nominating<T>>::get(&source).ok_or("Account must be nominating")?;
let mut t = Self::nominators_for(&target);
if t.get(target_index) != Some(&source) {
return Err("Invalid target index")
}
// Ok - all valid.
// update nominators_for
t.swap_remove(target_index);
<NominatorsFor<T>>::insert(&target, t);
// update nominating
<Nominating<T>>::remove(&source);
// update bondage
<Bondage<T>>::insert(
source,
<system::Module<T>>::block_number() + Self::bonding_duration()
);
}
/// Set the given account's preference for slashing behaviour should they be a validator.
/// Declare no desire to either validate or nominate.
///
/// An error (no-op) if `Self::intentions()[intentions_index] != origin`.
fn register_preferences(
origin,
#[compact] intentions_index: u32,
prefs: ValidatorPrefs<BalanceOf<T>>
) {
let who = ensure_signed(origin)?;
/// Effects will be felt at the beginning of the next era.
///
/// NOTE: This call must be made by the controller, not the stash.
fn chill(origin) {
let controller = ensure_signed(origin)?;
let _ledger = Self::ledger(&controller).ok_or("not a controller")?;
<Validators<T>>::remove(&controller);
<Nominators<T>>::remove(&controller);
}
if Self::intentions().get(intentions_index as usize) != Some(&who) {
return Err("Invalid index")
}
<ValidatorPreferences<T>>::insert(who, prefs);
/// (Re-)set the payment target for a controller.
///
/// Effects will be felt at the beginning of the next era.
///
/// NOTE: This call must be made by the controller, not the stash.
fn set_payee(origin, payee: RewardDestination) {
let controller = ensure_signed(origin)?;
let _ledger = Self::ledger(&controller).ok_or("not a controller")?;
<Payee<T>>::insert(&controller, payee);
}
/// Set the number of sessions in an era.
@@ -204,7 +459,7 @@ decl_module! {
/// Set the validators who cannot be slashed (if any).
fn set_invulnerables(validators: Vec<T::AccountId>) {
<Invulerables<T>>::put(validators);
<Invulnerables<T>>::put(validators);
}
}
}
@@ -222,69 +477,6 @@ decl_event!(
}
);
pub type PairOf<T> = (T, T);
decl_storage! {
trait Store for Module<T: Trait> as Staking {
/// The ideal number of staking participants.
pub ValidatorCount get(validator_count) config(): u32;
/// Minimum number of staking participants before emergency conditions are imposed.
pub MinimumValidatorCount get(minimum_validator_count) config(): u32 = DEFAULT_MINIMUM_VALIDATOR_COUNT;
/// The length of a staking era in sessions.
pub SessionsPerEra get(sessions_per_era) config(): T::BlockNumber = T::BlockNumber::sa(1000);
/// Maximum reward, per validator, that is provided per acceptable session.
pub SessionReward get(session_reward) config(): Perbill = Perbill::from_billionths(60);
/// Slash, per validator that is taken for the first time they are found to be offline.
pub OfflineSlash get(offline_slash) config(): Perbill = Perbill::from_millionths(1000); // Perbill::from_fraction() is only for std, so use from_millionths().
/// Number of instances of offline reports before slashing begins for validators.
pub OfflineSlashGrace get(offline_slash_grace) config(): u32;
/// The length of the bonding duration in blocks.
pub BondingDuration get(bonding_duration) config(): T::BlockNumber = T::BlockNumber::sa(1000);
/// Any validators that may never be slashed or forcible kicked. It's a Vec since they're easy to initialise
/// and the performance hit is minimal (we expect no more than four invulnerables) and restricted to testnets.
pub Invulerables get(invulnerables) config(): Vec<T::AccountId>;
/// The current era index.
pub CurrentEra get(current_era) config(): T::BlockNumber;
/// Preferences that a validator has.
pub ValidatorPreferences get(validator_preferences): map T::AccountId => ValidatorPrefs<BalanceOf<T>>;
/// All the accounts with a desire to stake.
pub Intentions get(intentions) config(): Vec<T::AccountId>;
/// All nominator -> nominee relationships.
pub Nominating get(nominating): map T::AccountId => Option<T::AccountId>;
/// Nominators for a particular account.
pub NominatorsFor get(nominators_for): map T::AccountId => Vec<T::AccountId>;
/// Nominators for a particular account that is in action right now.
pub CurrentNominatorsFor get(current_nominators_for): map T::AccountId => Vec<T::AccountId>;
/// Maximum reward, per validator, that is provided per acceptable session.
pub CurrentSessionReward get(current_session_reward) config(): BalanceOf<T>;
/// Slash, per validator that is taken for the first time they are found to be offline.
pub CurrentOfflineSlash get(current_offline_slash) config(): BalanceOf<T>;
/// The next value of sessions per era.
pub NextSessionsPerEra get(next_sessions_per_era): Option<T::BlockNumber>;
/// The session index at which the era length last changed.
pub LastEraLengthChange get(last_era_length_change): T::BlockNumber;
/// The highest and lowest staked validator slashable balances.
pub StakeRange get(stake_range): PairOf<BalanceOf<T>>;
/// The block at which the `who`'s funds become entirely liquid.
pub Bondage get(bondage): map T::AccountId => T::BlockNumber;
/// The number of times a given validator has been reported offline. This gets decremented by one each era that passes.
pub SlashCount get(slash_count): map T::AccountId => u32;
/// We are forcing a new era.
pub ForcingNewEra get(forcing_new_era): Option<()>;
/// Most recent `RECENT_OFFLINE_COUNT` instances. (who it was, when it was reported, how many instances they were offline for).
pub RecentlyOffline get(recently_offline): Vec<(T::AccountId, T::BlockNumber, u32)>;
}
}
impl<T: Trait> Module<T> {
// Just force_new_era without origin check.
fn apply_force_new_era(apply_rewards: bool) -> Result {
@@ -299,92 +491,83 @@ impl<T: Trait> Module<T> {
Self::sessions_per_era() * <session::Module<T>>::length()
}
/// Balance of a (potential) validator that includes all nominators.
pub fn nomination_balance(who: &T::AccountId) -> BalanceOf<T> {
Self::nominators_for(who).iter()
.map(T::Currency::total_balance)
.fold(Zero::zero(), |acc, x| acc + x)
/// The stashed funds whose staking activities are controlled by `controller` and
/// which are actively in stake right now.
pub fn stash_balance(controller: &T::AccountId) -> BalanceOf<T> {
Self::ledger(controller)
.map_or_else(Zero::zero, |l| l.active)
}
/// The total balance that can be slashed from an account.
/// The total balance that can be slashed from a validator controller account as of
/// right now.
pub fn slashable_balance(who: &T::AccountId) -> BalanceOf<T> {
Self::nominators_for(who).iter()
.map(T::Currency::total_balance)
.fold(T::Currency::total_balance(who), |acc, x| acc + x)
}
/// The block at which the `who`'s funds become entirely liquid.
pub fn unlock_block(who: &T::AccountId) -> LockStatus<T::BlockNumber> {
match Self::bondage(who) {
i if i == T::BlockNumber::max_value() => LockStatus::Bonded,
i if i <= <system::Module<T>>::block_number() => LockStatus::Liquid,
i => LockStatus::LockedUntil(i),
}
}
/// Get the current validators.
pub fn validators() -> Vec<T::AccountId> {
session::Module::<T>::validators()
Self::stakers(who).total
}
// PUBLIC MUTABLES (DANGEROUS)
/// Slash a given validator by a specific amount. Removes the slash from their balance by preference,
/// and reduces the nominators' balance if needed.
fn slash_validator(v: &T::AccountId, slash: BalanceOf<T>) {
// skip the slash in degenerate case of having only 4 staking participants despite having a larger
// desired number of validators (validator_count).
if Self::intentions().len() <= Self::minimum_validator_count() as usize {
return
}
// The exposure (backing stake) information of the validator to be slashed.
let exposure = Self::stakers(v);
// The amount we are actually going to slash (can't be bigger than thair total exposure)
let slash = slash.min(exposure.total);
// The amount we'll slash from the validator's stash directly.
let own_slash = exposure.own.min(slash);
let own_slash = own_slash - T::Currency::slash(v, own_slash).unwrap_or_default();
// The amount remaining that we can't slash from the validator, that must be taken from the nominators.
let rest_slash = slash - own_slash;
if let Some(rem) = T::Currency::slash(v, slash) {
let noms = Self::current_nominators_for(v);
let total = noms.iter().map(T::Currency::total_balance).fold(BalanceOf::<T>::zero(), |acc, x| acc + x);
if !rest_slash.is_zero() {
// The total to be slashed from the nominators.
let total = exposure.total - exposure.own;
if !total.is_zero() {
let safe_mul_rational = |b| b * rem / total;// FIXME #1572 avoid overflow
for n in noms.iter() {
let _ = T::Currency::slash(n, safe_mul_rational(T::Currency::total_balance(n))); // best effort - not much that can be done on fail.
let safe_mul_rational = |b| b * rest_slash / total;// FIXME #1572 avoid overflow
for i in exposure.others.iter() {
let _ = T::Currency::slash(&i.who, safe_mul_rational(i.value)); // best effort - not much that can be done on fail.
}
}
}
}
/// Actually make a payment to a staker. This uses the currency's reward function
/// to pay the right payee for the given staker account.
fn make_payout(who: &T::AccountId, amount: BalanceOf<T>) {
match Self::payee(who) {
RewardDestination::Controller => {
let _ = T::Currency::reward(&who, amount);
}
RewardDestination::Stash => {
let _ = Self::ledger(who).map(|l| T::Currency::reward(&l.stash, amount));
}
RewardDestination::Staked => <Ledger<T>>::mutate(who, |ml| {
if let Some(l) = ml.as_mut() {
l.active += amount;
l.total += amount;
let _ = T::Currency::reward(&l.stash, amount);
}
}),
}
}
/// Reward a given validator by a specific amount. Add the reward to their, and their nominators'
/// balance, pro-rata.
/// balance, pro-rata based on their exposure, after having removed the validator's pre-payout cut.
fn reward_validator(who: &T::AccountId, reward: BalanceOf<T>) {
let off_the_table = reward.min(Self::validator_preferences(who).validator_payment);
let off_the_table = reward.min(Self::validators(who).validator_payment);
let reward = reward - off_the_table;
let validator_cut = if reward.is_zero() {
Zero::zero()
} else {
let noms = Self::current_nominators_for(who);
let total = noms.iter()
.map(T::Currency::total_balance)
.fold(T::Currency::total_balance(who), |acc, x| acc + x)
.max(One::one());
let exposure = Self::stakers(who);
let total = exposure.total.max(One::one());
let safe_mul_rational = |b| b * reward / total;// FIXME #1572: avoid overflow
for n in noms.iter() {
let _ = T::Currency::reward(n, safe_mul_rational(T::Currency::total_balance(n)));
for i in &exposure.others {
Self::make_payout(&i.who, safe_mul_rational(i.value));
}
safe_mul_rational(T::Currency::total_balance(who))
safe_mul_rational(exposure.own)
};
let _ = T::Currency::reward(who, validator_cut + off_the_table);
}
/// Actually carry out the unstake operation.
/// Assumes `intentions()[intentions_index] == who`.
fn apply_unstake(who: &T::AccountId, intentions_index: usize) -> Result {
let mut intentions = Self::intentions();
if intentions.get(intentions_index) != Some(who) {
return Err("Invalid index");
}
intentions.swap_remove(intentions_index);
<Intentions<T>>::put(intentions);
<ValidatorPreferences<T>>::remove(who);
<SlashCount<T>>::remove(who);
<Bondage<T>>::insert(who, <system::Module<T>>::block_number() + Self::bonding_duration());
Ok(())
Self::make_payout(who, validator_cut + off_the_table);
}
/// Get the reward for the session, assuming it ends with this block.
@@ -394,23 +577,16 @@ impl<T: Trait> Module<T> {
return Self::current_session_reward();
}
let per65536: u64 = (T::Moment::sa(65536u64) * ideal_elapsed.clone() / actual_elapsed.max(ideal_elapsed)).as_();
Self::current_session_reward() * BalanceOf::<T>::sa(per65536) / BalanceOf::<T>::sa(65536u64)
Self::current_session_reward() * <BalanceOf<T>>::sa(per65536) / <BalanceOf<T>>::sa(65536u64)
}
/// Session has just changed. We need to determine whether we pay a reward, slash and/or
/// move to a new era.
fn new_session(actual_elapsed: T::Moment, should_reward: bool) {
if should_reward {
// apply good session reward
// accumulate good session reward
let reward = Self::this_session_reward(actual_elapsed);
let validators = <session::Module<T>>::validators();
for v in validators.iter() {
Self::reward_validator(v, reward);
}
Self::deposit_event(RawEvent::Reward(reward));
let total_minted = reward * <BalanceOf<T> as As<usize>>::sa(validators.len());
let total_rewarded_stake = Self::stake_range().1 * <BalanceOf<T> as As<usize>>::sa(validators.len());
T::OnRewardMinted::on_dilution(total_minted, total_rewarded_stake);
<CurrentEraReward<T>>::mutate(|r| *r += reward);
}
let session_index = <session::Module<T>>::current_index();
@@ -426,6 +602,19 @@ impl<T: Trait> Module<T> {
/// NOTE: This always happens immediately before a session change to ensure that new validators
/// get a chance to set their session keys.
fn new_era() {
// Payout
let reward = <CurrentEraReward<T>>::take();
if !reward.is_zero() {
let validators = <session::Module<T>>::validators();
for v in validators.iter() {
Self::reward_validator(v, reward);
}
Self::deposit_event(RawEvent::Reward(reward));
let total_minted = reward * <BalanceOf<T> as As<usize>>::sa(validators.len());
let total_rewarded_stake = Self::slot_stake() * <BalanceOf<T> as As<usize>>::sa(validators.len());
T::OnRewardMinted::on_dilution(total_minted, total_rewarded_stake);
}
// Increment current era.
<CurrentEra<T>>::put(&(<CurrentEra<T>>::get() + One::one()));
@@ -437,63 +626,99 @@ impl<T: Trait> Module<T> {
}
}
// evaluate desired staking amounts and nominations and optimise to find the best
// combination of validators, then use session::internal::set_validators().
// for now, this just orders would-be stakers by their balances and chooses the top-most
// <ValidatorCount<T>>::get() of them.
// FIXME #1571 this is not sound. this should be moved to an off-chain solution mechanism.
let mut intentions = Self::intentions()
.into_iter()
.map(|v| (Self::slashable_balance(&v), v))
.collect::<Vec<_>>();
// Reassign all Stakers.
// Avoid reevaluate validator set if it would leave us with fewer than the minimum
// needed validators
if intentions.len() < Self::minimum_validator_count() as usize {
return
// Map of (would-be) validator account to amount of stake backing it.
// First, we pull all validators, together with their stash balance into a Vec (cpu=O(V), mem=O(V))
let mut candidates = <Validators<T>>::enumerate()
.map(|(who, _)| {
let stash_balance = Self::stash_balance(&who);
(who, Exposure { total: stash_balance, own: stash_balance, others: vec![] })
})
.collect::<Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)>>();
// Second, we sort by accountid (cpu=O(V.log(V)))
candidates.sort_unstable_by_key(|i| i.0.clone());
// Third, iterate through nominators and add their balance to the first validator in their approval
// list. cpu=O(N.log(V))
for (who, nominees) in <Nominators<T>>::enumerate() {
// For this trivial nominator mapping, we just assume that nominators always
// have themselves assigned to the first validator in their list.
if nominees.is_empty() {
// Not possible, but we protect against it anyway.
continue;
}
if let Ok(index) = candidates.binary_search_by(|i| i.0.cmp(&nominees[0])) {
let stash_balance = Self::stash_balance(&who);
candidates[index].1.total += stash_balance;
candidates[index].1.others.push(IndividualExposure { who, value: stash_balance });
}
}
intentions.sort_unstable_by(|&(ref b1, _), &(ref b2, _)| b2.cmp(&b1));
let desired_validator_count = <ValidatorCount<T>>::get() as usize;
let stake_range = if !intentions.is_empty() {
let n = cmp::min(desired_validator_count, intentions.len());
(intentions[0].0, intentions[n - 1].0)
// Get the new staker set by sorting by total backing stake and truncating.
// cpu=O(V.log(s)) average, O(V.s) worst.
let count = Self::validator_count() as usize;
let candidates = if candidates.len() <= count {
candidates
} else {
(Zero::zero(), Zero::zero())
candidates.into_iter().fold(vec![], |mut winners: Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)>, entry| {
if let Err(insert_point) = winners.binary_search_by_key(&entry.1.total, |i| i.1.total) {
if winners.len() < count {
winners.insert(insert_point, entry)
} else {
if insert_point > 0 {
// Big enough to be considered: insert at beginning and swap up to relevant point.
winners[0] = entry;
for i in 0..(insert_point - 1) {
winners.swap(i, i + 1)
}
}
}
}
winners
})
};
<StakeRange<T>>::put(&stake_range);
let vals = &intentions.into_iter()
.map(|(_, v)| v)
.take(desired_validator_count)
.collect::<Vec<_>>();
// Clear Stakers and reduce their slash_count.
for v in <session::Module<T>>::validators().iter() {
<CurrentNominatorsFor<T>>::remove(v);
<Stakers<T>>::remove(v);
let slash_count = <SlashCount<T>>::take(v);
if slash_count > 1 {
<SlashCount<T>>::insert(v, slash_count - 1);
}
}
for v in vals.iter() {
<CurrentNominatorsFor<T>>::insert(v, Self::nominators_for(v));
// Figure out the minimum stake behind a slot.
let slot_stake = candidates.last().map(|i| i.1.total).unwrap_or_default();
<SlotStake<T>>::put(&slot_stake);
// Populate Stakers.
for (who, exposure) in &candidates {
<Stakers<T>>::insert(who, exposure);
}
<session::Module<T>>::set_validators(vals);
// Set the new validator set.
<session::Module<T>>::set_validators(
&candidates.into_iter().map(|i| i.0).collect::<Vec<_>>()
);
// Update the balances for slashing/rewarding according to the stakes.
<CurrentOfflineSlash<T>>::put(Self::offline_slash() * stake_range.1);
<CurrentSessionReward<T>>::put(Self::session_reward() * stake_range.1);
<CurrentOfflineSlash<T>>::put(Self::offline_slash() * slot_stake);
<CurrentSessionReward<T>>::put(Self::session_reward() * slot_stake);
}
/// Call when a validator is determined to be offline. `count` is the
/// number of offences the validator has committed.
pub fn on_offline_validator(v: T::AccountId, count: usize) {
use primitives::traits::{CheckedAdd, CheckedShl};
use primitives::traits::CheckedShl;
// Early exit if validator is invulnerable.
if Self::invulnerables().contains(&v) {
return
}
// TODO: remove once Alex/CC updated #1785
if Self::invulerables().contains(&v) {
return
}
let slash_count = Self::slash_count(&v);
let new_slash_count = slash_count + count as u32;
@@ -514,50 +739,22 @@ impl<T: Trait> Module<T> {
});
}
let event = if new_slash_count > grace {
let slash = {
let base_slash = Self::current_offline_slash();
let instances = slash_count - grace;
let mut total_slash = BalanceOf::<T>::default();
for i in instances..(instances + count as u32) {
if let Some(total) = base_slash.checked_shl(i)
.and_then(|slash| total_slash.checked_add(&slash)) {
total_slash = total;
} else {
// reset slash count only up to the current
// instance. the total slash overflows the unit for
// balance in the system therefore we can slash all
// the slashable balance for the account
<SlashCount<T>>::insert(v.clone(), slash_count + i);
total_slash = Self::slashable_balance(&v);
break;
}
}
total_slash
};
let prefs = Self::validators(&v);
let unstake_threshold = prefs.unstake_threshold.min(MAX_UNSTAKE_THRESHOLD);
let max_slashes = grace + unstake_threshold;
let event = if new_slash_count > max_slashes {
let slot_stake = Self::slot_stake();
// They're bailing.
let slash = Self::current_offline_slash()
// Multiply current_offline_slash by 2^(unstake_threshold with upper bound)
.checked_shl(unstake_threshold)
.map(|x| x.min(slot_stake))
.unwrap_or(slot_stake);
let _ = Self::slash_validator(&v, slash);
let next_slash = match slash.checked_shl(1) {
Some(slash) => slash,
None => Self::slashable_balance(&v),
};
let instances = new_slash_count - grace;
if instances > Self::validator_preferences(&v).unstake_threshold
|| Self::slashable_balance(&v) < next_slash
|| next_slash <= slash
{
if let Some(pos) = Self::intentions().into_iter().position(|x| &x == &v) {
Self::apply_unstake(&v, pos)
.expect("pos derived correctly from Self::intentions(); \
apply_unstake can only fail if pos wrong; \
Self::intentions() doesn't change; qed");
}
let _ = Self::apply_force_new_era(false);
}
<Validators<T>>::remove(&v);
let _ = Self::apply_force_new_era(false);
RawEvent::OfflineSlash(v.clone(), slash)
} else {
RawEvent::OfflineWarning(v.clone(), slash_count)
@@ -573,19 +770,38 @@ impl<T: Trait> OnSessionChange<T::Moment> for Module<T> {
}
}
impl<T: Trait> EnsureAccountLiquid<T::AccountId> for Module<T> {
impl<T: Trait> EnsureAccountLiquid<T::AccountId, BalanceOf<T>> for Module<T> {
fn ensure_account_liquid(who: &T::AccountId) -> Result {
if Self::bondage(who) <= <system::Module<T>>::block_number() {
Ok(())
if <Bonded<T>>::exists(who) {
Err("stash accounts are not liquid")
} else {
Err("cannot transfer illiquid funds")
Ok(())
}
}
fn ensure_account_can_withdraw(
who: &T::AccountId,
amount: BalanceOf<T>,
_reason: WithdrawReason,
) -> Result {
if let Some(controller) = Self::bonded(who) {
let ledger = Self::ledger(&controller).ok_or("stash without controller")?;
let free_balance = T::Currency::free_balance(&who);
ensure!(free_balance.saturating_sub(ledger.total) > amount,
"stash with too much under management");
}
Ok(())
}
}
impl<T: Trait> OnFreeBalanceZero<T::AccountId> for Module<T> {
fn on_free_balance_zero(who: &T::AccountId) {
<Bondage<T>>::remove(who);
if let Some(controller) = <Bonded<T>>::take(who) {
<Ledger<T>>::remove(&controller);
<Payee<T>>::remove(&controller);
<SlashCount<T>>::remove(&controller);
<Validators<T>>::remove(&controller);
<Nominators<T>>::remove(&controller);
}
}
}
+92 -50
View File
@@ -72,61 +72,103 @@ impl Trait for Test {
type Event = ();
}
pub fn new_test_ext(
ext_deposit: u64,
pub struct ExtBuilder {
existential_deposit: u64,
session_length: u64,
sessions_per_era: u64,
current_era: u64,
monied: bool,
reward: u64
) -> runtime_io::TestExternalities<Blake2Hasher> {
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0;
let balance_factor = if ext_deposit > 0 {
256
} else {
1
};
t.extend(consensus::GenesisConfig::<Test>{
code: vec![],
authorities: vec![],
}.build_storage().unwrap().0);
t.extend(session::GenesisConfig::<Test>{
session_length,
validators: vec![10, 20],
}.build_storage().unwrap().0);
t.extend(balances::GenesisConfig::<Test>{
balances: if monied {
if reward > 0 {
vec![(1, 10 * balance_factor), (2, 20 * balance_factor), (3, 30 * balance_factor), (4, 40 * balance_factor), (10, balance_factor), (20, balance_factor)]
} else {
vec![(1, 10 * balance_factor), (2, 20 * balance_factor), (3, 30 * balance_factor), (4, 40 * balance_factor)]
}
reward: u64,
}
impl Default for ExtBuilder {
fn default() -> Self {
Self {
existential_deposit: 0,
session_length: 3,
sessions_per_era: 3,
current_era: 0,
monied: true,
reward: 10,
}
}
}
impl ExtBuilder {
pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
self.existential_deposit = existential_deposit;
self
}
pub fn session_length(mut self, session_length: u64) -> Self {
self.session_length = session_length;
self
}
pub fn sessions_per_era(mut self, sessions_per_era: u64) -> Self {
self.sessions_per_era = sessions_per_era;
self
}
pub fn _current_era(mut self, current_era: u64) -> Self {
self.current_era = current_era;
self
}
pub fn _monied(mut self, monied: bool) -> Self {
self.monied = monied;
self
}
pub fn reward(mut self, reward: u64) -> Self {
self.reward = reward;
self
}
pub fn build(self) -> runtime_io::TestExternalities<Blake2Hasher> {
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0;
let balance_factor = if self.existential_deposit > 0 {
256
} else {
vec![(10, balance_factor), (20, balance_factor)]
},
existential_deposit: ext_deposit,
transfer_fee: 0,
creation_fee: 0,
vesting: vec![],
}.build_storage().unwrap().0);
t.extend(GenesisConfig::<Test>{
sessions_per_era,
current_era,
intentions: vec![10, 20],
validator_count: 2,
minimum_validator_count: 0,
bonding_duration: sessions_per_era * session_length * 3,
session_reward: Perbill::from_millionths((1000000 * reward / balance_factor) as u32),
offline_slash: if monied { Perbill::from_percent(40) } else { Perbill::zero() },
current_session_reward: reward,
current_offline_slash: 20,
offline_slash_grace: 0,
invulnerables: vec![],
}.build_storage().unwrap().0);
t.extend(timestamp::GenesisConfig::<Test>{
period: 5,
}.build_storage().unwrap().0);
runtime_io::TestExternalities::new(t)
1
};
t.extend(consensus::GenesisConfig::<Test>{
code: vec![],
authorities: vec![],
}.build_storage().unwrap().0);
t.extend(session::GenesisConfig::<Test>{
session_length: self.session_length,
validators: vec![10, 20],
keys: vec![],
}.build_storage().unwrap().0);
t.extend(balances::GenesisConfig::<Test>{
balances: if self.monied {
if self.reward > 0 {
vec![(1, 10 * balance_factor), (2, 20 * balance_factor), (3, 300 * balance_factor), (4, 400 * balance_factor), (10, balance_factor), (11, balance_factor * 1000), (20, balance_factor), (21, balance_factor * 2000)]
} else {
vec![(1, 10 * balance_factor), (2, 20 * balance_factor), (3, 300 * balance_factor), (4, 400 * balance_factor)]
}
} else {
vec![(10, balance_factor), (11, balance_factor * 1000), (20, balance_factor), (21, balance_factor * 2000)]
},
existential_deposit: self.existential_deposit,
transfer_fee: 0,
creation_fee: 0,
vesting: vec![],
}.build_storage().unwrap().0);
t.extend(GenesisConfig::<Test>{
sessions_per_era: self.sessions_per_era,
current_era: self.current_era,
stakers: vec![(11, 10, balance_factor * 1000), (21, 20, balance_factor * 2000)],
validator_count: 2,
minimum_validator_count: 0,
bonding_duration: self.sessions_per_era * self.session_length * 3,
session_reward: Perbill::from_millionths((1000000 * self.reward / balance_factor) as u32),
offline_slash: if self.monied { Perbill::from_percent(40) } else { Perbill::zero() },
current_session_reward: self.reward,
current_offline_slash: 20,
offline_slash_grace: 0,
invulnerables: vec![],
}.build_storage().unwrap().0);
t.extend(timestamp::GenesisConfig::<Test>{
period: 5,
}.build_storage().unwrap().0);
t.into()
}
}
pub type System = system::Module<Test>;
File diff suppressed because it is too large Load Diff
+116 -10
View File
@@ -18,7 +18,9 @@
use crate::rstd::result;
use crate::codec::Codec;
use crate::runtime_primitives::traits::{MaybeSerializeDebug, SimpleArithmetic, As};
use crate::runtime_primitives::traits::{
MaybeSerializeDebug, SimpleArithmetic, As
};
/// The account with the given id was killed.
pub trait OnFreeBalanceZero<AccountId> {
@@ -51,23 +53,58 @@ 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.
/// Determinator for whether a given account is able to use its **free** balance.
///
/// By convention, `ensure_account_liquid` overrules `ensure_account_can_withdraw`. If a
/// caller gets `Ok` from the former, then they do not need to call the latter.
///
/// This implies that if you define the latter away from its default of replicating the
/// former, then ensure you also redefine the former to return an `Err` in corresponding
/// situations, otherwise you'll end up giving inconsistent information.
// TODO: Remove in favour of explicit functionality in balances module: #1896
pub trait EnsureAccountLiquid<AccountId, Balance> {
/// Ensures that the account is completely unencumbered. If this is `Ok` then there's no need to
/// check any other items. If it's an `Err`, then you must use one pair of the other items.
fn ensure_account_liquid(who: &AccountId) -> result::Result<(), &'static str>;
/// 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.
///
/// By default this just reflects the results of `ensure_account_liquid`.
///
/// @warning If you redefine this away from the default, ensure that you define
/// `ensure_account_liquid` in accordance.
fn ensure_account_can_withdraw(
who: &AccountId,
_amount: Balance,
_reason: WithdrawReason
) -> result::Result<(), &'static str> {
Self::ensure_account_liquid(who)
}
}
impl<
AccountId,
X: EnsureAccountLiquid<AccountId>,
Y: EnsureAccountLiquid<AccountId>,
> EnsureAccountLiquid<AccountId> for (X, Y) {
Balance: Copy,
X: EnsureAccountLiquid<AccountId, Balance>,
Y: EnsureAccountLiquid<AccountId, Balance>,
> EnsureAccountLiquid<AccountId, Balance> for (X, Y) {
fn ensure_account_liquid(who: &AccountId) -> result::Result<(), &'static str> {
X::ensure_account_liquid(who)?;
Y::ensure_account_liquid(who)
}
fn ensure_account_can_withdraw(
who: &AccountId,
amount: Balance,
reason: WithdrawReason
) -> result::Result<(), &'static str> {
X::ensure_account_can_withdraw(who, amount, reason)?;
Y::ensure_account_can_withdraw(who, amount, reason)
}
}
impl<AccountId> EnsureAccountLiquid<AccountId> for () {
impl<AccountId, Balance> EnsureAccountLiquid<AccountId, Balance> for () {
fn ensure_account_liquid(_who: &AccountId) -> result::Result<(), &'static str> { Ok(()) }
}
@@ -102,7 +139,11 @@ pub trait Currency<AccountId> {
fn can_reserve(who: &AccountId, value: Self::Balance) -> bool;
/// The total amount of stake on the system.
fn total_issuance() -> Self:: Balance;
fn total_issuance() -> Self::Balance;
/// The minimum balance any single account may have. This is equivalent to Balances module's
/// Existential Deposit.
fn minimum_balance() -> Self::Balance;
/// The 'free' balance of a given account.
///
@@ -185,3 +226,68 @@ pub trait Currency<AccountId> {
value: Self::Balance
) -> result::Result<Option<Self::Balance>, &'static str>;
}
/// Charge bytes fee trait
pub trait ChargeBytesFee<AccountId> {
/// Charge fees from `transactor` for an extrinsic (transaction) of encoded length
/// `encoded_len` bytes. Return Ok iff the payment was successful.
fn charge_base_bytes_fee(transactor: &AccountId, encoded_len: usize) -> Result<(), &'static str>;
}
/// Charge fee trait
pub trait ChargeFee<AccountId>: ChargeBytesFee<AccountId> {
/// The type of fee amount.
type Amount;
/// Charge `amount` of fees from `transactor`. Return Ok iff the payment was successful.
fn charge_fee(transactor: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
/// Refund `amount` of previous charged fees from `transactor`. Return Ok iff the refund was successful.
fn refund_fee(transactor: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
}
/// Reason for moving funds out of an account.
#[derive(Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "std", derive(Debug))]
pub enum WithdrawReason {
/// In order to pay for (system) transaction costs.
TransactionPayment,
/// In order to transfer ownership.
Transfer,
/// In order to reserve some funds for a later return or repatriation
Reserve,
}
/// Transfer fungible asset trait
pub trait TransferAsset<AccountId> {
/// The type of asset amount.
type Amount;
/// Transfer asset from `from` account to `to` account with `amount` of asset.
fn transfer(from: &AccountId, to: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
/// Remove asset from `who` account by deducting `amount` in the account balances.
fn withdraw(who: &AccountId, amount: Self::Amount, reason: WithdrawReason) -> Result<(), &'static str>;
/// Add asset to `who` account by increasing `amount` in the account balances.
fn deposit(who: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
}
impl<T> ChargeBytesFee<T> for () {
fn charge_base_bytes_fee(_: &T, _: usize) -> Result<(), &'static str> { Ok(()) }
}
impl<T> ChargeFee<T> for () {
type Amount = ();
fn charge_fee(_: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
fn refund_fee(_: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
}
impl<T> TransferAsset<T> for () {
type Amount = ();
fn transfer(_: &T, _: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
fn withdraw(_: &T, _: Self::Amount, _: WithdrawReason) -> Result<(), &'static str> { Ok(()) }
fn deposit(_: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
}