diff --git a/Cargo.lock b/Cargo.lock index fc43b8edd..2b923ed96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -863,8 +863,6 @@ name = "darwinia-kton" version = "0.1.0" dependencies = [ "darwinia-support 0.1.0", - "node-primitives 2.0.0", - "node-runtime 0.2.0", "parity-scale-codec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)", @@ -885,8 +883,6 @@ dependencies = [ "darwinia-balances 2.0.0", "darwinia-kton 0.1.0", "darwinia-support 0.1.0", - "node-primitives 2.0.0", - "node-runtime 0.2.0", "parity-scale-codec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2961,7 +2957,6 @@ dependencies = [ "darwinia-eth-relay 0.2.0", "darwinia-kton 0.1.0", "darwinia-staking 0.2.0", - "darwinia-support 0.1.0", "integer-sqrt 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "node-primitives 2.0.0", "parity-scale-codec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/core/cli/src/lib.rs b/core/cli/src/lib.rs index 412a908f4..69a906723 100644 --- a/core/cli/src/lib.rs +++ b/core/cli/src/lib.rs @@ -1022,7 +1022,7 @@ mod tests { #[test] fn test_node_key_config_input() { fn secret_input(net_config_dir: Option) -> error::Result<()> { - NodeKeyType::variants().into_iter().try_for_each(|t| { + NodeKeyType::variants().iter().try_for_each(|t| { let node_key_type = NodeKeyType::from_str(t).unwrap(); let sk = match node_key_type { NodeKeyType::Ed25519 => ed25519::SecretKey::generate().as_ref().to_vec(), @@ -1050,7 +1050,7 @@ mod tests { #[test] fn test_node_key_config_file() { fn secret_file(net_config_dir: Option) -> error::Result<()> { - NodeKeyType::variants().into_iter().try_for_each(|t| { + NodeKeyType::variants().iter().try_for_each(|t| { let node_key_type = NodeKeyType::from_str(t).unwrap(); let tmp = TempDir::new("alice")?; let file = tmp.path().join(format!("{}_mysecret", t)).to_path_buf(); @@ -1080,7 +1080,7 @@ mod tests { where F: Fn(NodeKeyParams) -> error::Result<()>, { - NodeKeyType::variants().into_iter().try_for_each(|t| { + NodeKeyType::variants().iter().try_for_each(|t| { let node_key_type = NodeKeyType::from_str(t).unwrap(); f(NodeKeyParams { node_key_type, diff --git a/core/merkle-patricia-trie/src/lib.rs b/core/merkle-patricia-trie/src/lib.rs index 991b42ca8..77c841b0a 100644 --- a/core/merkle-patricia-trie/src/lib.rs +++ b/core/merkle-patricia-trie/src/lib.rs @@ -44,10 +44,9 @@ where { let memdb = Rc::new(MemoryDB::new()); let mut trie = MerklePatriciaTrie::new(memdb.clone()); - data.into_iter().for_each(|(key, value)| { - // TODO the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `core::ops::Try`) - trie.insert(key.as_ref().to_vec(), value.as_ref().to_vec()); - }); + for (k, v) in data { + trie.insert(k.as_ref().to_vec(), v.as_ref().to_vec())?; + } trie.root()?; Ok(trie) } diff --git a/core/merkle-patricia-trie/src/tests.rs b/core/merkle-patricia-trie/src/tests.rs index e64c094e9..0b3477c6a 100644 --- a/core/merkle-patricia-trie/src/tests.rs +++ b/core/merkle-patricia-trie/src/tests.rs @@ -1,13 +1,14 @@ #[cfg(test)] mod trie_tests { + use std::rc::Rc; + use hex::FromHex; use rand::Rng; - use std::rc::Rc; + use rlp::{self}; use crate::db::MemoryDB; use crate::proof::Proof; use crate::trie::*; - use rlp::{self, Rlp}; fn assert_root(data: Vec<(&[u8], &[u8])>, hash: &str) { let memdb = Rc::new(MemoryDB::new()); diff --git a/core/merkle-patricia-trie/src/trie.rs b/core/merkle-patricia-trie/src/trie.rs index 01527f2f8..8e359948d 100644 --- a/core/merkle-patricia-trie/src/trie.rs +++ b/core/merkle-patricia-trie/src/trie.rs @@ -765,17 +765,16 @@ impl<'a> Iterator for TrieIterator<'a> { #[cfg(test)] mod tests { - use rand::distributions::Alphanumeric; - use rand::seq::SliceRandom; - use rand::{thread_rng, Rng}; use std::collections::{HashMap, HashSet}; use std::rc::Rc; use ethereum_types; + use rand::distributions::Alphanumeric; + use rand::seq::SliceRandom; + use rand::{thread_rng, Rng}; use super::*; use crate::db::MemoryDB; - use core::borrow::Borrow; #[test] fn test_trie_insert() { @@ -996,7 +995,7 @@ mod tests { #[test] fn iterator_trie() { let memdb = Rc::new(MemoryDB::new()); - let mut root1; + let root1; let mut kv = HashMap::new(); kv.insert(b"test".to_vec(), b"test".to_vec()); kv.insert(b"test1".to_vec(), b"test1".to_vec()); diff --git a/core/sr-eth-primitives/src/header.rs b/core/sr-eth-primitives/src/header.rs index c351cb652..079155baa 100644 --- a/core/sr-eth-primitives/src/header.rs +++ b/core/sr-eth-primitives/src/header.rs @@ -484,7 +484,7 @@ mod tests { fn can_calculate_difficulty_ropsten() { let (header1, header2) = ropsten_sequential_header(); let expected = U256::from_str("f3c49f25").unwrap(); - let mut ethash_params = EthashPartial::ropsten_testnet(); + let ethash_params = EthashPartial::ropsten_testnet(); // ethash_params.set_difficulty_bomb_delays(0xc3500, 5000000); assert_eq!(ethash_params.calculate_difficulty(&header2, &header1), expected); } @@ -493,7 +493,7 @@ mod tests { fn can_calculate_difficulty_production() { let (header1, header2) = sequential_header(); let expected = U256::from_str("92c07e50de0b9").unwrap(); - let mut ethash_params = EthashPartial::production(); + let ethash_params = EthashPartial::production(); assert_eq!(ethash_params.calculate_difficulty(&header2, &header1), expected); } diff --git a/core/sr-eth-primitives/src/receipt.rs b/core/sr-eth-primitives/src/receipt.rs index e34b2b807..14ac81a9a 100644 --- a/core/sr-eth-primitives/src/receipt.rs +++ b/core/sr-eth-primitives/src/receipt.rs @@ -1,14 +1,12 @@ -use super::*; +use codec::{Decode, Encode}; use ethbloom::{Bloom, Input as BloomInput}; +use primitive_types::{H256, U256}; use rlp::*; use rstd::prelude::*; -//use substrate_primitives::RuntimeDebug; - -use codec::{Decode, Encode}; -use primitive_types::{H256, U256}; - use sr_primitives::RuntimeDebug; +use super::*; + #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] pub enum TransactionOutcome { /// Status and state root are unknown under EIP-98 rules. @@ -118,14 +116,13 @@ impl Decodable for Receipt { #[cfg(test)] mod tests { - use super::{Address, LogEntry, Receipt, TransactionOutcome, H256, U128, U256}; - use ethbloom::Bloom; - use hex_literal::*; - use rustc_hex::FromHex; use std::str::FromStr; + use hex_literal::*; use keccak_hasher::KeccakHasher; - use triehash::ordered_trie_root; + use rustc_hex::FromHex; + + use super::*; #[inline] fn construct_receipts( @@ -159,7 +156,7 @@ mod tests { data: vec![], }]; - let r = construct_receipts(None, U256::from(U128::from(21000)), Some(1), log_entries); + let _r = construct_receipts(None, U256::from(U128::from(21000)), Some(1), log_entries); // let rs = &rlp::encode(&r)[..]; // TODO: fix logbloom not match here! // assert_eq!(r.log_bloom, Bloom::from_str( diff --git a/node/runtime/Cargo.toml b/node/runtime/Cargo.toml index 645a32e2a..aa91a6b50 100644 --- a/node/runtime/Cargo.toml +++ b/node/runtime/Cargo.toml @@ -51,7 +51,6 @@ version = { package = "sr-version", git = "https://github.com/darwinia-network/s # darwinia balances = { package = "darwinia-balances", path = '../../srml/balances', default-features = false } -darwinia-support = { path = "../../srml/support", default-features = false } eth-relay = { package = "darwinia-eth-relay", path = "../../srml/eth-relay", default-features = false } kton = { package = "darwinia-kton", path = '../../srml/kton', default-features = false } node-primitives = { path = "../primitives", default-features = false } @@ -108,7 +107,6 @@ std = [ # darwinia "balances/std", - "darwinia-support/std", "eth-relay/std", "kton/std", "node-primitives/std", diff --git a/node/runtime/src/constants.rs b/node/runtime/src/constants.rs index e35cab469..15323bfdd 100644 --- a/node/runtime/src/constants.rs +++ b/node/runtime/src/constants.rs @@ -61,10 +61,10 @@ pub mod time { pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 10 * MINUTES; pub const EPOCH_DURATION_IN_SLOTS: u64 = { - // const SLOT_FILL_RATE: f64 = MILLISECS_PER_BLOCK as f64 / SLOT_DURATION as f64; + const SLOT_FILL_RATE: f64 = MILLISECS_PER_BLOCK as f64 / SLOT_DURATION as f64; // Develop - 5 + 60 // Production // (EPOCH_DURATION_IN_BLOCKS as f64 * SLOT_FILL_RATE) as u64 }; diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 84be8508b..96b91dd98 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -58,7 +58,6 @@ use version::NativeVersion; use version::RuntimeVersion; use constants::{currency::*, time::*}; -use darwinia_support::TimeStamp; use impls::{Author, CurrencyToVoteHandler, LinearWeightToFee, TargetedFeeAdjustment}; // Make the WASM binary available. @@ -350,9 +349,12 @@ impl kton::Trait for Runtime { } parameter_types! { - pub const SessionsPerEra: sr_staking_primitives::SessionIndex = 6; - // about 14 days = 14 * 24 * 60 * 60 - pub const BondingDuration: TimeStamp = 1_209_600; + // Develop + pub const SessionsPerEra: sr_staking_primitives::SessionIndex = 1; + // Production + // pub const SessionsPerEra: sr_staking_primitives::SessionIndex = 6; + // about 14 days = 14 * 24 * 60 * 60 * 1000 + pub const BondingDuration: Moment = 1_209_600_000; // decimal 9 pub const HardCap: Balance = 10_000_000_000 * COIN; // date in Los Angeles*: 11/19/2019, 2:33:20 AM diff --git a/srml/balances/src/tests.rs b/srml/balances/src/tests.rs index 08808420c..6bbe3c914 100644 --- a/srml/balances/src/tests.rs +++ b/srml/balances/src/tests.rs @@ -23,10 +23,9 @@ use support::{ use system::RawOrigin; use transaction_payment::ChargeTransactionPayment; -use darwinia_support::{LockIdentifier, WithdrawReason, WithdrawReasons, WithdrawLock, NormalLock, LockableCurrency}; - use super::*; -use mock::{info_from_weight, Balances, ExtBuilder, Runtime, System, CALL, Timestamp}; +use darwinia_support::{LockIdentifier, LockableCurrency, NormalLock, WithdrawLock, WithdrawReason, WithdrawReasons}; +use mock::{info_from_weight, Balances, ExtBuilder, Runtime, System, Timestamp, CALL}; const ID_1: LockIdentifier = *b"1 "; const ID_2: LockIdentifier = *b"2 "; @@ -41,13 +40,13 @@ fn basic_locking_should_work() { .execute_with(|| { assert_eq!(Balances::free_balance(&1), 10); Balances::set_lock( - ID_1, - &1, + ID_1, + &1, WithdrawLock::Normal(NormalLock { amount: 9, until: u64::max_value(), - }), - WithdrawReasons::all() + }), + WithdrawReasons::all(), ); assert_noop!( >::transfer(&1, &2, 5, AllowDeath), @@ -64,13 +63,13 @@ fn partial_locking_should_work() { .build() .execute_with(|| { Balances::set_lock( - ID_1, - &1, + ID_1, + &1, WithdrawLock::Normal(NormalLock { - amount: 5, + amount: 5, until: u64::max_value(), - }), - WithdrawReasons::all() + }), + WithdrawReasons::all(), ); assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); }); @@ -84,13 +83,13 @@ fn lock_removal_should_work() { .build() .execute_with(|| { Balances::set_lock( - ID_1, - &1, + ID_1, + &1, WithdrawLock::Normal(NormalLock { - amount: u64::max_value(), + amount: u64::max_value(), until: u64::max_value(), - }), - WithdrawReasons::all() + }), + WithdrawReasons::all(), ); Balances::remove_lock(ID_1, &1); assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); @@ -105,22 +104,22 @@ fn lock_replacement_should_work() { .build() .execute_with(|| { Balances::set_lock( - ID_1, + ID_1, &1, WithdrawLock::Normal(NormalLock { - amount: u64::max_value(), + amount: u64::max_value(), until: u64::max_value(), - }), - WithdrawReasons::all() + }), + WithdrawReasons::all(), ); Balances::set_lock( - ID_1, - &1, + ID_1, + &1, WithdrawLock::Normal(NormalLock { - amount: 5, + amount: 5, until: u64::max_value(), }), - WithdrawReasons::all() + WithdrawReasons::all(), ); assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); }); @@ -134,22 +133,22 @@ fn double_locking_should_work() { .build() .execute_with(|| { Balances::set_lock( - ID_1, - &1, + ID_1, + &1, WithdrawLock::Normal(NormalLock { - amount: 5, + amount: 5, until: u64::max_value(), }), - WithdrawReasons::all() + WithdrawReasons::all(), ); Balances::set_lock( - ID_2, - &1, + ID_2, + &1, WithdrawLock::Normal(NormalLock { - amount: 5, + amount: 5, until: u64::max_value(), }), - WithdrawReasons::all() + WithdrawReasons::all(), ); assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); }); @@ -163,31 +162,28 @@ fn combination_locking_should_work() { .build() .execute_with(|| { Balances::set_lock( - ID_1, - &1, + ID_1, + &1, WithdrawLock::Normal(NormalLock { - amount: u64::max_value(), + amount: u64::max_value(), until: 0, }), - WithdrawReasons::all() + WithdrawReasons::all(), ); Balances::set_lock( - ID_2, - &1, + ID_2, + &1, WithdrawLock::Normal(NormalLock { - amount: 0, + amount: 0, until: u64::max_value(), }), - WithdrawReasons::all() + WithdrawReasons::all(), ); Balances::set_lock( - ID_3, - &1, - WithdrawLock::Normal(NormalLock { - amount: 0, - until: 0, - }), - WithdrawReasons::all() + ID_3, + &1, + WithdrawLock::Normal(NormalLock { amount: 0, until: 0 }), + WithdrawReasons::all(), ); assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); }); @@ -202,10 +198,10 @@ fn combination_locking_should_work() { // .build() // .execute_with(|| { // Balances::set_lock( -// ID_1, -// &1, +// ID_1, +// &1, // WithdrawLock::Normal(NormalLock { -// amount: 5, +// amount: 5, // until: u64::max_value(), // }), // WithdrawReasons::all() @@ -225,13 +221,13 @@ fn lock_reasons_should_work() { .build() .execute_with(|| { Balances::set_lock( - ID_1, - &1, + ID_1, + &1, WithdrawLock::Normal(NormalLock { - amount: 10, + amount: 10, until: u64::max_value(), }), - WithdrawReason::Transfer.into() + WithdrawReason::Transfer.into(), ); assert_noop!( >::transfer(&1, &2, 1, AllowDeath), @@ -249,13 +245,13 @@ fn lock_reasons_should_work() { .is_ok()); Balances::set_lock( - ID_1, - &1, + ID_1, + &1, WithdrawLock::Normal(NormalLock { - amount: 10, + amount: 10, until: u64::max_value(), }), - WithdrawReason::Reserve.into() + WithdrawReason::Reserve.into(), ); assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); assert_noop!( @@ -301,13 +297,10 @@ fn lock_block_number_should_work() { .build() .execute_with(|| { Balances::set_lock( - ID_1, - &1, - WithdrawLock::Normal(NormalLock { - amount: 10, - until: 2, - }), - WithdrawReasons::all() + ID_1, + &1, + WithdrawLock::Normal(NormalLock { amount: 10, until: 2 }), + WithdrawReasons::all(), ); assert_noop!( >::transfer(&1, &2, 1, AllowDeath), @@ -328,10 +321,10 @@ fn lock_block_number_should_work() { // .build() // .execute_with(|| { // Balances::set_lock( -// ID_1, -// &1, +// ID_1, +// &1, // WithdrawLock::Normal(NormalLock { -// amount: 10, +// amount: 10, // until: u64::max_value(), // }), // WithdrawReasons::all() diff --git a/srml/eth-backing/Cargo.toml b/srml/eth-backing/Cargo.toml index 885bfb898..36f99293b 100644 --- a/srml/eth-backing/Cargo.toml +++ b/srml/eth-backing/Cargo.toml @@ -16,6 +16,7 @@ support = { package = "srml-support", git = "https://github.com/darwinia-network system = { package = "srml-system", git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-develop", default-features = false } darwinia-support = { package = "darwinia-support", path = "../support", default-features = false } +node-primitives = { path = "../primitives", default-features = false } [features] default = ["std"] @@ -26,5 +27,7 @@ std = [ "rstd/std", "support/std", "system/std", + "darwinia-support/std", + "node-primitives/std", ] diff --git a/srml/eth-backing/src/lib.rs b/srml/eth-backing/src/lib.rs index df1420ea0..98df2c81e 100644 --- a/srml/eth-backing/src/lib.rs +++ b/srml/eth-backing/src/lib.rs @@ -9,12 +9,13 @@ use rstd::vec::Vec; use support::{decl_event, decl_module, decl_storage, dispatch::Result, traits::Currency}; use system::ensure_signed; -use darwinia_support::{LockableCurrency, TimeStamp}; +use darwinia_support::LockableCurrency; +use node_primitives::Moment; //use merkle_mountain_range::{Hash, MerkleMountainRange}; pub trait Trait: system::Trait { type Event: From> + Into<::Event>; - type Ring: LockableCurrency; + type Ring: LockableCurrency; } // config() require `serde = { version = "1.0.101", optional = true }` diff --git a/srml/eth-relay/src/lib.rs b/srml/eth-relay/src/lib.rs index 594568868..a06fcbcee 100644 --- a/srml/eth-relay/src/lib.rs +++ b/srml/eth-relay/src/lib.rs @@ -88,7 +88,7 @@ decl_storage! { >::init_genesis_header(&header,config.genesis_difficulty); - // TODO: initilize other parameters. + // TODO: initialize other parameters. } }); } @@ -208,7 +208,7 @@ impl Module { ensure!( number >= Self::begin_header().ok_or("Begin Header - NOT EXISTED")?.number(), - "Block Number - TOO SMALL" + "Block Number - TOO SMALL", ); let prev_header = Self::header_of(parent_hash).ok_or("Previous Header - NOT EXISTED")?; diff --git a/srml/eth-relay/src/mock.rs b/srml/eth-relay/src/mock.rs index c747c7ebd..266cdc4bc 100644 --- a/srml/eth-relay/src/mock.rs +++ b/srml/eth-relay/src/mock.rs @@ -55,9 +55,9 @@ parameter_types! { } impl system::Trait for Runtime { type Origin = Origin; + type Call = (); type Index = u64; type BlockNumber = u64; - type Call = (); type Hash = H256; type Hashing = ::sr_primitives::traits::BlakeTwo256; type AccountId = u64; @@ -76,13 +76,13 @@ parameter_types! { } parameter_types! { - pub const ETH_MAINET: u64 = 0; - pub const ETH_ROPSTEN: u64 = 1; +// pub const EthMainet: u64 = 0; + pub const EthRopsten: u64 = 1; } impl Trait for Runtime { type Event = (); - type EthNetwork = ETH_ROPSTEN; + type EthNetwork = EthRopsten; } parameter_types! { diff --git a/srml/eth-relay/src/tests.rs b/srml/eth-relay/src/tests.rs index 70c4487a1..216045b31 100644 --- a/srml/eth-relay/src/tests.rs +++ b/srml/eth-relay/src/tests.rs @@ -1,25 +1,16 @@ //! Tests for the module. -use super::*; -//use sr_primitives::traits::SignedExtension; -//use support::{ -// assert_err, assert_noop, assert_ok, -// traits::{Currency, ExistenceRequirement::AllowDeath, LockableCurrency, ReservableCurrency}, -//}; -//use system::RawOrigin; -use mock::{EthRelay, ExtBuilder, System}; -//use mock::{info_from_weight, EthRelay, ExtBuilder, Runtime, System, CALL}; +use std::str::FromStr; -//use rstd::prelude::*; +use hex_literal::hex; +use rustc_hex::FromHex; use sr_eth_primitives::{ receipt::{LogEntry, TransactionOutcome}, Address, Bloom, H64, U128, }; -use hex_literal::hex; -use rustc_hex::FromHex; -use sha3::Keccak256; -use std::str::FromStr; +use super::*; +use mock::{EthRelay, ExtBuilder, System}; #[test] fn verify_receipt_proof() { @@ -79,7 +70,7 @@ fn verify_receipt_proof() { EthRelay::init_genesis_header(&header, 0x624c22d93f8e59_u64); - assert_eq!(EthRelay::verify_receipt(&proof_record), Some(receipt)); + assert_eq!(EthRelay::verify_receipt(&proof_record), Ok(receipt)); }); } @@ -112,8 +103,6 @@ fn relay_header() { // https://api-ropsten.etherscan.io/api?module=proxy&action=eth_getBlockByNumber&tag=0x69226b&boolean=true&apikey=YourApiKeyToken // https://jsoneditoronline.org/ - type DAG = LightDAG; - // 6760580 let mixh2 = H256::from(hex!("e06f0c107dcc91e9e82de0b42d0e22d5c2cfae5209422fda88cff4f810f4bffb")); let nonce2 = H64::from(hex!("9348d06003756cff")); diff --git a/srml/kton/Cargo.toml b/srml/kton/Cargo.toml index 12721233c..cd07c220d 100644 --- a/srml/kton/Cargo.toml +++ b/srml/kton/Cargo.toml @@ -22,9 +22,6 @@ darwinia-support = { path = "../support", default-features = false } runtime_io = { package = "sr-io", git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-develop" } substrate-primitives = { git = "https://github.com/darwinia-network/substrate.git", branch = "darwinia-develop" } -node-runtime = { path = "../../node/runtime" } -node-primitives = { path = "../../node/primitives" } - [features] default = ["std"] std = [ diff --git a/srml/kton/src/imbalance.rs b/srml/kton/src/imbalance.rs index 1d5398676..e2d338de9 100644 --- a/srml/kton/src/imbalance.rs +++ b/srml/kton/src/imbalance.rs @@ -1,7 +1,9 @@ -//TODO: move this into lib.rs to be a inner mod -use super::{result, Imbalance, Saturating, StorageValue, Trait, Zero}; +// TODO: move this into lib.rs to be a inner mod + use rstd::mem; +use crate::{result, Imbalance, Saturating, StorageValue, Trait, Zero}; + pub struct PositiveImbalance(T::Balance); impl PositiveImbalance { diff --git a/srml/kton/src/mock.rs b/srml/kton/src/mock.rs index abc434060..5a64d8a2c 100644 --- a/srml/kton/src/mock.rs +++ b/srml/kton/src/mock.rs @@ -1,6 +1,3 @@ -pub use node_primitives::Balance; -pub use node_runtime::constants::currency::COIN; - use std::{cell::RefCell, collections::HashSet}; use sr_primitives::{ @@ -12,8 +9,7 @@ use sr_primitives::{ use srml_support::{impl_outer_origin, parameter_types}; use substrate_primitives::H256; -use super::*; -use crate::{GenesisConfig, Module}; +use crate::*; thread_local! { static SESSION: RefCell<(Vec, HashSet)> = RefCell::new(Default::default()); @@ -22,12 +18,14 @@ thread_local! { /// The AccountId alias in this test module. pub type AccountId = u64; -// FIXME: -// replace -// testing::Header.number: u64 -// with -// node_primitives::BlockNumber +pub type Balance = u128; pub type BlockNumber = u64; +pub type Moment = u64; + +pub const NANO: Balance = 1; +pub const MICRO: Balance = 1_000 * NANO; +pub const MILLI: Balance = 1_000 * MICRO; +pub const COIN: Balance = 1_000 * MILLI; impl_outer_origin! { pub enum Origin for Test {} diff --git a/srml/kton/src/tests.rs b/srml/kton/src/tests.rs index b9e450af4..4f46f7a8c 100644 --- a/srml/kton/src/tests.rs +++ b/srml/kton/src/tests.rs @@ -1,9 +1,7 @@ -use srml_support::{assert_err, assert_noop, assert_ok, traits::Currency}; +use srml_support::{assert_err, assert_ok, traits::Currency}; -use darwinia_support::{LockIdentifier, NormalLock, TimeStamp, WithdrawLock, WithdrawReason, WithdrawReasons}; - -use super::*; -use crate::mock::*; +use crate::{mock::*, *}; +use darwinia_support::{LockIdentifier, NormalLock, WithdrawLock, WithdrawReasons}; const ID_1: LockIdentifier = *b"1 "; const ID_2: LockIdentifier = *b"2 "; @@ -51,7 +49,7 @@ fn transfer_should_fail() { &777, WithdrawLock::Normal(NormalLock { amount: Balance::max_value(), - until: TimeStamp::max_value(), + until: Moment::max_value(), }), WithdrawReasons::all(), ); @@ -66,7 +64,7 @@ fn transfer_should_fail() { fn set_lock_should_work() { ExtBuilder::default().existential_deposit(0).build().execute_with(|| { let lock_ids = [[0; 8], [1; 8], [2; 8], [3; 8]]; - let balance_per_lock = Kton::free_balance(&1) / (lock_ids.len() as u128); + let balance_per_lock = Kton::free_balance(&1) / (lock_ids.len() as Balance); // account `1`'s vesting length System::set_block_number(4); @@ -75,19 +73,19 @@ fn set_lock_should_work() { let mut locks = vec![]; for lock_id in lock_ids.iter() { Kton::set_lock( - *lock_id, - &1, + *lock_id, + &1, WithdrawLock::Normal(NormalLock { amount: balance_per_lock, - until: TimeStamp::max_value(), - }), - WithdrawReasons::all() + until: Moment::max_value(), + }), + WithdrawReasons::all(), ); locks.push(BalanceLock { id: *lock_id, withdraw_lock: WithdrawLock::Normal(NormalLock { amount: balance_per_lock, - until: TimeStamp::max_value(), + until: Moment::max_value(), }), reasons: WithdrawReasons::all(), }); @@ -111,13 +109,13 @@ fn remove_lock_should_work() { Timestamp::set_timestamp(0); let ts: u64 = Timestamp::now().into(); Kton::set_lock( - ID_1, - &2, + ID_1, + &2, WithdrawLock::Normal(NormalLock { - amount: Balance::max_value(), - until: TimeStamp::max_value(), + amount: Balance::max_value(), + until: Moment::max_value(), }), - WithdrawReasons::all() + WithdrawReasons::all(), ); assert_err!( Kton::transfer(Origin::signed(2), 1, 1), @@ -126,13 +124,13 @@ fn remove_lock_should_work() { // unexpired Kton::set_lock( - ID_2, - &2, + ID_2, + &2, WithdrawLock::Normal(NormalLock { - amount: Balance::max_value(), + amount: Balance::max_value(), until: ts + 1, - }), - WithdrawReasons::all() + }), + WithdrawReasons::all(), ); Kton::remove_lock(ID_1, &2); Timestamp::set_timestamp(ts); @@ -145,13 +143,13 @@ fn remove_lock_should_work() { // expired Kton::set_lock( - ID_3, - &2, + ID_3, + &2, WithdrawLock::Normal(NormalLock { - amount: Balance::max_value(), - until: ts, + amount: Balance::max_value(), + until: ts, }), - WithdrawReasons::all() + WithdrawReasons::all(), ); assert_ok!(Kton::transfer(Origin::signed(2), 1, 1)); }); @@ -165,41 +163,29 @@ fn update_lock_should_work() { // until > 1 locks.push(BalanceLock { id: [id; 8], - withdraw_lock: WithdrawLock::Normal(NormalLock { - amount: 1, - until: 2, - }), + withdraw_lock: WithdrawLock::Normal(NormalLock { amount: 1, until: 2 }), reasons: WithdrawReasons::none(), }); Kton::set_lock( - [id; 8], + [id; 8], &1, - WithdrawLock::Normal(NormalLock { - amount: 1, - until: 2, - }), - WithdrawReasons::none() + WithdrawLock::Normal(NormalLock { amount: 1, until: 2 }), + WithdrawReasons::none(), ); } let update_id = 4; - for amount in 32767u64..65535 { - let until = amount + 1; + for amount in 32767..65535 { + let until = amount as Moment + 1; locks[update_id as usize] = BalanceLock { id: [update_id; 8], - withdraw_lock: WithdrawLock::Normal(NormalLock { - amount: amount as u128, - until: until, - }), + withdraw_lock: WithdrawLock::Normal(NormalLock { amount, until }), reasons: WithdrawReasons::all(), }; Kton::set_lock( - [update_id; 8], + [update_id; 8], &1, - WithdrawLock::Normal(NormalLock { - amount: amount as u128, - until: until, - }), - WithdrawReasons::all() + WithdrawLock::Normal(NormalLock { amount, until }), + WithdrawReasons::all(), ); assert_eq!(Kton::locks(&1), locks); } @@ -211,31 +197,28 @@ fn combination_locking_should_work() { ExtBuilder::default().existential_deposit(0).build().execute_with(|| { Kton::deposit_creating(&1001, 10); Kton::set_lock( - ID_1, - &1001, + ID_1, + &1001, WithdrawLock::Normal(NormalLock { - amount: Balance::max_value(), + amount: Balance::max_value(), until: 0, }), - WithdrawReasons::none() + WithdrawReasons::none(), ); Kton::set_lock( - ID_2, - &1001, + ID_2, + &1001, WithdrawLock::Normal(NormalLock { amount: 0, - until: TimeStamp::max_value(), - }), - WithdrawReasons::none() + until: Moment::max_value(), + }), + WithdrawReasons::none(), ); Kton::set_lock( - ID_3, - &1001, - WithdrawLock::Normal(NormalLock { - amount: 0, - until: 0, - }), - WithdrawReasons::all() + ID_3, + &1001, + WithdrawLock::Normal(NormalLock { amount: 0, until: 0 }), + WithdrawReasons::all(), ); assert_ok!(Kton::transfer(Origin::signed(1001), 1002, 1)); }); diff --git a/srml/staking/Cargo.toml b/srml/staking/Cargo.toml index b6976270a..3005f16ff 100644 --- a/srml/staking/Cargo.toml +++ b/srml/staking/Cargo.toml @@ -31,8 +31,6 @@ rand = "0.7.2" balances = { package = "darwinia-balances", path = '../balances' } kton = { package = "darwinia-kton", path = "../kton" } -node-runtime = { path = "../../node/runtime" } -node-primitives = { path = "../../node/primitives" } [features] equalize = [] diff --git a/srml/staking/src/inflation.rs b/srml/staking/src/inflation.rs index 71b9ced5e..2237f643e 100644 --- a/srml/staking/src/inflation.rs +++ b/srml/staking/src/inflation.rs @@ -5,17 +5,17 @@ use sr_primitives::{ }; use substrate_primitives::U256; -use super::{KtonBalanceOf, RingBalanceOf, TimeStamp, Trait}; +use crate::{KtonBalanceOf, Moment, RingBalanceOf, Trait}; // 1 - (99 / 100) ^ sqrt(year) // () -> RingBalanceOf pub fn compute_total_payout( - era_duration: TimeStamp, - living_time: TimeStamp, + era_duration: Moment, + living_time: Moment, total_left: u128, ) -> (RingBalanceOf, RingBalanceOf) { // Milliseconds per year for the Julian year (365.25 days). - const MILLISECONDS_PER_YEAR: TimeStamp = ((36525 * 24 * 60 * 60) / 100) * 1000; + const MILLISECONDS_PER_YEAR: Moment = ((36525 * 24 * 60 * 60) / 100) * 1000; let year: u32 = (living_time / MILLISECONDS_PER_YEAR + 1).saturated_into::(); @@ -36,7 +36,7 @@ pub fn compute_total_payout( // consistent with the formula in smart contract in evolution land which can be found in // https://github.com/evolutionlandorg/bank/blob/master/contracts/GringottsBank.sol#L280 -pub fn compute_kton_return(value: RingBalanceOf, months: u32) -> KtonBalanceOf { +pub fn compute_kton_return(value: RingBalanceOf, months: u64) -> KtonBalanceOf { let value = value.saturated_into::(); let no = U256::from(67).pow(U256::from(months)); let de = U256::from(66).pow(U256::from(months)); diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index daae887a7..789a63916 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -17,17 +17,36 @@ #![recursion_limit = "128"] #![cfg_attr(not(feature = "std"), no_std)] #![feature(drain_filter)] -#![cfg_attr(all(feature = "bench", test), feature(test))] -#[cfg(all(feature = "bench", test))] -extern crate test; pub mod inflation; +mod err { + pub const CONTROLLER_INVALID: &'static str = "Controller Account - INVALID"; + pub const CONTROLLER_ALREADY_PAIRED: &'static str = "Controller Account - ALREADY PAIRED"; + + pub const STASH_INVALID: &'static str = "Stash Account - INVALID"; + pub const STASH_ALREADY_BONDED: &'static str = "Stash Account - ALREADY BONDED"; + + pub const UNLOCK_CHUNKS_REACH_MAX: &'static str = "Unlock Chunks - REACH MAX VALUE 32"; + + pub const CLAIM_DEPOSITS_EXPIRE_TIME_INVALID: &'static str = + "Claim Deposits With Punish - NOTHING TO CLAIM AT THIS TIME"; + pub const NODE_NAME_INVALID: &'static str = "Node Name - INVALID"; + pub const TARGETS_INVALID: &'static str = "Targets - CAN NOT BE EMPTY"; +} + +#[allow(unused)] +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + use codec::{Decode, Encode, HasCompact}; use rstd::{borrow::ToOwned, prelude::*, result}; use session::{historical::OnSessionEnding, SelectInitialValidators}; use sr_primitives::{ - traits::{CheckedSub, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero}, + traits::{Bounded, CheckedSub, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero}, + weights::SimpleDispatchInfo, Perbill, Perquintill, RuntimeDebug, }; #[cfg(feature = "std")] @@ -43,23 +62,19 @@ use srml_support::{ use system::{ensure_root, ensure_signed}; use darwinia_support::{ - LockIdentifier, LockableCurrency, NormalLock, StakingLock, TimeStamp, WithdrawLock, WithdrawReason, WithdrawReasons, + LockIdentifier, LockableCurrency, NormalLock, StakingLock, WithdrawLock, WithdrawReason, WithdrawReasons, }; use phragmen::{build_support_map, elect, equalize, ExtendedBalance, PhragmenStakedAssignment}; -#[cfg(test)] -mod mock; -#[cfg(test)] -mod tests; - const DEFAULT_MINIMUM_VALIDATOR_COUNT: u32 = 4; const MAX_NOMINATIONS: usize = 16; -const MAX_UNSTAKE_THRESHOLD: u32 = 10; const MAX_UNLOCKING_CHUNKS: u32 = 32; -const MONTH_IN_SECONDS: u32 = 2_592_000; - +const MONTH_IN_MILLISECONDS: u64 = 30 * 24 * 60 * 60 * 1000; const STAKING_ID: LockIdentifier = *b"staking "; +pub type Balance = u128; +pub type Moment = u64; + /// Counter for the number of eras that have passed. pub type EraIndex = u32; @@ -89,6 +104,7 @@ impl EraPoints { } } +/// Indicates the initial status of the staker. #[derive(RuntimeDebug)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum StakerStatus { @@ -100,58 +116,77 @@ pub enum StakerStatus { Nominator(Vec), } +/// A destination account for payment. +#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug)] +pub enum RewardDestination { + /// Pay into the stash account, increasing the amount at stake accordingly. + /// for now, we don't use this. + // DeprecatedStaked, + /// 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::Stash + } +} + +/// Preference of what happens on a slash event. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] pub struct ValidatorPrefs { pub node_name: Vec, - /// Validator should ensure this many more slashes than is necessary before being unstaked. - #[codec(compact)] - pub unstake_threshold: u32, /// percent of Reward that validator takes up-front; only the rest is split between themselves and /// nominators. #[codec(compact)] pub validator_payment_ratio: u32, } +impl ValidatorPrefs { + // TODO + /// Filter Ad, curse words... + fn node_name_is_valid(&self) -> bool { + if self.node_name.is_empty() { + return false; + } + + true + } +} + impl Default for ValidatorPrefs { fn default() -> Self { ValidatorPrefs { node_name: vec![], - unstake_threshold: 3, validator_payment_ratio: 0, } } } +/// To unify *Ring* and *Kton* balance. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] -pub enum StakingBalance { +pub enum StakingBalance +where + Ring: HasCompact, + Kton: HasCompact, +{ Ring(Ring), Kton(Kton), } -impl Default for StakingBalance { +impl Default for StakingBalance +where + Ring: Default + HasCompact, + Kton: Default + HasCompact, +{ fn default() -> Self { StakingBalance::Ring(Default::default()) } } -/// A destination account for payment. -#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug)] -pub enum RewardDestination { - /// Pay into the stash account, increasing the amount at stake accordingly. - /// for now, we don't use this. - // DeprecatedStaked, - /// 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::Stash - } -} - +/// The *Ring* under deposit. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] pub struct TimeDepositItem { #[codec(compact)] @@ -162,6 +197,7 @@ pub struct TimeDepositItem { expire_time: Moment, } +/// The ledger of a (bonded) stash. #[derive(PartialEq, Eq, Default, Clone, Encode, Decode, RuntimeDebug)] pub struct StakingLedger { /// The stash account whose balance is actually locked and at stake. @@ -185,25 +221,28 @@ pub struct StakingLedger // which can also be used for staking pub deposit_items: Vec>, - pub ring_staking_lock: StakingLock, - pub kton_staking_lock: StakingLock, + pub ring_staking_lock: StakingLock, + pub kton_staking_lock: StakingLock, } /// The amount of exposure (to slashing) than an individual nominator has. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug)] -pub struct IndividualExposure { +pub struct IndividualExposure { /// The stash account of the nominator in question. who: AccountId, /// Amount of funds exposed. + #[codec(compact)] value: Power, } /// A snapshot of the stake backing a single validator in the system. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, RuntimeDebug)] -pub struct Exposure { +pub struct Exposure { /// The total balance backing this validator. + #[codec(compact)] pub total: Power, /// The validator's own stash that is exposed. + #[codec(compact)] pub own: Power, /// The portions of nominators stashes that are exposed. pub others: Vec>, @@ -211,25 +250,28 @@ pub struct Exposure { /// A slashing event occurred, slashing a validator for a given amount of balance. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, RuntimeDebug)] -pub struct SlashJournalEntry { +pub struct SlashJournalEntry { who: AccountId, - amount: Balance, - own_slash: Balance, // the amount of `who`'s own exposure that was slashed + #[codec(compact)] + amount: Power, + // the amount of `who`'s own exposure that was slashed + #[codec(compact)] + own_slash: Power, } type RingBalanceOf = <::Ring as Currency<::AccountId>>::Balance; -type KtonBalanceOf = <::Kton as Currency<::AccountId>>::Balance; - -// for ring type RingPositiveImbalanceOf = <::Ring as Currency<::AccountId>>::PositiveImbalance; type RingNegativeImbalanceOf = <::Ring as Currency<::AccountId>>::NegativeImbalance; -// for kton +type KtonBalanceOf = <::Kton as Currency<::AccountId>>::Balance; type KtonPositiveImbalanceOf = <::Kton as Currency<::AccountId>>::PositiveImbalance; type KtonNegativeImbalanceOf = <::Kton as Currency<::AccountId>>::NegativeImbalance; type MomentOf = <::Time as Time>::Moment; +/// Means for interacting with a specialized version of the `session` trait. +/// +/// This is needed because `Staking` sets the `ValidatorIdOf` of the `session::Trait` pub trait SessionInterface: system::Trait { /// Disable a given validator by stash ID. /// @@ -258,6 +300,7 @@ where fn disable_validator(validator: &::AccountId) -> Result { >::disable(validator) } + fn validators() -> Vec<::AccountId> { >::validators() } @@ -268,40 +311,49 @@ where } pub trait Trait: timestamp::Trait + session::Trait { + /// The staking balances. type Ring: LockableCurrency; + /// The staking balances. type Kton: LockableCurrency; /// Time used for computing era duration. type Time: Time; + /// Convert a balance into a number used for election calculation. + /// This must fit into a `u64` but is allowed to be sensibly lossy. + /// TODO: #1377 + /// The backward convert should be removed as the new Phragmen API returns ratio. + /// The post-processing needs it but will be moved to off-chain. TODO: #2908 type CurrencyToVote: Convert + Convert; + /// Tokens have been minted and are unused for validator-reward. type RingRewardRemainder: OnUnbalanced>; /// The overarching event type. type Event: From> + Into<::Event>; - /// Handler for the unbalanced reduction when slashing a staker. - type RingSlash: OnUnbalanced>; - /// Handler for the unbalanced increment when rewarding a staker. type RingReward: OnUnbalanced>; + /// Handler for the unbalanced increment when rewarding a staker. + type KtonReward: OnUnbalanced>; + /// Handler for the unbalanced reduction when slashing a staker. + type RingSlash: OnUnbalanced>; + /// Handler for the unbalanced reduction when slashing a staker. type KtonSlash: OnUnbalanced>; - type KtonReward: OnUnbalanced>; /// Number of sessions per era. type SessionsPerEra: Get; /// Number of seconds that staked funds must remain bonded for. - type BondingDuration: Get; + type BondingDuration: Get; + + /// Interface for interacting with a session module. + type SessionInterface: self::SessionInterface; // custom type Cap: Get<>::Balance>; type GenesisTime: Get>; - - /// Interface for interacting with a session module. - type SessionInterface: self::SessionInterface; } /// Mode of era-forcing. @@ -326,33 +378,43 @@ impl Default for Forcing { decl_storage! { trait Store for Module as Staking { - pub ValidatorCount get(validator_count) config(): u32; + /// The ideal number of staking participants. + pub ValidatorCount get(fn validator_count) config(): u32; - pub MinimumValidatorCount get(minimum_validator_count) config(): - u32 = DEFAULT_MINIMUM_VALIDATOR_COUNT; + /// Minimum number of staking participants before emergency conditions are imposed. + pub MinimumValidatorCount get(fn minimum_validator_count) config(): u32 = DEFAULT_MINIMUM_VALIDATOR_COUNT; /// Any validators that may never be slashed or forcibly kicked. It's a Vec since they're /// easy to initialize and the performance hit is minimal (we expect no more than four /// invulnerables) and restricted to testnets. pub Invulnerables get(fn invulnerables) config(): Vec; - pub SessionReward get(session_reward) config(): Perbill = Perbill::from_percent(60); + /// Map from all locked "stash" accounts to the controller account. + pub Bonded get(fn bonded): map T::AccountId => Option; - pub Bonded get(bonded): map T::AccountId => Option; + /// Map from all (unlocked) "controller" accounts to the info regarding the staking. + pub Ledger get(fn ledger): map T::AccountId => Option, KtonBalanceOf, T::Moment>>; - pub Ledger get(ledger): map T::AccountId => Option, KtonBalanceOf, T::Moment>>; + /// Where the reward payment should be made. Keyed by stash. + pub Payee get(fn payee): map T::AccountId => RewardDestination; - pub Payee get(payee): map T::AccountId => RewardDestination; + /// The map from (wannabe) validator stash key to the preferences of that validator. + pub Validators get(fn validators): linked_map T::AccountId => ValidatorPrefs; - pub Validators get(validators): linked_map T::AccountId => ValidatorPrefs; + /// The map from nominator stash key to the set of stash keys of all validators to nominate. + pub Nominators get(fn nominators): linked_map T::AccountId => Vec; - pub Nominators get(nominators): linked_map T::AccountId => Vec; - - pub Stakers get(stakers): map T::AccountId => Exposure; + /// 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 Session module. + /// + /// This is keyed by the stash account. + pub Stakers get(fn stakers): map T::AccountId => Exposure; - pub CurrentElected get(current_elected): Vec; + /// The currently elected validator set keyed by stash account ID. + pub CurrentElected get(fn current_elected): Vec; - pub CurrentEra get(current_era) config(): EraIndex; + /// The current era index. + pub CurrentEra get(fn current_era) config(): EraIndex; /// The start of the current era. pub CurrentEraStart get(fn current_era_start): MomentOf; @@ -366,7 +428,9 @@ decl_storage! { /// The amount of balance actively at stake for each validator slot, currently. /// /// This is used to derive rewards and punishments. - pub SlotStake get(fn slot_stake): ExtendedBalance; + pub SlotStake get(fn slot_stake) build(|config: &GenesisConfig| { + config.stakers.iter().map(|&(_, _, value, _)| value.saturated_into()).min().unwrap_or_default() + }): ExtendedBalance; /// True if the next session change will be a new era regardless of index. pub ForceEra get(fn force_era) config(): Forcing; @@ -376,9 +440,10 @@ decl_storage! { /// The rest of the slashed value is handled by the `Slash`. pub SlashRewardFraction get(fn slash_reward_fraction) config(): Perbill; - pub RingPool get(ring_pool): RingBalanceOf; - - pub KtonPool get(kton_pool): KtonBalanceOf; + /// Total *Ring* in pool. + pub RingPool get(fn ring_pool): RingBalanceOf; + /// Total *Kton* in pool. + pub KtonPool get(fn kton_pool): KtonBalanceOf; /// A mapping from still-bonded eras to the first session index of that era. BondedEras: Vec<(EraIndex, SessionIndex)>; @@ -386,24 +451,25 @@ decl_storage! { /// All slashes that have occurred in a given era. EraSlashJournal get(fn era_slash_journal): map EraIndex => Vec>; } + add_extra_genesis { config(stakers): Vec<(T::AccountId, T::AccountId, RingBalanceOf, StakerStatus)>; build(|config: &GenesisConfig| { - for &(ref stash, ref controller, balance, ref status) in &config.stakers { - assert!(T::Ring::free_balance(&stash) >= balance); + for &(ref stash, ref controller, ring, ref status) in &config.stakers { + assert!(T::Ring::free_balance(&stash) >= ring); let _ = >::bond( T::Origin::from(Some(stash.clone()).into()), T::Lookup::unlookup(controller.clone()), - StakingBalance::Ring(balance), + StakingBalance::Ring(ring), RewardDestination::Stash, - 12, + 0, ); let _ = match status { StakerStatus::Validator => { >::validate( T::Origin::from(Some(controller.clone()).into()), ValidatorPrefs { - node_name: "Darwinia-Alice".bytes().collect(), + node_name: "Darwinia Node".bytes().collect(), ..Default::default() }, ) @@ -423,14 +489,18 @@ decl_storage! { decl_event!( pub enum Event where Balance = RingBalanceOf, ::AccountId { - /// All validators have been rewarded by the given balance. + /// All validators have been rewarded by the first balance; the second is the remainder + /// from the maximum amount of reward. Reward(Balance, Balance), // TODO: refactor to Balance later? + /// One validator (and its nominators) has been slashed by the given amount. Slash(AccountId, ExtendedBalance), + /// An old slashing report from a prior era was discarded because it could + /// not be processed. OldSlashingReportDiscarded(SessionIndex), - /// NodeName changed + /// NodeName changed. NodeNameUpdated, // Develop @@ -444,33 +514,55 @@ decl_module! { const SessionsPerEra: SessionIndex = T::SessionsPerEra::get(); /// Number of eras that staked funds must remain bonded for. - const BondingDuration: TimeStamp = T::BondingDuration::get(); + const BondingDuration: T::Moment = T::BondingDuration::get(); fn deposit_event() = default; + fn on_finalize() { + // Set the start of the first era. + if !>::exists() { + >::put(T::Time::now()); + } + } + + /// Take the origin account as a stash and lock up `value` of its balance. `controller` will + /// be the account that controls it. + /// + /// `value` must be more than the `minimum_balance` specified by `T::Currency`. + /// + /// The dispatch origin for this call must be _Signed_ by the stash account. + /// + /// # + /// - Independent of the arguments. Moderate complexity. + /// - O(1). + /// - Three extra DB entries. + /// + /// NOTE: Two of the storage writes (`Self::bonded`, `Self::payee`) are _never_ cleaned unless + /// the `origin` falls below _existential deposit_ and gets removed as dust. + /// # + #[weight = SimpleDispatchInfo::FixedNormal(500_000)] fn bond( origin, controller: ::Source, value: StakingBalance, KtonBalanceOf>, payee: RewardDestination, - promise_month: u32 + promise_month: u64 ) { let stash = ensure_signed(origin)?; - if >::exists(&stash) { - return Err("stash already bonded") - } + ensure!(!>::exists(&stash), err::STASH_ALREADY_BONDED); let controller = T::Lookup::lookup(controller)?; - if >::exists(&controller) { - return Err("controller already paired") - } - - ensure!(promise_month <= 36, "months at most is 36."); + ensure!(!>::exists(&controller), err::CONTROLLER_ALREADY_PAIRED); + // You're auto-bonded forever, here. We might improve this by only bonding when + // you actually validate/nominate and remove once you unbond __everything__. >::insert(&stash, &controller); >::insert(&stash, payee); - let ledger = StakingLedger {stash: stash.clone(), ..Default::default()}; + let ledger = StakingLedger { + stash: stash.clone(), + ..Default::default() + }; match value { StakingBalance::Ring(r) => { let stash_balance = T::Ring::free_balance(&stash); @@ -489,16 +581,29 @@ decl_module! { } } + /// 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. + /// Unlike [`bond`] or [`unbond`] this function does not impose any limitation on the amount + /// that can be added. + /// + /// The dispatch origin for this call must be _Signed_ by the stash, not the controller. + /// + /// # + /// - Independent of the arguments. Insignificant complexity. + /// - O(1). + /// - One DB entry. + /// # + #[weight = SimpleDispatchInfo::FixedNormal(500_000)] fn bond_extra( origin, value: StakingBalance, KtonBalanceOf>, - promise_month: u32 + promise_month: u64 ) { let stash = ensure_signed(origin)?; - let controller = Self::bonded(&stash).ok_or("not a stash")?; - let ledger = Self::ledger(&controller).ok_or("not a controller")?; - - ensure!(promise_month <= 36, "months at most is 36."); + let controller = Self::bonded(&stash).ok_or(err::STASH_INVALID)?; + let ledger = Self::ledger(&controller).ok_or(err::CONTROLLER_INVALID)?; match value { StakingBalance::Ring(r) => { @@ -523,9 +628,30 @@ decl_module! { /// for normal_ring or normal_kton, follow the original substrate pattern /// for time_deposit_ring, transform it into normal_ring first /// modify time_deposit_items and time_deposit_ring amount + + /// 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::minimum_balance(), then it is increased to the full amount. + /// + /// Once the unlock period is done, the funds will be withdrew automatically and ready for transfer. + /// + /// No more than a limited number of unlocking chunks (see `MAX_UNLOCKING_CHUNKS`) + /// can co-exists at the same time. In that case, [`StakingLock::shrink`] need + /// to be called first to remove some of the chunks (if possible). + /// + /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. + /// + /// # + /// - Independent of the arguments. Limited but potentially exploitable complexity. + /// - Contains a limited number of reads. + /// - Each call (requires the remainder of the bonded balance to be above `minimum_balance`) + /// will cause a new entry to be inserted into a vector (`StakingLock.unlockings`) kept in storage. + /// - One DB entry. + /// + #[weight = SimpleDispatchInfo::FixedNormal(400_000)] fn unbond(origin, value: StakingBalance, KtonBalanceOf>) { let controller = ensure_signed(origin)?; - let mut ledger = Self::clear_mature_deposits(Self::ledger(&controller).ok_or("not a controller")?); + let mut ledger = Self::clear_mature_deposits(Self::ledger(&controller).ok_or(err::CONTROLLER_INVALID)?); let StakingLedger { active_ring, active_deposit_ring, @@ -534,6 +660,10 @@ decl_module! { kton_staking_lock, .. } = &mut ledger; + let now = >::now(); + + ring_staking_lock.shrink(now); + kton_staking_lock.shrink(now); // due to the macro parser, we've to add a bracket // actually, this's totally wrong: @@ -542,28 +672,25 @@ decl_module! { // 1. `(a as u32 + b as u32) < c` // 2. `let c_ = a as u32 + b as u32; c_ < c` ensure!( - (ring_staking_lock.unbondings.len() as u32 + kton_staking_lock.unbondings.len() as u32) - < - MAX_UNLOCKING_CHUNKS, - "can not schedule more unlock chunks", + (ring_staking_lock.unbondings.len() as u32 + kton_staking_lock.unbondings.len() as u32) < MAX_UNLOCKING_CHUNKS, + err::UNLOCK_CHUNKS_REACH_MAX, ); - let at = >::now().saturated_into::() + T::BondingDuration::get(); match value { StakingBalance::Ring(r) => { - // total_active_ring = normal_ring + time_deposit_ring - // Only active normal ring can be unbond + // only active normal ring can be unbond + // active_ring = active_normal_ring + active_deposit_ring let active_normal_ring = *active_ring - *active_deposit_ring; - // unbond normal ring first let available_unbond_ring = r.min(active_normal_ring); - >::mutate(|r| *r -= available_unbond_ring); - if !available_unbond_ring.is_zero() { *active_ring -= available_unbond_ring; + ring_staking_lock.unbondings.push(NormalLock { + amount: available_unbond_ring, + until: now + T::BondingDuration::get(), + }); - ring_staking_lock.unbondings.push(NormalLock { amount: available_unbond_ring, until: at }); - + >::mutate(|r| *r -= available_unbond_ring); Self::update_ledger(&controller, &mut ledger, value); } }, @@ -571,24 +698,24 @@ decl_module! { let unbond_kton = k.min(*active_kton); if !unbond_kton.is_zero() { - >::mutate(|k| *k -= unbond_kton); - *active_kton -= unbond_kton; + kton_staking_lock.unbondings.push(NormalLock { + amount: unbond_kton, + until: now + T::BondingDuration::get(), + }); - kton_staking_lock.unbondings.push(NormalLock { amount: unbond_kton, until: at }); - + >::mutate(|k| *k -= unbond_kton); Self::update_ledger(&controller, &mut ledger, value); } }, } } - /// called by controller - fn deposit_extra(origin, value: RingBalanceOf, promise_month: u32) { + // TODO: doc + fn deposit_extra(origin, value: RingBalanceOf, promise_month: u64) { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or("not a controller")?; - - ensure!(promise_month >= 3 && promise_month <= 36, "months at least is 3 and at most is 36."); + let ledger = Self::ledger(&controller).ok_or(err::CONTROLLER_INVALID)?; + let promise_month = promise_month.max(3).min(36); let now = >::now(); let mut ledger = Self::clear_mature_deposits(ledger); @@ -599,7 +726,7 @@ decl_module! { deposit_items, .. } = &mut ledger; - let value = value.min(*active_ring - *active_deposit_ring); // active_normal_ring + let value = value.min(*active_ring - *active_deposit_ring); // for now, kton_return is free // mint kton let kton_return = inflation::compute_kton_return::(value, promise_month); @@ -610,24 +737,27 @@ decl_module! { deposit_items.push(TimeDepositItem { value, start_time: now, - expire_time: now + (MONTH_IN_SECONDS * promise_month).into(), + expire_time: now + T::Moment::saturated_from((promise_month * MONTH_IN_MILLISECONDS).into()), }); >::insert(&controller, ledger); } + // TODO: doc fn claim_mature_deposits(origin) { let controller = ensure_signed(origin)?; - let ledger = Self::clear_mature_deposits(Self::ledger(&controller).ok_or("not a controller")?); + let ledger = Self::clear_mature_deposits(Self::ledger(&controller).ok_or(err::CONTROLLER_INVALID)?); + >::insert(controller, ledger); } - fn claim_deposits_with_punish(origin, expire_time: T::Moment) { + // TODO: doc + fn try_claim_deposits_with_punish(origin, expire_time: T::Moment) { let controller = ensure_signed(origin)?; - let mut ledger = Self::ledger(&controller).ok_or("not a controller")?; - + let mut ledger = Self::ledger(&controller).ok_or(err::CONTROLLER_INVALID)?; let now = >::now(); - ensure!(expire_time > now, "use unbond instead."); + + ensure!(expire_time > now, err::CLAIM_DEPOSITS_EXPIRE_TIME_INVALID); let StakingLedger { stash, @@ -642,15 +772,16 @@ decl_module! { } let kton_slash = { - let passed_duration = (now - item.start_time).saturated_into::() / MONTH_IN_SECONDS; - let plan_duration = (item.expire_time - item.start_time).saturated_into::() / MONTH_IN_SECONDS; + let passed_duration = (now - item.start_time).saturated_into::() / MONTH_IN_MILLISECONDS; + let plan_duration = (item.expire_time - item.start_time).saturated_into::() / MONTH_IN_MILLISECONDS; ( inflation::compute_kton_return::(item.value, plan_duration) - inflation::compute_kton_return::(item.value, passed_duration) - ) * 3.into() + ).max(1.into()) * 3.into() }; + // check total free balance and locked one // strict on punishing in kton if T::Kton::free_balance(stash) @@ -679,18 +810,23 @@ decl_module! { >::insert(&controller, ledger); } + /// Declare the desire to validate for the origin controller. + /// + /// Effects will be felt at the beginning of the next era. + /// + /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. + /// + /// # + /// - Independent of the arguments. Insignificant complexity. + /// - Contains a limited number of reads. + /// - Writes are limited to the `origin` account key. + /// # + #[weight = SimpleDispatchInfo::FixedNormal(750_000)] fn validate(origin, prefs: ValidatorPrefs) { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or("not a controller")?; + let ledger = Self::ledger(&controller).ok_or(err::CONTROLLER_INVALID)?; - ensure!( - !prefs.node_name.is_empty(), - "node name can not be empty", - ); - ensure!( - prefs.unstake_threshold <= MAX_UNSTAKE_THRESHOLD, - "unstake threshold too large", - ); + ensure!(prefs.node_name_is_valid(), err::NODE_NAME_INVALID); let stash = &ledger.stash; let mut prefs = prefs; @@ -707,11 +843,25 @@ decl_module! { }); } + /// Declare the desire to nominate `targets` for the origin controller. + /// + /// Effects will be felt at the beginning of the next era. + /// + /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. + /// + /// # + /// - The transaction's complexity is proportional to the size of `targets`, + /// which is capped at `MAX_NOMINATIONS`. + /// - Both the reads and writes follow a similar pattern. + /// # + #[weight = SimpleDispatchInfo::FixedNormal(750_000)] fn nominate(origin, targets: Vec<::Source>) { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or("not a controller")?; + let ledger = Self::ledger(&controller).ok_or(err::CONTROLLER_INVALID)?; let stash = &ledger.stash; - ensure!(!targets.is_empty(), "targets cannot be empty"); + + ensure!(!targets.is_empty(), err::TARGETS_INVALID); + let targets = targets.into_iter() .take(MAX_NOMINATIONS) .map(T::Lookup::lookup) @@ -721,28 +871,66 @@ decl_module! { >::insert(stash, targets); } + /// Declare no desire to either validate or nominate. + /// + /// Effects will be felt at the beginning of the next era.、 + /// + /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. + /// + /// # + /// - Independent of the arguments. Insignificant complexity. + /// - Contains one read. + /// - Writes are limited to the `origin` account key. + /// # + #[weight = SimpleDispatchInfo::FixedNormal(500_000)] fn chill(origin) { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or("not a controller")?; + let ledger = Self::ledger(&controller).ok_or(err::CONTROLLER_INVALID)?; let stash = &ledger.stash; + >::remove(stash); >::remove(stash); } + /// (Re-)set the payment target for a controller. + /// + /// Effects will be felt at the beginning of the next era. + /// + /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. + /// + /// # + /// - Independent of the arguments. Insignificant complexity. + /// - Contains a limited number of reads. + /// - Writes are limited to the `origin` account key. + /// # + #[weight = SimpleDispatchInfo::FixedNormal(500_000)] fn set_payee(origin, payee: RewardDestination) { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or("not a controller")?; + let ledger = Self::ledger(&controller).ok_or(err::CONTROLLER_INVALID)?; let stash = &ledger.stash; + >::insert(stash, payee); } + /// (Re-)set the controller of a stash. + /// + /// Effects will be felt at the beginning of the next era. + /// + /// The dispatch origin for this call must be _Signed_ by the stash, not the controller. + /// + /// # + /// - Independent of the arguments. Insignificant complexity. + /// - Contains a limited number of reads. + /// - Writes are limited to the `origin` account key. + /// # + #[weight = SimpleDispatchInfo::FixedNormal(750_000)] fn set_controller(origin, controller: ::Source) { let stash = ensure_signed(origin)?; - let old_controller = Self::bonded(&stash).ok_or("not a stash")?; + let old_controller = Self::bonded(&stash).ok_or(err::STASH_INVALID)?; let controller = T::Lookup::lookup(controller)?; - if >::exists(&controller) { - return Err("controller already paired") - } + + ensure!(!>::exists(&controller), err::CONTROLLER_ALREADY_PAIRED); + if controller != old_controller { >::insert(&stash, &controller); if let Some(l) = >::take(&old_controller) { @@ -759,6 +947,24 @@ decl_module! { // ----- Root calls. + /// Force there to be no new eras indefinitely. + /// + /// # + /// - No arguments. + /// # + #[weight = SimpleDispatchInfo::FreeOperational] + fn force_no_eras(origin) { + ensure_root(origin)?; + ForceEra::put(Forcing::ForceNone); + } + + /// Force there to be a new era at the end of the next session. After this, it will be + /// reset to normal (non-forced) behaviour. + /// + /// # + /// - No arguments. + /// # + #[weight = SimpleDispatchInfo::FreeOperational] fn force_new_era(origin) { ensure_root(origin)?; ForceEra::put(Forcing::ForceNone); @@ -769,10 +975,36 @@ decl_module! { ensure_root(origin)?; >::put(validators); } + + /// Force a current staker to become completely unstaked, immediately. + #[weight = SimpleDispatchInfo::FreeOperational] + fn force_unstake(origin, stash: T::AccountId) { + ensure_root(origin)?; + + // remove the lock. + T::Ring::remove_lock(STAKING_ID, &stash); + T::Kton::remove_lock(STAKING_ID, &stash); + // remove all staking-related information. + Self::kill_stash(&stash); + } + + /// Force there to be a new era at the end of sessions indefinitely. + /// + /// # + /// - One storage write + /// # + #[weight = SimpleDispatchInfo::FreeOperational] + fn force_new_era_always(origin) { + ensure_root(origin)?; + ForceEra::put(Forcing::ForceAlways); + } } } impl Module { + // PUBLIC IMMUTABLES + + // TODO: doc pub fn clear_mature_deposits( mut ledger: StakingLedger, KtonBalanceOf, T::Moment>, ) -> StakingLedger, KtonBalanceOf, T::Moment> { @@ -795,13 +1027,16 @@ impl Module { ledger } + // TODO: doc fn bond_helper_in_ring( stash: &T::AccountId, controller: &T::AccountId, value: RingBalanceOf, - promise_month: u32, + promise_month: u64, mut ledger: StakingLedger, KtonBalanceOf, T::Moment>, ) { + let promise_month = promise_month.min(36); + // if stash promise to a extra-lock // there will be extra reward, kton, which // can also be use to stake. @@ -813,11 +1048,10 @@ impl Module { let kton_positive_imbalance = T::Kton::deposit_creating(&stash, kton_return); T::KtonReward::on_unbalanced(kton_positive_imbalance); let now = >::now(); - let expire_time = now + (MONTH_IN_SECONDS * promise_month).into(); ledger.deposit_items.push(TimeDepositItem { value, start_time: now, - expire_time, + expire_time: now + T::Moment::saturated_from((promise_month * MONTH_IN_MILLISECONDS).into()), }); } ledger.active_ring = ledger.active_ring.saturating_add(value); @@ -825,6 +1059,7 @@ impl Module { Self::update_ledger(&controller, &mut ledger, StakingBalance::Ring(value)); } + // TODO: doc fn bond_helper_in_kton( controller: &T::AccountId, value: KtonBalanceOf, @@ -835,6 +1070,55 @@ impl Module { Self::update_ledger(&controller, &mut ledger, StakingBalance::Kton(value)); } + // TODO: there is reserve balance in Balance.Slash, we assuming it is zero for now. + fn slash_individual( + stash: &T::AccountId, + slash_ratio: Perbill, + ) -> (RingNegativeImbalanceOf, KtonNegativeImbalanceOf, ExtendedBalance) { + let controller = Self::bonded(stash).unwrap(); + let mut ledger = Self::ledger(&controller).unwrap(); + + let (ring_imbalance, _) = if !ledger.active_ring.is_zero() { + let slashable_ring = slash_ratio * ledger.active_ring; + let value_slashed = Self::slash_helper(&controller, &mut ledger, StakingBalance::Ring(slashable_ring)); + T::Ring::slash(stash, value_slashed.0) + } else { + (>::zero(), Zero::zero()) + }; + let (kton_imbalance, _) = if !ledger.active_kton.is_zero() { + let slashable_kton = slash_ratio * ledger.active_kton; + let value_slashed = Self::slash_helper(&controller, &mut ledger, StakingBalance::Kton(slashable_kton)); + T::Kton::slash(stash, value_slashed.1) + } else { + (>::zero(), Zero::zero()) + }; + + (ring_imbalance, kton_imbalance, 0) + } + + // TODO: doc + fn power_of(stash: &T::AccountId) -> ExtendedBalance { + // power is a mixture of ring and kton + // power = ring_ratio * POWER_COUNT / 2 + kton_ratio * POWER_COUNT / 2 + fn calc_power>(active: S, pool: S) -> ExtendedBalance { + const HALF_POWER_COUNT: u128 = 1_000_000_000 / 2; + + Perquintill::from_rational_approximation( + active.saturated_into::(), + pool.saturated_into::().max(1), + ) * HALF_POWER_COUNT + } + + Self::bonded(stash) + .and_then(Self::ledger) + .map(|l| calc_power(l.active_ring, Self::ring_pool()) + calc_power(l.active_kton, Self::kton_pool())) + .unwrap_or_default() + } + + // MUTABLES (DANGEROUS) + + /// Update the ledger for a controller. This will also update the stash lock. The lock will + /// will lock the entire funds except paying for further transactions. fn update_ledger( controller: &T::AccountId, ledger: &mut StakingLedger, KtonBalanceOf, T::Moment>, @@ -900,7 +1184,7 @@ impl Module { // The amount we'll slash from the validator's stash directly. let own_slash = own_remaining.min(slash); let (mut ring_imbalance, mut kton_imbalance, missing) = - Self::slash_individual(stash, Perbill::from_rational_approximation(own_slash, exposure.own)); // T::Currency::slash(stash, own_slash); + Self::slash_individual(stash, Perbill::from_rational_approximation(own_slash, exposure.own)); let own_slash = own_slash - missing; // The amount remaining that we can't slash from the validator, // that must be taken from the nominators. @@ -936,33 +1220,7 @@ impl Module { (ring_imbalance, kton_imbalance) } - // TODO: there is reserve balance in Balance.Slash, we assuming it is zero for now. - fn slash_individual( - stash: &T::AccountId, - slash_ratio: Perbill, - ) -> (RingNegativeImbalanceOf, KtonNegativeImbalanceOf, ExtendedBalance) { - let controller = Self::bonded(stash).unwrap(); - let mut ledger = Self::ledger(&controller).unwrap(); - - // slash ring - let (ring_imbalance, _) = if !ledger.active_ring.is_zero() { - let slashable_ring = slash_ratio * ledger.active_ring; - let value_slashed = Self::slash_helper(&controller, &mut ledger, StakingBalance::Ring(slashable_ring)); - T::Ring::slash(stash, value_slashed.0) - } else { - (>::zero(), Zero::zero()) - }; - let (kton_imbalance, _) = if !ledger.active_kton.is_zero() { - let slashable_kton = slash_ratio * ledger.active_kton; - let value_slashed = Self::slash_helper(&controller, &mut ledger, StakingBalance::Kton(slashable_kton)); - T::Kton::slash(stash, value_slashed.1) - } else { - (>::zero(), Zero::zero()) - }; - - (ring_imbalance, kton_imbalance, 0) - } - + // TODO: doc fn slash_helper( controller: &T::AccountId, ledger: &mut StakingLedger, KtonBalanceOf, T::Moment>, @@ -995,9 +1253,7 @@ impl Module { // from the nearest expire time if !value_left.is_zero() { // sorted by expire_time from far to near - deposit_items.sort_unstable_by_key(|item| { - TimeStamp::max_value() - item.expire_time.saturated_into::() - }); + deposit_items.sort_unstable_by_key(|item| T::Moment::max_value() - item.expire_time); deposit_items.drain_filter(|item| { if value_left.is_zero() { return false; @@ -1035,6 +1291,46 @@ impl Module { } } + /// 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(stash: &T::AccountId, amount: RingBalanceOf) -> Option> { + let dest = Self::payee(stash); + match dest { + RewardDestination::Controller => { + Self::bonded(stash).and_then(|controller| T::Ring::deposit_into_existing(&controller, amount).ok()) + } + RewardDestination::Stash => T::Ring::deposit_into_existing(stash, amount).ok(), + } + } + + /// Reward a given validator by a specific amount. Add the reward to the validator's, and its + /// nominators' balance, pro-rata based on their exposure, after having removed the validator's + /// pre-payout cut. + fn reward_validator(stash: &T::AccountId, reward: RingBalanceOf) -> RingPositiveImbalanceOf { + let off_the_table = Perbill::from_percent(Self::validators(stash).validator_payment_ratio) * reward; + let reward = reward - off_the_table; + let mut imbalance = >::zero(); + let validator_cut = if reward.is_zero() { + Zero::zero() + } else { + let exposures = Self::stakers(stash); + let total = exposures.total.max(One::one()); + + for i in &exposures.others { + let per_u64 = Perbill::from_rational_approximation(i.value, total); + imbalance.maybe_subsume(Self::make_payout(&i.who, per_u64 * reward)); + } + + let per_u64 = Perbill::from_rational_approximation(exposures.own, total); + per_u64 * reward + }; + imbalance.maybe_subsume(Self::make_payout(stash, validator_cut + off_the_table)); + + imbalance + } + + /// Session has just ended. Provide the validator set for the next session if it's an era-end, along + /// with the exposure of the prior validator set. fn new_session( session_index: SessionIndex, ) -> Option<( @@ -1084,9 +1380,9 @@ impl Module { // Self::deposit_event(RawEvent::Print((T::Cap::get() - T::Ring::total_issuance()).saturated_into::())); let (total_payout, max_payout) = inflation::compute_total_payout::( - era_duration.saturated_into::(), - (T::Time::now() - T::GenesisTime::get()).saturated_into::(), - (T::Cap::get() - T::Ring::total_issuance()).saturated_into::(), + era_duration.saturated_into::(), + (T::Time::now() - T::GenesisTime::get()).saturated_into::(), + (T::Cap::get() - T::Ring::total_issuance()).saturated_into::(), ); let mut total_imbalance = >::zero(); @@ -1120,8 +1416,8 @@ impl Module { *v = start_session_index; }); let bonding_era = { - const BONDING_DURATION_ERA_TO_SECS_RATIO: TimeStamp = 300; - (T::BondingDuration::get() / BONDING_DURATION_ERA_TO_SECS_RATIO) as _ + const BONDING_DURATION_ERA_TO_SECS_RATIO: Moment = 300; + (T::BondingDuration::get().saturated_into::() / BONDING_DURATION_ERA_TO_SECS_RATIO) as EraIndex }; if current_era > bonding_era { @@ -1146,63 +1442,6 @@ impl Module { maybe_new_validators } - /// Reward a given validator by a specific amount. Add the reward to the validator's, and its - /// nominators' balance, pro-rata based on their exposure, after having removed the validator's - /// pre-payout cut. - fn reward_validator(stash: &T::AccountId, reward: RingBalanceOf) -> RingPositiveImbalanceOf { - let off_the_table = Perbill::from_percent(Self::validators(stash).validator_payment_ratio) * reward; - let reward = reward - off_the_table; - let mut imbalance = >::zero(); - let validator_cut = if reward.is_zero() { - Zero::zero() - } else { - let exposures = Self::stakers(stash); - let total = exposures.total.max(One::one()); - - for i in &exposures.others { - let per_u64 = Perbill::from_rational_approximation(i.value, total); - imbalance.maybe_subsume(Self::make_payout(&i.who, per_u64 * reward)); - } - - let per_u64 = Perbill::from_rational_approximation(exposures.own, total); - per_u64 * reward - }; - imbalance.maybe_subsume(Self::make_payout(stash, validator_cut + off_the_table)); - - imbalance - } - - /// 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(stash: &T::AccountId, amount: RingBalanceOf) -> Option> { - let dest = Self::payee(stash); - match dest { - RewardDestination::Controller => { - Self::bonded(stash).and_then(|controller| T::Ring::deposit_into_existing(&controller, amount).ok()) - } - RewardDestination::Stash => T::Ring::deposit_into_existing(stash, amount).ok(), - } - } - - // TODO: Comments - fn power_of(stash: &T::AccountId) -> ExtendedBalance { - // power is a mixture of ring and kton - // power = ring_ratio * POWER_COUNT / 2 + kton_ratio * POWER_COUNT / 2 - fn calc_power>(active: S, pool: S) -> ExtendedBalance { - const HALF_POWER_COUNT: u128 = 1_000_000_000 / 2; - - Perquintill::from_rational_approximation( - active.saturated_into::(), - pool.saturated_into::().max(1), - ) * HALF_POWER_COUNT - } - - Self::bonded(stash) - .and_then(Self::ledger) - .map(|l| calc_power(l.active_ring, Self::ring_pool()) + calc_power(l.active_kton, Self::kton_pool())) - .unwrap_or_default() - } - /// Select a new validator set from the assembled stakers and their role preferences. /// /// Returns the new `SlotStake` value. @@ -1300,10 +1539,9 @@ impl Module { }) .collect::>>(), }; - if exposure.total < slot_stake { - slot_stake = exposure.total; - } - >::insert(&c, exposure.clone()); + slot_stake = slot_stake.min(exposure.total); + + >::insert(&c, exposure); } // Update slot stake. @@ -1330,7 +1568,7 @@ impl Module { /// Remove all associated data of a stash account from the staking system. /// - /// This is called : + /// This is called: /// - Immediately when an account's balance falls below existential deposit. /// - after a `withdraw_unbond()` call that frees all of a stash's bonded balance. fn kill_stash(stash: &T::AccountId) { @@ -1342,6 +1580,18 @@ impl Module { >::remove(stash); } + /// Add reward points to validators using their stash account ID. + /// + /// Validators are keyed by stash account ID and must be in the current elected set. + /// + /// For each element in the iterator the given number of points in u32 is added to the + /// validator, thus duplicates are handled. + /// + /// At the end of the era each the total payout will be distributed among validator + /// relatively to their points. + /// + /// COMPLEXITY: Complexity is `number_of_validator_to_reward x current_elected_len`. + /// If you need to reward lots of validator consider using `reward_by_indices`. pub fn reward_by_ids(validators_points: impl IntoIterator) { CurrentEraPointsEarned::mutate(|rewards| { let current_elected = >::current_elected(); @@ -1353,6 +1603,23 @@ impl Module { }); } + /// Add reward points to validators using their validator index. + /// + /// For each element in the iterator the given number of points in u32 is added to the + /// validator, thus duplicates are handled. + pub fn reward_by_indices(validators_points: impl IntoIterator) { + // TODO: This can be optimised once #3302 is implemented. + let current_elected_len = >::current_elected().len() as u32; + + CurrentEraPointsEarned::mutate(|rewards| { + for (validator_index, points) in validators_points.into_iter() { + if validator_index < current_elected_len { + rewards.add_points_to_index(validator_index, points); + } + } + }); + } + /// Ensures that at the end of the current session there will be a new era. fn ensure_new_era() { match ForceEra::get() { diff --git a/srml/staking/src/mock.rs b/srml/staking/src/mock.rs index d8d432822..1c5e3f7d6 100644 --- a/srml/staking/src/mock.rs +++ b/srml/staking/src/mock.rs @@ -1,6 +1,3 @@ -pub use node_primitives::Balance; -pub use node_runtime::constants::currency::COIN; - use std::{cell::RefCell, collections::HashSet}; use sr_primitives::{ @@ -12,22 +9,16 @@ use sr_primitives::{ use sr_staking_primitives::SessionIndex; use srml_support::{ assert_ok, impl_outer_origin, parameter_types, - traits::{Currency, Get}, - StorageLinkedMap, + traits::{Currency, FindAuthor, Get}, + ConsensusEngineId, StorageLinkedMap, }; use substrate_primitives::{crypto::key_types, H256}; use crate::*; -use darwinia_support::TimeStamp; use phragmen::ExtendedBalance; /// The AccountId alias in this test module. pub type AccountId = u64; -// FIXME: -// replace -// testing::Header.number: u64 -// with -// node_primitives::BlockNumber pub type BlockNumber = u64; /// Module alias @@ -38,6 +29,11 @@ pub type Session = session::Module; pub type Timestamp = timestamp::Module; pub type Staking = Module; +pub const NANO: Balance = 1; +pub const MICRO: Balance = 1_000 * NANO; +pub const MILLI: Balance = 1_000 * MICRO; +pub const COIN: Balance = 1_000 * MILLI; + /// Simple structure that exposes how u64 currency can be represented as... u64. pub struct CurrencyToVoteHandler; impl Convert for CurrencyToVoteHandler { @@ -84,8 +80,9 @@ impl session::SessionHandler for TestSessionHandler { } } -pub fn is_disabled(validator: AccountId) -> bool { - SESSION.with(|d| d.borrow().1.contains(&validator)) +pub fn is_disabled(controller: AccountId) -> bool { + let stash = Staking::ledger(&controller).unwrap().stash; + SESSION.with(|d| d.borrow().1.contains(&stash)) } pub struct ExistentialDeposit; @@ -99,6 +96,17 @@ impl_outer_origin! { pub enum Origin for Test {} } +/// Author of block is always 11 +pub struct Author11; +impl FindAuthor for Author11 { + fn find_author<'a, I>(_digests: I) -> Option + where + I: 'a + IntoIterator, + { + Some(11) + } +} + // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. #[derive(Clone, PartialEq, Eq, Debug)] pub struct Test; @@ -125,11 +133,10 @@ impl system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); } + parameter_types! { - pub const TransferFee: u64 = 0; - pub const CreationFee: u64 = 0; - pub const TransactionBaseFee: u64 = 0; - pub const TransactionByteFee: u64 = 0; + pub const TransferFee: Balance = 0; + pub const CreationFee: Balance = 0; } impl balances::Trait for Test { type Balance = Balance; @@ -142,6 +149,7 @@ impl balances::Trait for Test { type TransferFee = TransferFee; type CreationFee = CreationFee; } + parameter_types! { pub const Period: BlockNumber = 1; pub const Offset: BlockNumber = 0; @@ -164,6 +172,14 @@ impl session::historical::Trait for Test { type FullIdentification = crate::Exposure; type FullIdentificationOf = crate::ExposureOf; } + +impl authorship::Trait for Test { + type FindAuthor = Author11; + type UncleGenerations = UncleGenerations; + type FilterUncle = (); + type EventHandler = Module; +} + parameter_types! { pub const MinimumPeriod: u64 = 5; } @@ -182,11 +198,9 @@ impl kton::Trait for Test { parameter_types! { pub const SessionsPerEra: SessionIndex = 3; - pub const BondingDuration: TimeStamp = 60; -} -parameter_types! { - // decimal 9 + pub const BondingDuration: Moment = 60; pub const CAP: Balance = 10_000_000_000 * COIN; + pub const GenesisTime: Moment = 0; } impl Trait for Test { type Ring = Ring; @@ -195,39 +209,44 @@ impl Trait for Test { type CurrencyToVote = CurrencyToVoteHandler; type RingRewardRemainder = (); type Event = (); - type RingSlash = (); type RingReward = (); - type KtonSlash = (); type KtonReward = (); + type RingSlash = (); + type KtonSlash = (); type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; - type Cap = CAP; - type GenesisTime = (); type SessionInterface = Self; + + type Cap = CAP; + type GenesisTime = GenesisTime; } pub struct ExtBuilder { existential_deposit: Balance, - current_era: EraIndex, - reward: Balance, validator_pool: bool, nominate: bool, validator_count: u32, minimum_validator_count: u32, fair: bool, + num_validators: Option, + invulnerables: Vec, + + current_era: EraIndex, } impl Default for ExtBuilder { fn default() -> Self { Self { existential_deposit: 0, - current_era: 0, - reward: 10, validator_pool: false, nominate: true, - validator_count: 3, + validator_count: 2, minimum_validator_count: 0, fair: true, + num_validators: None, + invulnerables: vec![], + + current_era: 0, } } } @@ -237,10 +256,6 @@ impl ExtBuilder { self.existential_deposit = existential_deposit; self } - pub fn _current_era(mut self, current_era: EraIndex) -> Self { - self.current_era = current_era; - self - } pub fn validator_pool(mut self, validator_pool: bool) -> Self { self.validator_pool = validator_pool; self @@ -261,27 +276,32 @@ impl ExtBuilder { self.fair = is_fair; self } + pub fn num_validators(mut self, num_validators: u32) -> Self { + self.num_validators = Some(num_validators); + self + } + pub fn invulnerables(mut self, invulnerables: Vec) -> Self { + self.invulnerables = invulnerables; + self + } + + fn current_era(mut self, current_era: EraIndex) -> Self { + self.current_era = current_era; + self + } + pub fn set_associated_consts(&self) { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); } pub fn build(self) -> runtime_io::TestExternalities { self.set_associated_consts(); let mut storage = system::GenesisConfig::default().build_storage::().unwrap(); - let balance_factor = if self.existential_deposit > 0 { - 1_000 * COIN - } else { - 1 * COIN - }; - let validators = if self.validator_pool { - vec![10, 20, 30, 40] - } else { - vec![10, 20] - }; + let balance_factor = if self.existential_deposit > 0 { 256 } else { 1 }; - let _ = session::GenesisConfig:: { - keys: validators.iter().map(|x| (*x, UintAuthorityId(*x))).collect(), - } - .assimilate_storage(&mut storage); + let num_validators = self.num_validators.unwrap_or(self.validator_count); + let validators = (0..num_validators) + .map(|x| ((x + 1) * 10 + 1) as u64) + .collect::>(); let _ = balances::GenesisConfig:: { balances: vec![ @@ -299,11 +319,12 @@ impl ExtBuilder { (41, balance_factor * 2000), (100, 2000 * balance_factor), (101, 2000 * balance_factor), + // This allow us to have a total_payout different from 0. + (999, 1_000_000_000_000), ], vesting: vec![], } .assimilate_storage(&mut storage); - let _ = kton::GenesisConfig:: { balances: vec![], vesting: vec![], @@ -319,10 +340,10 @@ impl ExtBuilder { }; let nominated = if self.nominate { vec![11, 21] } else { vec![] }; let _ = GenesisConfig:: { - current_era: self.current_era, + current_era: 0, stakers: vec![ - // (2, 1, 1 * COIN, StakerStatus::::Validator), - (11, 10, 100 * COIN, StakerStatus::::Validator), + // (stash, controller, staked_amount, status) + (11, 10, balance_factor * 1000, StakerStatus::::Validator), (21, 20, stake_21, StakerStatus::::Validator), (31, 30, stake_31, StakerStatus::::Validator), (41, 40, balance_factor * 1000, status_41), @@ -336,15 +357,17 @@ impl ExtBuilder { ], validator_count: self.validator_count, minimum_validator_count: self.minimum_validator_count, - session_reward: Perbill::from_rational_approximation(1_000_000 * self.reward / balance_factor, 1_000_000), - // offline_slash: Perbill::from_percent(5), - // offline_slash_grace: 0, - invulnerables: vec![], + invulnerables: self.invulnerables, slash_reward_fraction: Perbill::from_percent(10), ..Default::default() } .assimilate_storage(&mut storage); + let _ = session::GenesisConfig:: { + keys: validators.iter().map(|x| (*x, UintAuthorityId(*x))).collect(), + } + .assimilate_storage(&mut storage); + let mut ext = runtime_io::TestExternalities::from(storage); ext.execute_with(|| { let validators = Session::validators(); @@ -397,11 +420,6 @@ pub fn check_nominator_exposure(stash: u64) { ); } -pub fn assert_total_expo(stash: u64, val: Balance) { - let expo = Staking::stakers(&stash); - assert_eq!(expo.total, val); -} - pub fn assert_is_stash(acc: u64) { assert!(Staking::bonded(&acc).is_some(), "Not a stash."); } @@ -420,9 +438,8 @@ pub fn bond_validator(acc: u64, val: Balance) { assert_ok!(Staking::validate( Origin::signed(acc), ValidatorPrefs { - node_name: "test".as_bytes().to_vec(), - unstake_threshold: 0, - validator_payment_ratio: 0, + node_name: "StakingTest".as_bytes().to_vec(), + ..Default::default() } )); } @@ -441,11 +458,20 @@ pub fn bond_nominator(acc: u64, val: Balance, target: Vec) { assert_ok!(Staking::nominate(Origin::signed(acc), target)); } +pub fn advance_session() { + let current_index = Session::current_index(); + start_session(current_index + 1); +} + pub fn start_session(session_index: SessionIndex) { - for i in 0..(session_index - Session::current_index()) { + // Compensate for session delay + let session_index = session_index + 1; + for i in Session::current_index()..session_index { System::set_block_number((i + 1).into()); + Timestamp::set_timestamp(System::block_number() * 1000); Session::on_initialize(System::block_number()); } + assert_eq!(Session::current_index(), session_index); } @@ -453,3 +479,30 @@ pub fn start_era(era_index: EraIndex) { start_session((era_index * 3).into()); assert_eq!(Staking::current_era(), era_index); } + +// TODO +pub fn current_total_payout_for_duration(duration: u64) -> Balance { + // inflation::compute_total_payout( + // era_duration.saturated_into::(), + // (>::Time::now() - >::GenesisTime::get()).saturated_into::(), + // (>::Cap::get() - Ring::total_issuance()).saturated_into::(), + // ) + // .0 + unimplemented!() +} + +pub fn reward_all_elected() { + let rewards = >::current_elected() + .iter() + .map(|v| (*v, 1)) + .collect::>(); + + >::reward_by_ids(rewards) +} + +pub fn validator_controllers() -> Vec { + Session::validators() + .into_iter() + .map(|s| Staking::bonded(&s).expect("no controller for validator")) + .collect() +} diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index d4138029f..dfdadd18e 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -1,15 +1,26 @@ -use srml_support::{assert_err, assert_ok, traits::Currency}; - -use super::*; -use crate::mock::*; -use darwinia_support::{BalanceLock, NormalLock, StakingLock, WithdrawLock, WithdrawReason, WithdrawReasons}; - -// gen_paired_account!(a(1), b(2), m(12)); -// will create stash `a` and controller `b` -// `a` has 100 Ring and 100 Kton -// promise for `m` month with 50 Ring and 50 Kton -// `m` can be ignore, and it wont perform `bond` action -// gen_paired_account!(a(1), b(2)); +use sr_primitives::traits::OnInitialize; +use srml_support::{ + assert_eq_uvec, assert_err, assert_noop, assert_ok, + traits::{Currency, ReservableCurrency}, +}; + +use crate::{mock::*, *}; +use darwinia_support::{BalanceLock, NormalLock, StakingLock, WithdrawLock, WithdrawReasons}; + +/// gen_paired_account!(a(1), b(2), m(12)); +/// will create stash `a` and controller `b` +/// `a` has 100 Ring and 100 Kton +/// promise for `m` month with 50 Ring and 50 Kton +/// +/// `m` can be ignore, this won't create variable `m` +/// ```rust +/// gen_parired_account!(a(1), b(2), 12); +/// ``` +/// +/// `m(12)` can be ignore, and it won't perform `bond` action +/// ```rust +/// gen_paired_account!(a(1), b(2)); +/// ``` macro_rules! gen_paired_account { ($stash:ident($stash_id:expr), $controller:ident($controller_id:expr), $promise_month:ident($how_long:expr)) => { #[allow(non_snake_case, unused)] @@ -67,75 +78,2153 @@ macro_rules! gen_paired_account { } #[test] -fn test_env_build() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +fn force_unstake_works() { + // Verifies initial conditions of mock. + ExtBuilder::default().build().execute_with(|| { + // Account 11 is stashed and locked, and account 10 is the controller. + assert_eq!(Staking::bonded(&11), Some(10)); + // Cant transfer. + assert_noop!( + Ring::transfer(Origin::signed(11), 1, 10), + "account liquidity restrictions prevent withdrawal", + ); + // Force unstake requires root. + assert_noop!(Staking::force_unstake(Origin::signed(11), 11), "RequireRootOrigin"); + // We now force them to unstake. + assert_ok!(Staking::force_unstake(Origin::ROOT, 11)); + // No longer bonded. + assert_eq!(Staking::bonded(&11), None); + // Transfer works. + assert_ok!(Ring::transfer(Origin::signed(11), 1, 10)); + }); +} + +#[test] +fn basic_setup_works() { + // Verifies initial conditions of mock. + ExtBuilder::default().build().execute_with(|| { + // Account 11 is stashed and locked, and account 10 is the controller. + assert_eq!(Staking::bonded(&11), Some(10)); + // Account 21 is stashed and locked, and account 20 is the controller. + assert_eq!(Staking::bonded(&21), Some(20)); + // Account 1 is not a stashed. + assert_eq!(Staking::bonded(&1), None); + + // Account 10 controls the stash from account 11, which is 100 * balance_factor units. + assert_eq!( + Staking::ledger(&10), + Some(StakingLedger { + stash: 11, + active_ring: 1000, + active_deposit_ring: 0, + active_kton: 0, + deposit_items: vec![], + ring_staking_lock: StakingLock { + staking_amount: 1000, + unbondings: vec![], + }, + kton_staking_lock: Default::default(), + }) + ); + // Account 20 controls the stash from account 21, which is 200 * balance_factor units. + assert_eq!( + Staking::ledger(&20), + Some(StakingLedger { + stash: 21, + active_ring: 1000, + active_deposit_ring: 0, + active_kton: 0, + deposit_items: vec![], + ring_staking_lock: StakingLock { + staking_amount: 1000, + unbondings: vec![], + }, + kton_staking_lock: Default::default(), + }) + ); + // Account 1 does not control any stash. + assert_eq!(Staking::ledger(&1), None); + + // ValidatorPrefs are default. + { + let validator_prefs = ValidatorPrefs { + node_name: "Darwinia Node".bytes().collect(), + ..Default::default() + }; + assert_eq!( + >::enumerate().collect::>(), + vec![ + (31, validator_prefs.clone()), + (21, validator_prefs.clone()), + (11, validator_prefs.clone()), + ] + ); + } + + assert_eq!( + Staking::ledger(100), + Some(StakingLedger { + stash: 101, + active_ring: 500, + active_deposit_ring: 0, + active_kton: 0, + deposit_items: vec![], + ring_staking_lock: StakingLock { + staking_amount: 500, + unbondings: vec![], + }, + kton_staking_lock: Default::default(), + }) + ); + assert_eq!(Staking::nominators(101), vec![11, 21]); + + if cfg!(feature = "equalize") { + let vote_form_101_per_validator = Staking::power_of(&101) / 2; + + let exposure_own_of_11 = Staking::power_of(&11); + let exposure_total_of_11 = exposure_own_of_11 + vote_form_101_per_validator; + + let exposure_own_of_21 = Staking::power_of(&21); + let exposure_total_of_21 = exposure_own_of_21 + vote_form_101_per_validator; + + assert_eq!( + Staking::stakers(11), + Exposure { + total: exposure_total_of_11, + own: exposure_own_of_11, + others: vec![IndividualExposure { + who: 101, + value: vote_form_101_per_validator, + }], + } + ); + assert_eq!( + Staking::stakers(21), + Exposure { + total: exposure_total_of_21, + own: exposure_own_of_21, + others: vec![IndividualExposure { + who: 101, + value: vote_form_101_per_validator, + }], + } + ); + // initial slot_stake. + assert_eq!(exposure_total_of_11, exposure_total_of_21); + assert_eq!(Staking::slot_stake(), exposure_total_of_11); + } else { + let vote_of_101 = Staking::power_of(&101); + + let exposure_own_of_11 = Staking::power_of(&11); + let exposure_others_of_11 = vote_of_101 * 4 / 1; + let exposure_total_of_11 = exposure_own_of_11 + exposure_others_of_11; + + assert_eq!( + Staking::stakers(11), + Exposure { + total: exposure_total_of_11, + own: exposure_own_of_11, + others: vec![IndividualExposure { + who: 101, + value: exposure_others_of_11, + }], + } + ); + assert_eq!( + Staking::stakers(21), + Exposure { + total: Staking::power_of(&21), + own: 1000, + others: vec![IndividualExposure { + who: 101, + value: vote_of_101 * 4 / 3, + }], + } + ); + // initial slot_stake. + assert_eq!(Staking::slot_stake(), exposure_total_of_11); + } + + // The number of validators required. + assert_eq!(Staking::validator_count(), 2); + + // Initial Era and session. + assert_eq!(Staking::current_era(), 0); + + // Account 10 has `balance_factor` free balance. + assert_eq!(Ring::free_balance(&10), 1); + assert_eq!(Ring::free_balance(&10), 1); + + // New era is not being forced. + assert_eq!(Staking::force_era(), Forcing::NotForcing); + + // All exposures must be correct. + check_exposure_all(); + check_nominator_all(); + }); +} + +#[test] +fn change_controller_works() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Staking::bonded(&11), Some(10)); + + assert!(>::enumerate() + .map(|(c, _)| c) + .collect::>() + .contains(&11)); + // 10 can control 11 who is initially a validator. + assert_ok!(Staking::chill(Origin::signed(10))); + assert!(!>::enumerate() + .map(|(c, _)| c) + .collect::>() + .contains(&11)); + + assert_ok!(Staking::set_controller(Origin::signed(11), 5)); + + start_era(1); + + assert_noop!( + Staking::validate( + Origin::signed(10), + ValidatorPrefs { + node_name: "Darwinia Node".bytes().collect(), + ..Default::default() + } + ), + err::CONTROLLER_INVALID, + ); + assert_ok!(Staking::validate( + Origin::signed(5), + ValidatorPrefs { + node_name: "Darwinia Node".bytes().collect(), + ..Default::default() + } + )); + }) +} + +// TODO +#[test] +fn rewards_should_work() { + // should check that: + // * rewards get recorded per session + // * rewards get paid per Era + // * Check that nominators are also rewarded + ExtBuilder::default().nominate(false).build().execute_with(|| { + // Init some balances. + let _ = Ring::make_free_balance_be(&2, 500); + + let delay = 1000; + let init_balance_2 = Ring::total_balance(&2); + let init_balance_10 = Ring::total_balance(&10); + let init_balance_11 = Ring::total_balance(&11); + + // Set payee to controller. + assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); + + // Initial config should be correct. + assert_eq!(Staking::current_era(), 0); + assert_eq!(Session::current_index(), 0); + + // Add a dummy nominator. + // + // Equal division indicates that the reward will be equally divided among validator and + // nominator. + >::insert( + &11, + Exposure { + own: 500, + total: 1000, + others: vec![IndividualExposure { who: 2, value: 500 }], + }, + ); + + >::insert(&2, RewardDestination::Stash); + assert_eq!(Staking::payee(2), RewardDestination::Stash); + assert_eq!(Staking::payee(11), RewardDestination::Controller); + + let mut block = 3; // Block 3 => Session 1 => Era 0. + System::set_block_number(block); + Timestamp::set_timestamp(block * 5000); // on time. + Session::on_initialize(System::block_number()); + assert_eq!(Staking::current_era(), 0); + assert_eq!(Session::current_index(), 1); + >::reward_by_ids(vec![(11, 50)]); + >::reward_by_ids(vec![(11, 50)]); + // This is the second validator of the current elected set. + >::reward_by_ids(vec![(21, 50)]); + // This must be no-op as it is not an elected validator. + >::reward_by_ids(vec![(1001, 10_000)]); + + // Compute total payout now for whole duration as other parameter won't change. + // let total_payout = current_total_payout_for_duration(9 * 5 * 1000); + // assert!(total_payout > 10); // Test is meaningful if reward something + + // No reward yet + assert_eq!(Ring::total_balance(&2), init_balance_2); + assert_eq!(Ring::total_balance(&10), init_balance_10); + assert_eq!(Ring::total_balance(&11), init_balance_11); + + block = 6; // Block 6 => Session 2 => Era 0. + System::set_block_number(block); + Timestamp::set_timestamp(block * 5000 + delay); // a little late. + Session::on_initialize(System::block_number()); + assert_eq!(Staking::current_era(), 0); + assert_eq!(Session::current_index(), 2); + + block = 9; // Block 9 => Session 3 => Era 1. + System::set_block_number(block); + Timestamp::set_timestamp(block * 5000); // back to being on time. no delays. + Session::on_initialize(System::block_number()); + assert_eq!(Staking::current_era(), 1); + assert_eq!(Session::current_index(), 3); + + // 11 validator has 2/3 of the total rewards and half half for it and its nominator. + // assert_eq_error_rate!(Balances::total_balance(&2), init_balance_2 + total_payout / 3, 1); + // assert_eq_error_rate!(Balances::total_balance(&10), init_balance_10 + total_payout / 3, 1); + assert_eq!(Ring::total_balance(&11), init_balance_11); + }); +} + +// TODO +#[test] +fn multi_era_reward_should_work() { + // Should check that: + // The value of current_session_reward is set at the end of each era, based on + // slot_stake and session_reward. + ExtBuilder::default().nominate(false).build().execute_with(|| { + let _init_balance_10 = Ring::total_balance(&10); + + // Set payee to controller. + assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); + + // Compute now as other parameter won't change + // let total_payout_0 = current_total_payout_for_duration(3000); + // assert!(total_payout_0 > 10); // Test is meaningfull if reward something + >::reward_by_ids(vec![(11, 1)]); + + start_session(0); + start_session(1); + start_session(2); + start_session(3); + + assert_eq!(Staking::current_era(), 1); + // assert_eq!(Ring::total_balance(&10), init_balance_10 + total_payout_0); + + start_session(4); + + // let total_payout_1 = current_total_payout_for_duration(3000); + // assert!(total_payout_1 > 10); // Test is meaningfull if reward something + >::reward_by_ids(vec![(11, 101)]); + + // New era is triggered here. + start_session(5); + + // Pay time. + // assert_eq!( + // Ring::total_balance(&10), + // init_balance_10 + total_payout_0 + total_payout_1 + // ); + }); +} + +#[test] +fn staking_should_work() { + // should test: + // * new validators can be added to the default set + // * new ones will be chosen per era + // * either one can unlock the stash and back-down from being a validator via `chill`ing. + ExtBuilder::default() + .nominate(false) + .fair(false) // to give 20 more staked value + .build() + .execute_with(|| { + Timestamp::set_timestamp(1); // Initialize time. + + // remember + compare this along with the test. + assert_eq_uvec!(validator_controllers(), vec![20, 10]); + + // Put some money in account that we'll use. + for i in 1..5 { let _ = Ring::make_free_balance_be(&i, 2000); } + + // --- Block 1: + start_session(1); + // Add a new candidate for being a validator. account 3 controlled by 4. + assert_ok!(Staking::bond(Origin::signed(3), 4, StakingBalance::Ring(1500), RewardDestination::Controller, 0)); + assert_ok!(Staking::validate( + Origin::signed(4), + ValidatorPrefs { + node_name: "Darwinia Node".bytes().collect(), + ..Default::default() + }, + )); + + // No effects will be seen so far. + assert_eq_uvec!(validator_controllers(), vec![20, 10]); + + // --- Block 2: + start_session(2); + + // No effects will be seen so far. Era has not been yet triggered. + assert_eq_uvec!(validator_controllers(), vec![20, 10]); + + + // --- Block 3: the validators will now be queued. + start_session(3); + assert_eq!(Staking::current_era(), 1); + + // --- Block 4: the validators will now be changed. + start_session(4); + + assert_eq_uvec!(validator_controllers(), vec![20, 4]); + // --- Block 4: Unstake 4 as a validator, freeing up the balance stashed in 3. + // 4 will chill. + Staking::chill(Origin::signed(4)).unwrap(); + + // --- Block 5: nothing. 4 is still there. + start_session(5); + assert_eq_uvec!(validator_controllers(), vec![20, 4]); + + // --- Block 6: 4 will not be a validator. + start_session(7); + assert_eq_uvec!(validator_controllers(), vec![20, 10]); + + // Note: the stashed value of 4 is still lock. + assert_eq!( + Staking::ledger(&4).unwrap(), + StakingLedger { + stash: 3, + active_ring: 1500, + active_deposit_ring: 0, + active_kton: 0, + deposit_items: vec![], + ring_staking_lock: StakingLock { + staking_amount: 1500, + unbondings: vec![], + }, + kton_staking_lock: Default::default(), + }, + ); + // e.g. It cannot spend more than 500 that it has free from the total 2000. + assert_noop!(Ring::reserve(&3, 501), "account liquidity restrictions prevent withdrawal"); + assert_ok!(Ring::reserve(&3, 409)); + }); +} + +#[test] +fn less_than_needed_candidates_works() { + ExtBuilder::default() + .minimum_validator_count(1) + .validator_count(4) + .nominate(false) + .num_validators(3) + .build() + .execute_with(|| { + assert_eq!(Staking::validator_count(), 4); + assert_eq!(Staking::minimum_validator_count(), 1); + assert_eq_uvec!(validator_controllers(), vec![30, 20, 10]); + + start_era(1); + + // Previous set is selected. NO election algorithm is even executed. + assert_eq_uvec!(validator_controllers(), vec![30, 20, 10]); + + // But the exposure is updated in a simple way. No external votes exists. + // This is purely self-vote. + assert_eq!(Staking::stakers(10).others.len(), 0); + assert_eq!(Staking::stakers(20).others.len(), 0); + assert_eq!(Staking::stakers(30).others.len(), 0); + check_exposure_all(); + check_nominator_all(); + }); +} + +#[test] +fn no_candidate_emergency_condition() { + ExtBuilder::default() + .minimum_validator_count(10) + .validator_count(15) + .num_validators(4) + .validator_pool(true) + .nominate(false) + .build() + .execute_with(|| { + // Initial validators. + assert_eq_uvec!(validator_controllers(), vec![10, 20, 30, 40]); + + // Set the minimum validator count. + ::MinimumValidatorCount::put(10); + ::ValidatorCount::put(15); + assert_eq!(Staking::validator_count(), 15); + + let _ = Staking::chill(Origin::signed(10)); + + // Trigger era. + System::set_block_number(1); + Session::on_initialize(System::block_number()); + + // Previous ones are elected. chill is invalidates. TODO: #2494 + assert_eq_uvec!(validator_controllers(), vec![10, 20, 30, 40]); + assert_eq!(Staking::current_elected().len(), 0); + }); +} + +// TODO +//#[test] +//fn nominating_and_rewards_should_work() { +// // PHRAGMEN OUTPUT: running this test with the reference impl gives: +// // +// // Sequential Phragmén gives +// // 10 is elected with stake 2200.0 and score 0.0003333333333333333 +// // 20 is elected with stake 1800.0 and score 0.0005555555555555556 +// +// // 10 has load 0.0003333333333333333 and supported +// // 10 with stake 1000.0 +// // 20 has load 0.0005555555555555556 and supported +// // 20 with stake 1000.0 +// // 30 has load 0 and supported +// // 30 with stake 0 +// // 40 has load 0 and supported +// // 40 with stake 0 +// // 2 has load 0.0005555555555555556 and supported +// // 10 with stake 600.0 20 with stake 400.0 30 with stake 0.0 +// // 4 has load 0.0005555555555555556 and supported +// // 10 with stake 600.0 20 with stake 400.0 40 with stake 0.0 +// +// // Sequential Phragmén with post processing gives +// // 10 is elected with stake 2000.0 and score 0.0003333333333333333 +// // 20 is elected with stake 2000.0 and score 0.0005555555555555556 +// +// // 10 has load 0.0003333333333333333 and supported +// // 10 with stake 1000.0 +// // 20 has load 0.0005555555555555556 and supported +// // 20 with stake 1000.0 +// // 30 has load 0 and supported +// // 30 with stake 0 +// // 40 has load 0 and supported +// // 40 with stake 0 +// // 2 has load 0.0005555555555555556 and supported +// // 10 with stake 400.0 20 with stake 600.0 30 with stake 0 +// // 4 has load 0.0005555555555555556 and supported +// // 10 with stake 600.0 20 with stake 400.0 40 with stake 0.0 +// ExtBuilder::default() +// .nominate(false) +// .validator_pool(true) +// .build() +// .execute_with(|| { +// // initial validators -- everyone is actually even. +// assert_eq_uvec!(validator_controllers(), vec![40, 30]); +// +// // Set payee to controller +// assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); +// assert_ok!(Staking::set_payee(Origin::signed(20), RewardDestination::Controller)); +// assert_ok!(Staking::set_payee(Origin::signed(30), RewardDestination::Controller)); +// assert_ok!(Staking::set_payee(Origin::signed(40), RewardDestination::Controller)); +// +// // give the man some money +// let initial_balance = 1000; +// for i in [1, 2, 3, 4, 5, 10, 11, 20, 21].iter() { +// let _ = Balances::make_free_balance_be(i, initial_balance); +// } +// +// // bond two account pairs and state interest in nomination. +// // 2 will nominate for 10, 20, 30 +// assert_ok!(Staking::bond(Origin::signed(1), 2, 1000, RewardDestination::Controller)); +// assert_ok!(Staking::nominate(Origin::signed(2), vec![11, 21, 31])); +// // 4 will nominate for 10, 20, 40 +// assert_ok!(Staking::bond(Origin::signed(3), 4, 1000, RewardDestination::Controller)); +// assert_ok!(Staking::nominate(Origin::signed(4), vec![11, 21, 41])); +// +// // the total reward for era 0 +// let total_payout_0 = current_total_payout_for_duration(3000); +// assert!(total_payout_0 > 100); // Test is meaningfull if reward something +// >::reward_by_ids(vec![(41, 1)]); +// >::reward_by_ids(vec![(31, 1)]); +// >::reward_by_ids(vec![(21, 10)]); // must be no-op +// >::reward_by_ids(vec![(11, 10)]); // must be no-op +// +// start_era(1); +// +// // 10 and 20 have more votes, they will be chosen by phragmen. +// assert_eq_uvec!(validator_controllers(), vec![20, 10]); +// +// // OLD validators must have already received some rewards. +// assert_eq!(Balances::total_balance(&40), 1 + total_payout_0 / 2); +// assert_eq!(Balances::total_balance(&30), 1 + total_payout_0 / 2); +// +// // ------ check the staked value of all parties. +// +// if cfg!(feature = "equalize") { +// // total expo of 10, with 1200 coming from nominators (externals), according to phragmen. +// assert_eq!(Staking::stakers(11).own, 1000); +// assert_eq_error_rate!(Staking::stakers(11).total, 1000 + 1000, 2); +// // 2 and 4 supported 10, each with stake 600, according to phragmen. +// assert_eq!( +// Staking::stakers(11).others.iter().map(|e| e.value).collect::>>(), +// vec![600, 400] +// ); +// assert_eq!( +// Staking::stakers(11).others.iter().map(|e| e.who).collect::>(), +// vec![3, 1] +// ); +// // total expo of 20, with 500 coming from nominators (externals), according to phragmen. +// assert_eq!(Staking::stakers(21).own, 1000); +// assert_eq_error_rate!(Staking::stakers(21).total, 1000 + 1000, 2); +// // 2 and 4 supported 20, each with stake 250, according to phragmen. +// assert_eq!( +// Staking::stakers(21).others.iter().map(|e| e.value).collect::>>(), +// vec![400, 600] +// ); +// assert_eq!( +// Staking::stakers(21).others.iter().map(|e| e.who).collect::>(), +// vec![3, 1] +// ); +// } else { +// // total expo of 10, with 1200 coming from nominators (externals), according to phragmen. +// assert_eq!(Staking::stakers(11).own, 1000); +// assert_eq!(Staking::stakers(11).total, 1000 + 800); +// // 2 and 4 supported 10, each with stake 600, according to phragmen. +// assert_eq!( +// Staking::stakers(11).others.iter().map(|e| e.value).collect::>>(), +// vec![400, 400] +// ); +// assert_eq!( +// Staking::stakers(11).others.iter().map(|e| e.who).collect::>(), +// vec![3, 1] +// ); +// // total expo of 20, with 500 coming from nominators (externals), according to phragmen. +// assert_eq!(Staking::stakers(21).own, 1000); +// assert_eq_error_rate!(Staking::stakers(21).total, 1000 + 1200, 2); +// // 2 and 4 supported 20, each with stake 250, according to phragmen. +// assert_eq!( +// Staking::stakers(21).others.iter().map(|e| e.value).collect::>>(), +// vec![600, 600] +// ); +// assert_eq!( +// Staking::stakers(21).others.iter().map(|e| e.who).collect::>(), +// vec![3, 1] +// ); +// } +// +// // They are not chosen anymore +// assert_eq!(Staking::stakers(31).total, 0); +// assert_eq!(Staking::stakers(41).total, 0); +// +// // the total reward for era 1 +// let total_payout_1 = current_total_payout_for_duration(3000); +// assert!(total_payout_1 > 100); // Test is meaningfull if reward something +// >::reward_by_ids(vec![(41, 10)]); // must be no-op +// >::reward_by_ids(vec![(31, 10)]); // must be no-op +// >::reward_by_ids(vec![(21, 2)]); +// >::reward_by_ids(vec![(11, 1)]); +// +// start_era(2); +// +// // nothing else will happen, era ends and rewards are paid again, +// // it is expected that nominators will also be paid. See below +// +// let payout_for_10 = total_payout_1 / 3; +// let payout_for_20 = 2 * total_payout_1 / 3; +// if cfg!(feature = "equalize") { +// // Nominator 2: has [400 / 2000 ~ 1 / 5 from 10] + [600 / 2000 ~ 3 / 10 from 20]'s reward. +// assert_eq_error_rate!( +// Balances::total_balance(&2), +// initial_balance + payout_for_10 / 5 + payout_for_20 * 3 / 10, +// 2, +// ); +// // Nominator 4: has [400 / 2000 ~ 1 / 5 from 20] + [600 / 2000 ~ 3 / 10 from 10]'s reward. +// assert_eq_error_rate!( +// Balances::total_balance(&4), +// initial_balance + payout_for_20 / 5 + payout_for_10 * 3 / 10, +// 2, +// ); +// +// // Validator 10: got 1000 / 2000 external stake. +// assert_eq_error_rate!( +// Balances::total_balance(&10), +// initial_balance + payout_for_10 / 2, +// 1, +// ); +// // Validator 20: got 1000 / 2000 external stake. +// assert_eq_error_rate!( +// Balances::total_balance(&20), +// initial_balance + payout_for_20 / 2, +// 1, +// ); +// } else { +// // Nominator 2: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 20]'s reward. ==> 2/9 + 3/11 +// assert_eq_error_rate!( +// Balances::total_balance(&2), +// initial_balance + (2 * payout_for_10 / 9 + 3 * payout_for_20 / 11), +// 1, +// ); +// // Nominator 4: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 20]'s reward. ==> 2/9 + 3/11 +// assert_eq_error_rate!( +// Balances::total_balance(&4), +// initial_balance + (2 * payout_for_10 / 9 + 3 * payout_for_20 / 11), +// 1, +// ); +// +// // Validator 10: got 800 / 1800 external stake => 8/18 =? 4/9 => Validator's share = 5/9 +// assert_eq_error_rate!( +// Balances::total_balance(&10), +// initial_balance + 5 * payout_for_10 / 9, +// 1, +// ); +// // Validator 20: got 1200 / 2200 external stake => 12/22 =? 6/11 => Validator's share = 5/11 +// assert_eq_error_rate!( +// Balances::total_balance(&20), +// initial_balance + 5 * payout_for_20 / 11, +// 1, +// ); +// } +// +// check_exposure_all(); +// check_nominator_all(); +// }); +//} + +// TODO +//#[test] +//fn nominators_also_get_slashed() { +// // A nominator should be slashed if the validator they nominated is slashed +// // Here is the breakdown of roles: +// // 10 - is the controller of 11 +// // 11 - is the stash. +// // 2 - is the nominator of 20, 10 +// ExtBuilder::default().nominate(false).build().execute_with(|| { +// assert_eq!(Staking::validator_count(), 2); +// +// // Set payee to controller +// assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); +// +// // give the man some money. +// let initial_balance = 1000; +// for i in [1, 2, 3, 10].iter() { +// let _ = Balances::make_free_balance_be(i, initial_balance); +// } +// +// // 2 will nominate for 10, 20 +// let nominator_stake = 500; +// assert_ok!(Staking::bond(Origin::signed(1), 2, nominator_stake, RewardDestination::default())); +// assert_ok!(Staking::nominate(Origin::signed(2), vec![20, 10])); +// +// let total_payout = current_total_payout_for_duration(3000); +// assert!(total_payout > 100); // Test is meaningfull if reward something +// >::reward_by_ids(vec![(11, 1)]); +// +// // new era, pay rewards, +// start_era(1); +// +// // Nominator stash didn't collect any. +// assert_eq!(Balances::total_balance(&2), initial_balance); +// +// // 10 goes offline +// Staking::on_offence( +// &[OffenceDetails { +// offender: ( +// 11, +// Staking::stakers(&11), +// ), +// reporters: vec![], +// }], +// &[Perbill::from_percent(5)], +// ); +// let expo = Staking::stakers(11); +// let slash_value = 50; +// let total_slash = expo.total.min(slash_value); +// let validator_slash = expo.own.min(total_slash); +// let nominator_slash = nominator_stake.min(total_slash - validator_slash); +// +// // initial + first era reward + slash +// assert_eq!(Balances::total_balance(&11), initial_balance - validator_slash); +// assert_eq!(Balances::total_balance(&2), initial_balance - nominator_slash); +// check_exposure_all(); +// check_nominator_all(); +// // Because slashing happened. +// assert!(is_disabled(10)); +// }); +//} + +#[test] +fn double_staking_should_fail() { + // should test (in the same order): + // * an account already bonded as stash cannot be be stashed again. + // * an account already bonded as stash cannot nominate. + // * an account already bonded as controller can nominate. + ExtBuilder::default().build().execute_with(|| { + let arbitrary_value = 5; + // 2 = controller, 1 stashed => ok + assert_ok!(Staking::bond( + Origin::signed(1), + 2, + StakingBalance::Ring(arbitrary_value), + RewardDestination::default(), + 0, + )); + // 4 = not used so far, 1 stashed => not allowed. + assert_noop!( + Staking::bond( + Origin::signed(1), + 4, + StakingBalance::Ring(arbitrary_value), + RewardDestination::default(), + 0, + ), + err::STASH_ALREADY_BONDED, + ); + // 1 = stashed => attempting to nominate should fail. + assert_noop!(Staking::nominate(Origin::signed(1), vec![1]), err::CONTROLLER_INVALID); + // 2 = controller => nominating should work. + assert_ok!(Staking::nominate(Origin::signed(2), vec![1])); + }); +} + +#[test] +fn double_controlling_should_fail() { + // should test (in the same order): + // * an account already bonded as controller CANNOT be reused as the controller of another account. + ExtBuilder::default().build().execute_with(|| { + let arbitrary_value = 5; + // 2 = controller, 1 stashed => ok + assert_ok!(Staking::bond( + Origin::signed(1), + 2, + StakingBalance::Ring(arbitrary_value), + RewardDestination::default(), + 0, + )); + // 2 = controller, 3 stashed (Note that 2 is reused.) => no-op + assert_noop!( + Staking::bond( + Origin::signed(3), + 2, + StakingBalance::Ring(arbitrary_value), + RewardDestination::default(), + 0, + ), + err::CONTROLLER_ALREADY_PAIRED, + ); + }); +} + +#[test] +fn session_and_eras_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Staking::current_era(), 0); + + // Block 1: No change. + start_session(0); + assert_eq!(Session::current_index(), 1); + assert_eq!(Staking::current_era(), 0); + + // Block 2: Simple era change. + start_session(2); + assert_eq!(Session::current_index(), 3); + assert_eq!(Staking::current_era(), 1); + + // Block 3: Schedule an era length change; no visible changes. + start_session(3); + assert_eq!(Session::current_index(), 4); + assert_eq!(Staking::current_era(), 1); + + // Block 4: Era change kicks in. + start_session(5); + assert_eq!(Session::current_index(), 6); + assert_eq!(Staking::current_era(), 2); + + // Block 5: No change. + start_session(6); + assert_eq!(Session::current_index(), 7); + assert_eq!(Staking::current_era(), 2); + + // Block 6: No change. + start_session(7); + assert_eq!(Session::current_index(), 8); + assert_eq!(Staking::current_era(), 2); + + // Block 7: Era increment. + start_session(8); + assert_eq!(Session::current_index(), 9); + assert_eq!(Staking::current_era(), 3); + }); +} + +#[test] +fn forcing_new_era_works() { + ExtBuilder::default().build().execute_with(|| { + // normal flow of session. + assert_eq!(Staking::current_era(), 0); + start_session(0); + assert_eq!(Staking::current_era(), 0); + start_session(1); + assert_eq!(Staking::current_era(), 0); + start_session(2); + assert_eq!(Staking::current_era(), 1); + + // no era change. + ForceEra::put(Forcing::ForceNone); + start_session(3); + assert_eq!(Staking::current_era(), 1); + start_session(4); + assert_eq!(Staking::current_era(), 1); + start_session(5); + assert_eq!(Staking::current_era(), 1); + start_session(6); + assert_eq!(Staking::current_era(), 1); + + // back to normal. + // this immediately starts a new session. + ForceEra::put(Forcing::NotForcing); + start_session(7); + assert_eq!(Staking::current_era(), 2); + start_session(8); + assert_eq!(Staking::current_era(), 2); + + // forceful change + ForceEra::put(Forcing::ForceAlways); + start_session(9); + assert_eq!(Staking::current_era(), 3); + start_session(10); + assert_eq!(Staking::current_era(), 4); + start_session(11); + assert_eq!(Staking::current_era(), 5); + + // just one forceful change + ForceEra::put(Forcing::ForceNew); + start_session(12); + assert_eq!(Staking::current_era(), 6); + + assert_eq!(ForceEra::get(), Forcing::NotForcing); + start_session(13); + assert_eq!(Staking::current_era(), 6); + }); +} + +#[test] +fn cannot_transfer_staked_balance() { + // Tests that a stash account cannot transfer funds + ExtBuilder::default().nominate(false).build().execute_with(|| { + // Confirm account 11 is stashed + assert_eq!(Staking::bonded(&11), Some(10)); + // Confirm account 11 has some free balance + assert_eq!(Ring::free_balance(&11), 1000); + // Confirm account 11 (via controller 10) is totally staked + assert_eq!(Staking::stakers(&11).total, Staking::power_of(&11)); + // Confirm account 11 cannot transfer as a result + assert_noop!( + Ring::transfer(Origin::signed(11), 20, 1), + "account liquidity restrictions prevent withdrawal", + ); + + // Give account 11 extra free balance + let _ = Ring::make_free_balance_be(&11, 10000); + // Confirm that account 11 can now transfer some balance + assert_ok!(Ring::transfer(Origin::signed(11), 20, 1)); + }); +} + +#[test] +fn cannot_transfer_staked_balance_2() { + // Tests that a stash account cannot transfer funds + // Same test as above but with 20, and more accurate. + // 21 has 2000 free balance but 1000 at stake + ExtBuilder::default() + .nominate(false) + .fair(true) + .build() + .execute_with(|| { + // Confirm account 21 is stashed + assert_eq!(Staking::bonded(&21), Some(20)); + // Confirm account 21 has some free balance + assert_eq!(Ring::free_balance(&21), 2000); + // Confirm account 21 (via controller 20) is totally staked + assert_eq!(Staking::stakers(&21).total, Staking::power_of(&11)); + // Confirm account 21 can transfer at most 1000 + assert_noop!( + Ring::transfer(Origin::signed(21), 20, 1001), + "account liquidity restrictions prevent withdrawal", + ); + assert_ok!(Ring::transfer(Origin::signed(21), 20, 1000)); + }); +} + +#[test] +fn cannot_reserve_staked_balance() { + // Checks that a bonded account cannot reserve balance from free balance + ExtBuilder::default().build().execute_with(|| { + // Confirm account 11 is stashed + assert_eq!(Staking::bonded(&11), Some(10)); + // Confirm account 11 has some free balance + assert_eq!(Ring::free_balance(&11), 1000); + // Confirm account 11 (via controller 10) is totally staked + assert_eq!(Staking::stakers(&11).own, Staking::power_of(&11)); + // Confirm account 11 cannot transfer as a result + assert_noop!( + Ring::reserve(&11, 1), + "account liquidity restrictions prevent withdrawal" + ); + + // Give account 11 extra free balance + let _ = Ring::make_free_balance_be(&11, 10000); + // Confirm account 11 can now reserve balance + assert_ok!(Ring::reserve(&11, 1)); + }); +} + +// TODO +//#[test] +//fn reward_destination_works() { +// // Rewards go to the correct destination as determined in Payee +// ExtBuilder::default().nominate(false).build().execute_with(|| { +// // Check that account 11 is a validator +// assert!(Staking::current_elected().contains(&11)); +// // Check the balance of the validator account +// assert_eq!(Balances::free_balance(&10), 1); +// // Check the balance of the stash account +// assert_eq!(Balances::free_balance(&11), 1000); +// // Check how much is at stake +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedger { +// stash: 11, +// total: 1000, +// active: 1000, +// unlocking: vec![], +// }) +// ); +// +// // Compute total payout now for whole duration as other parameter won't change +// let total_payout_0 = current_total_payout_for_duration(3000); +// assert!(total_payout_0 > 100); // Test is meaningfull if reward something +// >::reward_by_ids(vec![(11, 1)]); +// +// start_era(1); +// +// // Check that RewardDestination is Staked (default) +// assert_eq!(Staking::payee(&11), RewardDestination::Staked); +// // Check that reward went to the stash account of validator +// assert_eq!(Balances::free_balance(&11), 1000 + total_payout_0); +// // Check that amount at stake increased accordingly +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedger { +// stash: 11, +// total: 1000 + total_payout_0, +// active: 1000 + total_payout_0, +// unlocking: vec![], +// }) +// ); +// +// //Change RewardDestination to Stash +// >::insert(&11, RewardDestination::Stash); +// +// // Compute total payout now for whole duration as other parameter won't change +// let total_payout_1 = current_total_payout_for_duration(3000); +// assert!(total_payout_1 > 100); // Test is meaningfull if reward something +// >::reward_by_ids(vec![(11, 1)]); +// +// start_era(2); +// +// // Check that RewardDestination is Stash +// assert_eq!(Staking::payee(&11), RewardDestination::Stash); +// // Check that reward went to the stash account +// assert_eq!(Balances::free_balance(&11), 1000 + total_payout_0 + total_payout_1); +// // Record this value +// let recorded_stash_balance = 1000 + total_payout_0 + total_payout_1; +// // Check that amount at stake is NOT increased +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedger { +// stash: 11, +// total: 1000 + total_payout_0, +// active: 1000 + total_payout_0, +// unlocking: vec![], +// }) +// ); +// +// // Change RewardDestination to Controller +// >::insert(&11, RewardDestination::Controller); +// +// // Check controller balance +// assert_eq!(Balances::free_balance(&10), 1); +// +// // Compute total payout now for whole duration as other parameter won't change +// let total_payout_2 = current_total_payout_for_duration(3000); +// assert!(total_payout_2 > 100); // Test is meaningfull if reward something +// >::reward_by_ids(vec![(11, 1)]); +// +// start_era(3); +// +// // Check that RewardDestination is Controller +// assert_eq!(Staking::payee(&11), RewardDestination::Controller); +// // Check that reward went to the controller account +// assert_eq!(Balances::free_balance(&10), 1 + total_payout_2); +// // Check that amount at stake is NOT increased +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedger { +// stash: 11, +// total: 1000 + total_payout_0, +// active: 1000 + total_payout_0, +// unlocking: vec![], +// }) +// ); +// // Check that amount in staked account is NOT increased. +// assert_eq!(Balances::free_balance(&11), recorded_stash_balance); +// }); +//} + +// TODO +//#[test] +//fn validator_payment_prefs_work() { +// // Test that validator preferences are correctly honored +// // Note: unstake threshold is being directly tested in slashing tests. +// // This test will focus on validator payment. +// ExtBuilder::default().build().execute_with(|| { +// // Initial config +// let validator_cut = 5; +// let stash_initial_balance = Balances::total_balance(&11); +// +// // check the balance of a validator accounts. +// assert_eq!(Balances::total_balance(&10), 1); +// // check the balance of a validator's stash accounts. +// assert_eq!(Balances::total_balance(&11), stash_initial_balance); +// // and the nominator (to-be) +// let _ = Balances::make_free_balance_be(&2, 500); +// +// // add a dummy nominator. +// >::insert( +// &11, +// Exposure { +// own: 500, // equal division indicates that the reward will be equally divided among validator and nominator. +// total: 1000, +// others: vec![IndividualExposure { who: 2, value: 500 }], +// }, +// ); +// >::insert(&2, RewardDestination::Stash); +// >::insert( +// &11, +// ValidatorPrefs { +// validator_payment: validator_cut, +// }, +// ); +// +// // Compute total payout now for whole duration as other parameter won't change +// let total_payout_0 = current_total_payout_for_duration(3000); +// assert!(total_payout_0 > 100); // Test is meaningfull if reward something +// >::reward_by_ids(vec![(11, 1)]); +// +// start_era(1); +// +// // whats left to be shared is the sum of 3 rounds minus the validator's cut. +// let shared_cut = total_payout_0 - validator_cut; +// // Validator's payee is Staked account, 11, reward will be paid here. +// assert_eq!( +// Balances::total_balance(&11), +// stash_initial_balance + shared_cut / 2 + validator_cut +// ); +// // Controller account will not get any reward. +// assert_eq!(Balances::total_balance(&10), 1); +// // Rest of the reward will be shared and paid to the nominator in stake. +// assert_eq!(Balances::total_balance(&2), 500 + shared_cut / 2); +// +// check_exposure_all(); +// check_nominator_all(); +// }); +//} + +#[test] +fn bond_extra_works() { + // Tests that extra `free_balance` in the stash can be added to stake + // NOTE: this tests only verifies `StakingLedger` for correct updates + // See `bond_extra_and_withdraw_unbonded_works` for more details and updates on `Exposure`. + ExtBuilder::default().build().execute_with(|| { + // Check that account 10 is a validator + assert!(>::exists(11)); + // Check that account 10 is bonded to account 11 + assert_eq!(Staking::bonded(&11), Some(10)); + // Check how much is at stake + assert_eq!( + Staking::ledger(&10).unwrap(), + StakingLedger { + stash: 11, + active_ring: 1000, + active_deposit_ring: 0, + active_kton: 0, + deposit_items: vec![], + ring_staking_lock: StakingLock { + staking_amount: 1000, + unbondings: vec![], + }, + kton_staking_lock: Default::default(), + }, + ); + + // Give account 11 some large free balance greater than total + let _ = Ring::make_free_balance_be(&11, 1000000); + + // Call the bond_extra function from controller, add only 100 + assert_ok!(Staking::bond_extra(Origin::signed(11), StakingBalance::Ring(100), 12)); + // There should be 100 more `total` and `active` in the ledger + assert_eq!( + Staking::ledger(&10).unwrap(), + StakingLedger { + stash: 11, + active_ring: 1000 + 100, + active_deposit_ring: 100, + active_kton: 0, + deposit_items: vec![TimeDepositItem { + value: 100, + start_time: 0, + expire_time: 31104000000, + }], + ring_staking_lock: StakingLock { + staking_amount: 1000 + 100, + unbondings: vec![], + }, + kton_staking_lock: Default::default(), + }, + ); + + // Call the bond_extra function with a large number, should handle it + assert_ok!(Staking::bond_extra( + Origin::signed(11), + StakingBalance::Ring(Balance::max_value()), + 0, + )); + // The full amount of the funds should now be in the total and active + assert_eq!( + Staking::ledger(&10).unwrap(), + StakingLedger { + stash: 11, + active_ring: 1000000, + active_deposit_ring: 100, + active_kton: 0, + deposit_items: vec![TimeDepositItem { + value: 100, + start_time: 0, + expire_time: 31104000000, + }], + ring_staking_lock: StakingLock { + staking_amount: 1000000, + unbondings: vec![], + }, + kton_staking_lock: Default::default(), + }, + ); + }); +} + +// TODO +//#[test] +//fn bond_extra_and_withdraw_unbonded_automatically_works() { +// // * Should test +// // * Given an account being bonded [and chosen as a validator](not mandatory) +// // * It can add extra funds to the bonded account. +// // * it can unbond a portion of its funds from the stash account. +// // * Once the unbonding period is done, it can actually take the funds out of the stash. +// ExtBuilder::default().nominate(false).build().execute_with(|| { +// // Set payee to controller. avoids confusion +// assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); +// +// // Give account 11 some large free balance greater than total +// let _ = Balances::make_free_balance_be(&11, 1000000); +// +// // Initial config should be correct +// assert_eq!(Staking::current_era(), 0); +// assert_eq!(Session::current_index(), 0); +// +// // check the balance of a validator accounts. +// assert_eq!(Balances::total_balance(&10), 1); +// +// // confirm that 10 is a normal validator and gets paid at the end of the era. +// start_era(1); +// +// // Initial state of 10 +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedger { +// stash: 11, +// total: 1000, +// active: 1000, +// unlocking: vec![], +// }) +// ); +// assert_eq!( +// Staking::stakers(&11), +// Exposure { +// total: 1000, +// own: 1000, +// others: vec![] +// } +// ); +// +// // deposit the extra 100 units +// Staking::bond_extra(Origin::signed(11), 100).unwrap(); +// +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedger { +// stash: 11, +// total: 1000 + 100, +// active: 1000 + 100, +// unlocking: vec![], +// }) +// ); +// // Exposure is a snapshot! only updated after the next era update. +// assert_ne!( +// Staking::stakers(&11), +// Exposure { +// total: 1000 + 100, +// own: 1000 + 100, +// others: vec![] +// } +// ); +// +// // trigger next era. +// Timestamp::set_timestamp(10); +// start_era(2); +// assert_eq!(Staking::current_era(), 2); +// +// // ledger should be the same. +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedger { +// stash: 11, +// total: 1000 + 100, +// active: 1000 + 100, +// unlocking: vec![], +// }) +// ); +// // Exposure is now updated. +// assert_eq!( +// Staking::stakers(&11), +// Exposure { +// total: 1000 + 100, +// own: 1000 + 100, +// others: vec![] +// } +// ); +// +// // Unbond almost all of the funds in stash. +// Staking::unbond(Origin::signed(10), 1000).unwrap(); +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedger { +// stash: 11, +// total: 1000 + 100, +// active: 100, +// unlocking: vec![UnlockChunk { +// value: 1000, +// era: 2 + 3 +// }] +// }) +// ); +// +// // Attempting to free the balances now will fail. 2 eras need to pass. +// Staking::withdraw_unbonded(Origin::signed(10)).unwrap(); +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedger { +// stash: 11, +// total: 1000 + 100, +// active: 100, +// unlocking: vec![UnlockChunk { +// value: 1000, +// era: 2 + 3 +// }] +// }) +// ); +// +// // trigger next era. +// start_era(3); +// +// // nothing yet +// Staking::withdraw_unbonded(Origin::signed(10)).unwrap(); +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedger { +// stash: 11, +// total: 1000 + 100, +// active: 100, +// unlocking: vec![UnlockChunk { +// value: 1000, +// era: 2 + 3 +// }] +// }) +// ); +// +// // trigger next era. +// start_era(5); +// +// Staking::withdraw_unbonded(Origin::signed(10)).unwrap(); +// // Now the value is free and the staking ledger is updated. +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedger { +// stash: 11, +// total: 100, +// active: 100, +// unlocking: vec![] +// }) +// ); +// }) +//} + +#[test] +fn too_many_unbond_calls_should_not_work() { + ExtBuilder::default().build().execute_with(|| { + // Locked at Moment(60). + for _ in 0..MAX_UNLOCKING_CHUNKS - 1 { + assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(1))); + } + + Timestamp::set_timestamp(1); + + // Locked at MomentT(61). + assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(1))); + + // Can't do more. + assert_noop!( + Staking::unbond(Origin::signed(10), StakingBalance::Ring(1)), + err::UNLOCK_CHUNKS_REACH_MAX, + ); + + // Free up automatically. + Timestamp::set_timestamp(BondingDuration::get()); + + // Can add again. + assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(1))); + assert_eq!(Staking::ledger(&10).unwrap().ring_staking_lock.unbondings.len(), 2); + }) +} + +// TODO +//#[test] +//fn slot_stake_is_least_staked_validator_and_exposure_defines_maximum_punishment() { +// // Test that slot_stake is determined by the least staked validator +// // Test that slot_stake is the maximum punishment that can happen to a validator +// ExtBuilder::default() +// .nominate(false) +// .fair(false) +// .build() +// .execute_with(|| { +// // Confirm validator count is 2 +// assert_eq!(Staking::validator_count(), 2); +// // Confirm account 10 and 20 are validators +// assert!(>::exists(&11) && >::exists(&21)); +// +// assert_eq!(Staking::stakers(&11).total, 1000); +// assert_eq!(Staking::stakers(&21).total, 2000); +// +// // Give the man some money. +// let _ = Balances::make_free_balance_be(&10, 1000); +// let _ = Balances::make_free_balance_be(&20, 1000); +// +// // We confirm initialized slot_stake is this value +// assert_eq!(Staking::slot_stake(), Staking::stakers(&11).total); +// +// // Now lets lower account 20 stake +// >::insert( +// &21, +// Exposure { +// total: 69, +// own: 69, +// others: vec![], +// }, +// ); +// assert_eq!(Staking::stakers(&21).total, 69); +// >::insert( +// &20, +// StakingLedger { +// stash: 22, +// total: 69, +// active: 69, +// unlocking: vec![], +// }, +// ); +// +// // Compute total payout now for whole duration as other parameter won't change +// let total_payout_0 = current_total_payout_for_duration(3000); +// assert!(total_payout_0 > 100); // Test is meaningfull if reward something +// >::reward_by_ids(vec![(11, 1)]); +// >::reward_by_ids(vec![(21, 1)]); +// +// // New era --> rewards are paid --> stakes are changed +// start_era(1); +// +// // -- new balances + reward +// assert_eq!(Staking::stakers(&11).total, 1000 + total_payout_0 / 2); +// assert_eq!(Staking::stakers(&21).total, 69 + total_payout_0 / 2); +// +// let _11_balance = Balances::free_balance(&11); +// assert_eq!(_11_balance, 1000 + total_payout_0 / 2); +// +// // -- slot stake should also be updated. +// assert_eq!(Staking::slot_stake(), 69 + total_payout_0 / 2); +// +// check_exposure_all(); +// check_nominator_all(); +// }); +//} + +#[test] +fn on_free_balance_zero_stash_removes_validator() { + // Tests that validator storage items are cleaned up when stash is empty + // Tests that storage items are untouched when controller is empty + ExtBuilder::default().existential_deposit(10).build().execute_with(|| { + // Check the balance of the validator account + assert_eq!(Ring::free_balance(&10), 256); + // Check the balance of the stash account + assert_eq!(Ring::free_balance(&11), 256000); + // Check these two accounts are bonded + assert_eq!(Staking::bonded(&11), Some(10)); + + // Set some storage items which we expect to be cleaned up + // Set payee information + assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Stash)); + + // Check storage items that should be cleaned up + assert!(>::exists(&10)); + assert!(>::exists(&11)); + assert!(>::exists(&11)); + assert!(>::exists(&11)); + + // Reduce free_balance of controller to 0 + let _ = Ring::slash(&10, Balance::max_value()); + + // Check the balance of the stash account has not been touched + assert_eq!(Ring::free_balance(&11), 256000); + // Check these two accounts are still bonded + assert_eq!(Staking::bonded(&11), Some(10)); + + // Check storage items have not changed + assert!(>::exists(&10)); + assert!(>::exists(&11)); + assert!(>::exists(&11)); + assert!(>::exists(&11)); + + // Reduce free_balance of stash to 0 + let _ = Ring::slash(&11, Balance::max_value()); + // Check total balance of stash + assert_eq!(Ring::total_balance(&11), 0); + + // Check storage items do not exist + assert!(!>::exists(&10)); + assert!(!>::exists(&11)); + assert!(!>::exists(&11)); + assert!(!>::exists(&11)); + assert!(!>::exists(&11)); + }); +} + +#[test] +fn on_free_balance_zero_stash_removes_nominator() { + // Tests that nominator storage items are cleaned up when stash is empty + // Tests that storage items are untouched when controller is empty + ExtBuilder::default().existential_deposit(10).build().execute_with(|| { + // Make 10 a nominator + assert_ok!(Staking::nominate(Origin::signed(10), vec![20])); + // Check that account 10 is a nominator + assert!(>::exists(11)); + // Check the balance of the nominator account + assert_eq!(Ring::free_balance(&10), 256); + // Check the balance of the stash account + assert_eq!(Ring::free_balance(&11), 256000); + + // Set payee information + assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Stash)); + + // Check storage items that should be cleaned up + assert!(>::exists(&10)); + assert!(>::exists(&11)); + assert!(>::exists(&11)); + assert!(>::exists(&11)); + + // Reduce free_balance of controller to 0 + let _ = Ring::slash(&10, Balance::max_value()); + // Check total balance of account 10 + assert_eq!(Ring::total_balance(&10), 0); + + // Check the balance of the stash account has not been touched + assert_eq!(Ring::free_balance(&11), 256000); + // Check these two accounts are still bonded + assert_eq!(Staking::bonded(&11), Some(10)); + + // Check storage items have not changed + assert!(>::exists(&10)); + assert!(>::exists(&11)); + assert!(>::exists(&11)); + assert!(>::exists(&11)); + + // Reduce free_balance of stash to 0 + let _ = Ring::slash(&11, Balance::max_value()); + // Check total balance of stash + assert_eq!(Ring::total_balance(&11), 0); + + // Check storage items do not exist + assert!(!>::exists(&10)); + assert!(!>::exists(&11)); + assert!(!>::exists(&11)); + assert!(!>::exists(&11)); + assert!(!>::exists(&11)); + }); +} + +#[test] +fn switching_roles() { + // Test that it should be possible to switch between roles (nominator, validator, idle) with minimal overhead. + ExtBuilder::default().nominate(false).build().execute_with(|| { + // Initialize time. + Timestamp::set_timestamp(1); + + // Reset reward destination. + for i in &[10, 20] { + assert_ok!(Staking::set_payee(Origin::signed(*i), RewardDestination::Controller)); + } + + assert_eq_uvec!(validator_controllers(), vec![20, 10]); + + // Put some money in account that we'll use. + for i in 1..7 { + let _ = Ring::deposit_creating(&i, 5000); + } + + // Add 2 nominators. + assert_ok!(Staking::bond( + Origin::signed(1), + 2, + StakingBalance::Ring(2000), + RewardDestination::Controller, + 0, + )); + assert_ok!(Staking::nominate(Origin::signed(2), vec![11, 5])); + + assert_ok!(Staking::bond( + Origin::signed(3), + 4, + StakingBalance::Ring(500), + RewardDestination::Controller, + 0, + )); + assert_ok!(Staking::nominate(Origin::signed(4), vec![21, 1])); + + // Add a new validator candidate. + assert_ok!(Staking::bond( + Origin::signed(5), + 6, + StakingBalance::Ring(1000), + RewardDestination::Controller, + 0, + )); + assert_ok!(Staking::validate( + Origin::signed(6), + ValidatorPrefs { + node_name: "Darwinia Node".bytes().collect(), + ..Default::default() + }, + )); + + // New block. + start_session(1); + + // No change. + assert_eq_uvec!(validator_controllers(), vec![20, 10]); + + // New block. + start_session(2); + + // No change. + assert_eq_uvec!(validator_controllers(), vec![20, 10]); + + // new block --> ne era --> new validators. + start_session(3); + + // With current nominators 10 and 5 have the most stake. + assert_eq_uvec!(validator_controllers(), vec![6, 10]); + + // 2 decides to be a validator. Consequences: + assert_ok!(Staking::validate( + Origin::signed(2), + ValidatorPrefs { + node_name: "Darwinia Node".bytes().collect(), + ..Default::default() + }, + )); + // New stakes: + // 10: 1000 self vote + // 20: 1000 self vote + 250 vote + // 6 : 1000 self vote + // 2 : 2000 self vote + 250 vote. + // Winners: 20 and 2 + + start_session(4); + assert_eq_uvec!(validator_controllers(), vec![6, 10]); + + start_session(5); + assert_eq_uvec!(validator_controllers(), vec![6, 10]); + + // ne era. + start_session(6); + assert_eq_uvec!(validator_controllers(), vec![2, 20]); + check_exposure_all(); + check_nominator_all(); + }); +} + +#[test] +fn wrong_vote_is_null() { + ExtBuilder::default() + .nominate(false) + .validator_pool(true) + .build() + .execute_with(|| { + assert_eq_uvec!(validator_controllers(), vec![40, 30]); + + // Put some money in account that we'll use. + for i in 1..3 { + let _ = Ring::deposit_creating(&i, 5000); + } + + // Add 1 nominators + assert_ok!(Staking::bond( + Origin::signed(1), + 2, + StakingBalance::Ring(2000), + RewardDestination::default(), + 0, + )); + assert_ok!(Staking::nominate( + Origin::signed(2), + vec![ + 11, 21, // Good votes. + 1, 2, 15, 1000, 25 // Crap votes. No effect. + ], + )); + + // New block. + start_era(1); + + assert_eq_uvec!(validator_controllers(), vec![20, 10]); + }); +} + +#[test] +fn bond_with_no_staked_value() { + // Behavior when someone bonds with no staked value. + // Particularly when she votes and the candidate is elected. + ExtBuilder::default() + .validator_count(3) + .existential_deposit(5) + .nominate(false) + .minimum_validator_count(1) + .build() + .execute_with(|| { + // Bonded with absolute minimum value possible. + assert_ok!(Staking::bond( + Origin::signed(1), + 2, + StakingBalance::Ring(5), + RewardDestination::Controller, + 0, + )); + // assert_eq!(Ring::locks(&1)[0].amount, 5); + + assert_ok!(Staking::unbond(Origin::signed(2), StakingBalance::Ring(5))); + assert_eq!( + Staking::ledger(2), + Some(StakingLedger { + stash: 1, + active_ring: 0, + active_deposit_ring: 0, + active_kton: 0, + deposit_items: vec![], + ring_staking_lock: StakingLock { + staking_amount: 0, + unbondings: vec![NormalLock { amount: 5, until: 60 }], + }, + kton_staking_lock: Default::default(), + }), + ); + + Timestamp::set_timestamp(BondingDuration::get() - 1); + + // Not yet removed. + assert!(Staking::ledger(2).is_some()); + // assert_eq!(Ring::locks(&1)[0].amount, 5); + + Timestamp::set_timestamp(BondingDuration::get()); + + // FIXME + // Poof. Account 1 is removed from the staking system. + // assert!(Staking::ledger(2).is_none()); + // assert_eq!(Ring::locks(&1).len(), 0); + }); +} + +// TODO +//#[test] +//fn bond_with_little_staked_value_bounded_by_slot_stake() { +// // Behavior when someone bonds with little staked value. +// // Particularly when she votes and the candidate is elected. +// ExtBuilder::default() +// .validator_count(3) +// .nominate(false) +// .minimum_validator_count(1) +// .build() +// .execute_with(|| { +// // setup +// assert_ok!(Staking::chill(Origin::signed(30))); +// assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); +// let init_balance_2 = Balances::free_balance(&2); +// let init_balance_10 = Balances::free_balance(&10); +// +// // Stingy validator. +// assert_ok!(Staking::bond(Origin::signed(1), 2, 1, RewardDestination::Controller)); +// assert_ok!(Staking::validate(Origin::signed(2), ValidatorPrefs::default())); +// +// let total_payout_0 = current_total_payout_for_duration(3000); +// assert!(total_payout_0 > 100); // Test is meaningfull if reward something +// reward_all_elected(); +// start_era(1); +// +// // 2 is elected. +// // and fucks up the slot stake. +// assert_eq_uvec!(validator_controllers(), vec![20, 10, 2]); +// assert_eq!(Staking::slot_stake(), 1); +// +// // Old ones are rewarded. +// assert_eq!(Balances::free_balance(&10), init_balance_10 + total_payout_0 / 3); +// // no rewards paid to 2. This was initial election. +// assert_eq!(Balances::free_balance(&2), init_balance_2); +// +// let total_payout_1 = current_total_payout_for_duration(3000); +// assert!(total_payout_1 > 100); // Test is meaningfull if reward something +// reward_all_elected(); +// start_era(2); +// +// assert_eq_uvec!(validator_controllers(), vec![20, 10, 2]); +// assert_eq!(Staking::slot_stake(), 1); +// +// assert_eq!(Balances::free_balance(&2), init_balance_2 + total_payout_1 / 3); +// assert_eq!( +// Balances::free_balance(&10), +// init_balance_10 + total_payout_0 / 3 + total_payout_1 / 3, +// ); +// check_exposure_all(); +// check_nominator_all(); +// }); +//} + +// TODO +//#[cfg(feature = "equalize")] +//#[test] +//fn phragmen_linear_worse_case_equalize() { +// ExtBuilder::default() +// .nominate(false) +// .validator_pool(true) +// .fair(true) +// .build() +// .execute_with(|| { +// bond_validator(50, 1000); +// bond_validator(60, 1000); +// bond_validator(70, 1000); +// +// bond_nominator(2, 2000, vec![11]); +// bond_nominator(4, 1000, vec![11, 21]); +// bond_nominator(6, 1000, vec![21, 31]); +// bond_nominator(8, 1000, vec![31, 41]); +// bond_nominator(110, 1000, vec![41, 51]); +// bond_nominator(120, 1000, vec![51, 61]); +// bond_nominator(130, 1000, vec![61, 71]); +// +// for i in &[10, 20, 30, 40, 50, 60, 70] { +// assert_ok!(Staking::set_payee(Origin::signed(*i), RewardDestination::Controller)); +// } +// +// assert_eq_uvec!(validator_controllers(), vec![40, 30]); +// assert_ok!(Staking::set_validator_count(Origin::ROOT, 7)); +// +// start_era(1); +// +// assert_eq_uvec!(validator_controllers(), vec![10, 60, 40, 20, 50, 30, 70]); +// +// assert_eq_error_rate!(Staking::stakers(11).total, 3000, 2); +// assert_eq_error_rate!(Staking::stakers(21).total, 2255, 2); +// assert_eq_error_rate!(Staking::stakers(31).total, 2255, 2); +// assert_eq_error_rate!(Staking::stakers(41).total, 1925, 2); +// assert_eq_error_rate!(Staking::stakers(51).total, 1870, 2); +// assert_eq_error_rate!(Staking::stakers(61).total, 1890, 2); +// assert_eq_error_rate!(Staking::stakers(71).total, 1800, 2); +// +// check_exposure_all(); +// check_nominator_all(); +// }) +//} - let (stash, controller) = (11, 10); +#[test] +fn new_era_elects_correct_number_of_validators() { + ExtBuilder::default() + .nominate(true) + .validator_pool(true) + .fair(true) + .validator_count(1) + .build() + .execute_with(|| { + assert_eq!(Staking::validator_count(), 1); + assert_eq!(validator_controllers().len(), 1); + + System::set_block_number(1); + Session::on_initialize(System::block_number()); + + assert_eq!(validator_controllers().len(), 1); + check_exposure_all(); + check_nominator_all(); + }) +} + +#[test] +fn reward_from_authorship_event_handler_works() { + ExtBuilder::default().build().execute_with(|| { + use authorship::EventHandler; + + assert_eq!(>::author(), 11); + + >::note_author(11); + >::note_uncle(21, 1); + // An uncle author that is not currently elected doesn't get rewards, + // but the block producer does get reward for referencing it. + >::note_uncle(31, 1); + // Rewarding the same two times works. + >::note_uncle(11, 1); + + // Not mandatory but must be coherent with rewards. + assert_eq!(>::get(), vec![21, 11]); + + // 21 is rewarded as an uncle producer. + // 11 is rewarded as a block producer and uncle referencer and uncle producer. + assert_eq!(CurrentEraPointsEarned::get().individual, vec![1, 20 + 2 * 3 + 1]); + assert_eq!(CurrentEraPointsEarned::get().total, 28); + }) +} + +#[test] +fn add_reward_points_fns_works() { + ExtBuilder::default().build().execute_with(|| { + let validators = >::current_elected(); + // Not mandatory but must be coherent with rewards. + assert_eq!(validators, vec![21, 11]); + + >::reward_by_indices(vec![(0, 1), (1, 1), (2, 1), (1, 1)]); + + >::reward_by_ids(vec![(21, 1), (11, 1), (31, 1), (11, 1)]); + + assert_eq!(CurrentEraPointsEarned::get().individual, vec![2, 4]); + assert_eq!(CurrentEraPointsEarned::get().total, 6); + }) +} + +#[test] +fn unbonded_balance_is_not_slashable() { + ExtBuilder::default().build().execute_with(|| { + // Total amount staked is slashable. + assert_eq!(Staking::ledger(&10).unwrap().active_ring, 1000); + + assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(800))); + + // Only the active portion. + assert_eq!(Staking::ledger(&10).unwrap().active_ring, 200); + }) +} + +#[test] +fn era_is_always_same_length() { + // This ensures that the sessions is always of the same length if there is no forcing no + // session changes. + ExtBuilder::default().build().execute_with(|| { + start_era(1); + assert_eq!(Staking::current_era_start_session_index(), SessionsPerEra::get()); + + start_era(2); + assert_eq!(Staking::current_era_start_session_index(), SessionsPerEra::get() * 2); - assert_eq!(Staking::bonded(&stash), Some(controller)); + let session = Session::current_index(); + ForceEra::put(Forcing::ForceNew); + advance_session(); + assert_eq!(Staking::current_era(), 3); + assert_eq!(Staking::current_era_start_session_index(), session + 1); + + start_era(4); assert_eq!( - Staking::ledger(&controller).unwrap(), - StakingLedger { - stash, - active_ring: 100 * COIN, - active_deposit_ring: 100 * COIN, - active_kton: 0, - deposit_items: vec![TimeDepositItem { - value: 100 * COIN, - start_time: 0, - expire_time: (12 * MONTH_IN_SECONDS) as _, - }], - ring_staking_lock: StakingLock { - staking_amount: 100 * COIN, - unbondings: vec![], - }, - kton_staking_lock: Default::default(), - } + Staking::current_era_start_session_index(), + session + SessionsPerEra::get() + 1 ); + }); +} - assert_eq!(Kton::free_balance(&11), COIN / 100); - assert_eq!(Kton::total_issuance(), 16 * COIN / 100); +#[test] +fn offence_forces_new_era() { + ExtBuilder::default().build().execute_with(|| { + Staking::on_offence( + &[OffenceDetails { + offender: (11, Staking::stakers(&11)), + reporters: vec![], + }], + &[Perbill::from_percent(5)], + ); - let origin_ledger = Staking::ledger(&controller).unwrap(); - let _ = Ring::deposit_creating(&stash, 100 * COIN); - assert_ok!(Staking::bond_extra( - Origin::signed(stash), - StakingBalance::Ring(20 * COIN), - 13, - )); - assert_eq!( - Staking::ledger(&controller).unwrap(), - StakingLedger { - stash, - active_ring: origin_ledger.active_ring + 20 * COIN, - active_deposit_ring: origin_ledger.active_deposit_ring + 20 * COIN, - active_kton: 0, - deposit_items: vec![ - TimeDepositItem { - value: 100 * COIN, - start_time: 0, - expire_time: (12 * MONTH_IN_SECONDS) as _, - }, - TimeDepositItem { - value: 20 * COIN, - start_time: 0, - expire_time: (13 * MONTH_IN_SECONDS) as _, - }, - ], - ring_staking_lock: StakingLock { - staking_amount: origin_ledger.active_ring + 20 * COIN, - unbondings: vec![], + assert_eq!(Staking::force_era(), Forcing::ForceNew); + }); +} + +#[test] +fn offence_ensures_new_era_without_clobbering() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(Staking::force_new_era_always(Origin::ROOT)); + + Staking::on_offence( + &[OffenceDetails { + offender: (11, Staking::stakers(&11)), + reporters: vec![], + }], + &[Perbill::from_percent(5)], + ); + + assert_eq!(Staking::force_era(), Forcing::ForceAlways); + }); +} + +#[test] +fn offence_deselects_validator_when_slash_is_zero() { + ExtBuilder::default().build().execute_with(|| { + assert!(>::exists(11)); + Staking::on_offence( + &[OffenceDetails { + offender: (11, Staking::stakers(&11)), + reporters: vec![], + }], + &[Perbill::from_percent(0)], + ); + assert_eq!(Staking::force_era(), Forcing::ForceNew); + assert!(!>::exists(11)); + }); +} + +// TODO +//#[test] +//fn slashing_performed_according_exposure() { +// // This test checks that slashing is performed according the exposure (or more precisely, +// // historical exposure), not the current balance. +// ExtBuilder::default().build().execute_with(|| { +// assert_eq!(Staking::stakers(&11).own, 1000); +// +// // Handle an offence with a historical exposure. +// Staking::on_offence( +// &[OffenceDetails { +// offender: ( +// 11, +// Exposure { +// total: 500, +// own: 500, +// others: vec![], +// }, +// ), +// reporters: vec![], +// }], +// &[Perbill::from_percent(50)], +// ); +// +// // The stash account should be slashed for 250 (50% of 500). +// assert_eq!(Balances::free_balance(&11), 1000 - 250); +// }); +//} + +#[test] +fn reporters_receive_their_slice() { + // This test verifies that the reporters of the offence receive their slice from the slashed + // amount. + ExtBuilder::default().build().execute_with(|| { + // The reporters' reward is calculated from the total exposure. + #[cfg(feature = "equalize")] + let initial_balance = 1250; + #[cfg(not(feature = "equalize"))] + let initial_balance = 1125; + + Staking::on_offence( + &[OffenceDetails { + offender: (11, Staking::stakers(&11)), + reporters: vec![1, 2], + }], + &[Perbill::from_percent(50)], + ); + + // initial_balance x 50% (slash fraction) x 10% (rewards slice) + let reward = initial_balance / 20 / 2; + assert_eq!(Ring::free_balance(&1), 10 + reward); + assert_eq!(Ring::free_balance(&2), 20 + reward); + }); +} + +#[test] +fn invulnerables_are_not_slashed() { + // For invulnerable validators no slashing is performed. + ExtBuilder::default().invulnerables(vec![11]).build().execute_with(|| { + #[cfg(feature = "equalize")] + let initial_balance = 1250; + #[cfg(not(feature = "equalize"))] + let initial_balance = 1375; + + assert_eq!(Ring::free_balance(&11), 1000); + assert_eq!(Ring::free_balance(&21), 2000); + + Staking::on_offence( + &[ + OffenceDetails { + offender: (11, Staking::stakers(&11)), + reporters: vec![], }, - kton_staking_lock: Default::default(), - } + OffenceDetails { + offender: (21, Staking::stakers(&21)), + reporters: vec![], + }, + ], + &[Perbill::from_percent(50), Perbill::from_percent(20)], + ); + + // The validator 11 hasn't been slashed, but 21 has been. + assert_eq!(Ring::free_balance(&11), 1000); + // 2000 - (0.2 * initial_balance) + assert_eq!(Ring::free_balance(&21), 2000 - (2 * initial_balance / 10)); + }); +} + +#[test] +fn dont_slash_if_fraction_is_zero() { + // Don't slash if the fraction is zero. + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Ring::free_balance(&11), 1000); + + Staking::on_offence( + &[OffenceDetails { + offender: (11, Staking::stakers(&11)), + reporters: vec![], + }], + &[Perbill::from_percent(0)], ); + + // The validator hasn't been slashed. The new era is not forced. + assert_eq!(Ring::free_balance(&11), 1000); }); } +// custom tests + #[test] fn normal_kton_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { + ExtBuilder::default().build().execute_with(|| { { let (stash, controller) = (1001, 1000); @@ -148,7 +2237,7 @@ fn normal_kton_should_work() { 0, )); assert_eq!( - Staking::ledger(&controller).unwrap(), + Staking::ledger(controller).unwrap(), StakingLedger { stash, active_ring: 0, @@ -188,7 +2277,7 @@ fn normal_kton_should_work() { 12, )); assert_eq!( - Staking::ledger(&controller).unwrap(), + Staking::ledger(controller).unwrap(), StakingLedger { stash, active_ring: 0, @@ -208,114 +2297,109 @@ fn normal_kton_should_work() { #[test] fn time_deposit_ring_unbond_and_withdraw_automatically_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { + ExtBuilder::default().build().execute_with(|| { let (stash, controller) = (11, 10); - { - let locks = vec![BalanceLock { + let unbond_value = 10; + assert_ok!(Staking::unbond( + Origin::signed(controller), + StakingBalance::Ring(unbond_value), + )); + assert_eq!( + Ring::locks(stash), + vec![BalanceLock { id: STAKING_ID, withdraw_lock: WithdrawLock::WithStaking(StakingLock { - staking_amount: 100 * COIN, - unbondings: vec![], + staking_amount: 1000 - unbond_value, + unbondings: vec![NormalLock { + amount: unbond_value, + until: BondingDuration::get(), + }], }), reasons: WithdrawReasons::all(), - }]; - let ledger = StakingLedger { + }], + ); + assert_eq!( + Staking::ledger(controller).unwrap(), + StakingLedger { stash, - active_ring: 100 * COIN, - active_deposit_ring: 100 * COIN, + active_ring: 1000 - unbond_value, + active_deposit_ring: 0, active_kton: 0, - deposit_items: vec![TimeDepositItem { - value: 100 * COIN, - start_time: 0, - expire_time: (12 * MONTH_IN_SECONDS) as _, - }], + deposit_items: vec![], ring_staking_lock: StakingLock { - staking_amount: 100 * COIN, - unbondings: vec![], + staking_amount: 1000 - unbond_value, + unbondings: vec![NormalLock { + amount: unbond_value, + until: BondingDuration::get(), + }], }, kton_staking_lock: Default::default(), - }; - - assert_ok!(Staking::unbond( - Origin::signed(controller), - StakingBalance::Ring(10 * COIN) - )); - assert_eq!(Ring::locks(stash), locks); - assert_eq!(Staking::ledger(&controller).unwrap(), ledger,); - - assert_ok!(Staking::unbond( - Origin::signed(controller), - StakingBalance::Ring(120 * COIN) - )); - assert_eq!(Ring::locks(stash), locks); - assert_eq!(Staking::ledger(&controller).unwrap(), ledger); - } + }, + ); - { - let (unbond_start, unbond_value) = ((13 * MONTH_IN_SECONDS) as _, 10 * COIN); - Timestamp::set_timestamp(unbond_start); + let unbond_start = 30; + Timestamp::set_timestamp(unbond_start); + assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(COIN))); - assert_ok!(Staking::unbond( - Origin::signed(controller), - StakingBalance::Ring(unbond_value) - )); - assert_eq!( - Ring::locks(stash), - vec![BalanceLock { - id: STAKING_ID, - withdraw_lock: WithdrawLock::WithStaking(StakingLock { - staking_amount: 100 * COIN - unbond_value, - unbondings: vec![NormalLock { + assert_eq!( + Ring::locks(stash), + vec![BalanceLock { + id: STAKING_ID, + withdraw_lock: WithdrawLock::WithStaking(StakingLock { + staking_amount: 0, + unbondings: vec![ + NormalLock { amount: unbond_value, + until: BondingDuration::get(), + }, + NormalLock { + amount: 1000 - unbond_value, until: unbond_start + BondingDuration::get(), - }], - }), - reasons: WithdrawReasons::all(), - }] - ); - assert_eq!( - Staking::ledger(&controller).unwrap(), - StakingLedger { - stash, - active_ring: 100 * COIN - unbond_value, - active_deposit_ring: 0, - active_kton: 0, - deposit_items: vec![], - ring_staking_lock: StakingLock { - staking_amount: 100 * COIN - unbond_value, - unbondings: vec![NormalLock { + }, + ], + }), + reasons: WithdrawReasons::all(), + }], + ); + assert_eq!( + Staking::ledger(controller).unwrap(), + StakingLedger { + stash, + active_ring: 0, + active_deposit_ring: 0, + active_kton: 0, + deposit_items: vec![], + ring_staking_lock: StakingLock { + staking_amount: 0, + unbondings: vec![ + NormalLock { amount: unbond_value, + until: BondingDuration::get(), + }, + NormalLock { + amount: 1000 - unbond_value, until: unbond_start + BondingDuration::get(), - }], - }, - kton_staking_lock: Default::default(), - } - ); + }, + ], + }, + kton_staking_lock: Default::default(), + }, + ); - Timestamp::set_timestamp(unbond_start + BondingDuration::get()); - assert_err!( - Ring::ensure_can_withdraw( - &stash, - unbond_value, - WithdrawReason::Transfer.into(), - 100 * COIN - unbond_value - 1, - ), - "account liquidity restrictions prevent withdrawal" - ); - assert_ok!(Ring::ensure_can_withdraw( - &stash, - unbond_value, - WithdrawReason::Transfer.into(), - 100 * COIN - unbond_value, - )); - } + assert_err!( + Ring::transfer(Origin::signed(stash), controller, 1), + "account liquidity restrictions prevent withdrawal", + ); + + Timestamp::set_timestamp(BondingDuration::get()); + assert_ok!(Ring::transfer(Origin::signed(stash), controller, 1)); }); } #[test] fn normal_unbond_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { + ExtBuilder::default().build().execute_with(|| { let (stash, controller) = (11, 10); let value = 200 * COIN; let promise_month = 12; @@ -323,7 +2407,7 @@ fn normal_unbond_should_work() { { let kton_free_balance = Kton::free_balance(&stash); - let mut ledger = Staking::ledger(&controller).unwrap(); + let mut ledger = Staking::ledger(controller).unwrap(); assert_ok!(Staking::bond_extra( Origin::signed(stash), @@ -339,27 +2423,27 @@ fn normal_unbond_should_work() { ledger.deposit_items.push(TimeDepositItem { value, start_time: 0, - expire_time: (promise_month * MONTH_IN_SECONDS) as _, + expire_time: promise_month * MONTH_IN_MILLISECONDS, }); ledger.ring_staking_lock.staking_amount += value; - assert_eq!(Staking::ledger(&controller).unwrap(), ledger); + assert_eq!(Staking::ledger(controller).unwrap(), ledger); } { let kton_free_balance = Kton::free_balance(&stash); - let mut ledger = Staking::ledger(&controller).unwrap(); + let mut ledger = Staking::ledger(controller).unwrap(); - // we try to bond 1 kton, but stash only has 0.03 Kton - // extra = 1.min(0.03) - // bond += 0.03 + // We try to bond 1 kton, but stash only has 0.2 Kton. + // extra = COIN.min(20_000_000) + // bond += 20_000_000 assert_ok!(Staking::bond_extra( Origin::signed(stash), StakingBalance::Kton(COIN), - 0 + 0, )); ledger.active_kton += kton_free_balance; ledger.kton_staking_lock.staking_amount += kton_free_balance; - assert_eq!(Staking::ledger(&controller).unwrap(), ledger); + assert_eq!(Staking::ledger(controller).unwrap(), ledger); assert_ok!(Staking::unbond( Origin::signed(controller), @@ -371,30 +2455,30 @@ fn normal_unbond_should_work() { amount: kton_free_balance, until: BondingDuration::get(), }); - assert_eq!(Staking::ledger(&controller).unwrap(), ledger); + assert_eq!(Staking::ledger(controller).unwrap(), ledger); } }); } #[test] fn punished_claim_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { + ExtBuilder::default().build().execute_with(|| { let (stash, controller) = (1001, 1000); let promise_month = 36; - let _ = Ring::deposit_creating(&stash, 100 * COIN); - Kton::deposit_creating(&stash, COIN / 100000); + let bond_value = 10; + let _ = Ring::deposit_creating(&stash, 1000); let mut ledger = StakingLedger { stash, - active_ring: 10 * COIN, - active_deposit_ring: 10 * COIN, + active_ring: bond_value, + active_deposit_ring: bond_value, active_kton: 0, deposit_items: vec![TimeDepositItem { - value: 10 * COIN, + value: bond_value, start_time: 0, - expire_time: (promise_month * MONTH_IN_SECONDS) as _, + expire_time: promise_month * MONTH_IN_MILLISECONDS, }], ring_staking_lock: StakingLock { - staking_amount: 10 * COIN, + staking_amount: bond_value, unbondings: vec![], }, kton_staking_lock: Default::default(), @@ -403,75 +2487,73 @@ fn punished_claim_should_work() { assert_ok!(Staking::bond( Origin::signed(stash), controller, - StakingBalance::Ring(10 * COIN), + StakingBalance::Ring(bond_value), RewardDestination::Stash, promise_month, )); - assert_eq!(Staking::ledger(&controller).unwrap(), ledger); - // kton is 0, skip unbond_with_punish - assert_ok!(Staking::claim_deposits_with_punish( + assert_eq!(Staking::ledger(controller).unwrap(), ledger); + // Kton is 0, skip `unbond_with_punish`. + assert_ok!(Staking::try_claim_deposits_with_punish( Origin::signed(controller), - (promise_month * MONTH_IN_SECONDS) as _, + promise_month * MONTH_IN_MILLISECONDS, )); - assert_eq!(Staking::ledger(&controller).unwrap(), ledger); + assert_eq!(Staking::ledger(controller).unwrap(), ledger); - // set more kton balance to make it work - Kton::deposit_creating(&stash, 10 * COIN); - let kton_free_balance = Kton::free_balance(&stash); - let kton_punishment = inflation::compute_kton_return::(10 * COIN, promise_month); - assert_ok!(Staking::claim_deposits_with_punish( + // Set more kton balance to make it work. + Kton::deposit_creating(&stash, COIN); + assert_ok!(Staking::try_claim_deposits_with_punish( Origin::signed(controller), - (promise_month * MONTH_IN_SECONDS) as _, + promise_month * MONTH_IN_MILLISECONDS, )); - ledger.active_deposit_ring -= 10 * COIN; + ledger.active_deposit_ring -= bond_value; ledger.deposit_items.clear(); - assert_eq!(Staking::ledger(&controller).unwrap(), ledger); - assert_eq!(Kton::free_balance(&stash), kton_free_balance - 3 * kton_punishment); + assert_eq!(Staking::ledger(controller).unwrap(), ledger); + assert_eq!(Kton::free_balance(&stash), COIN - 3); }); } #[test] fn transform_to_deposited_ring_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { + ExtBuilder::default().build().execute_with(|| { let (stash, controller) = (1001, 1000); - let _ = Ring::deposit_creating(&stash, 100 * COIN); + let _ = Ring::deposit_creating(&stash, COIN); assert_ok!(Staking::bond( Origin::signed(stash), controller, - StakingBalance::Ring(10 * COIN), + StakingBalance::Ring(COIN), RewardDestination::Stash, 0, )); let kton_free_balance = Kton::free_balance(&stash); - let mut ledger = Staking::ledger(&controller).unwrap(); + let mut ledger = Staking::ledger(controller).unwrap(); - assert_ok!(Staking::deposit_extra(Origin::signed(controller), 5 * COIN, 12)); - ledger.active_deposit_ring += 5 * COIN; + assert_ok!(Staking::deposit_extra(Origin::signed(controller), COIN, 12)); + ledger.active_deposit_ring += COIN; ledger.deposit_items.push(TimeDepositItem { - value: 5 * COIN, + value: COIN, start_time: 0, - expire_time: (12 * MONTH_IN_SECONDS) as u64, + expire_time: 12 * MONTH_IN_MILLISECONDS, }); - assert_eq!(Staking::ledger(&controller).unwrap(), ledger); - assert_eq!(Kton::free_balance(&stash), kton_free_balance + (5 * COIN / 10000)); + assert_eq!(Staking::ledger(controller).unwrap(), ledger); + assert_eq!(Kton::free_balance(&stash), kton_free_balance + (COIN / 10000)); }); } #[test] fn expired_ring_should_capable_to_promise_again() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { + ExtBuilder::default().build().execute_with(|| { let (stash, controller) = (1001, 1000); - let _ = Ring::deposit_creating(&stash, 100 * COIN); + let _ = Ring::deposit_creating(&stash, 10); assert_ok!(Staking::bond( Origin::signed(stash), controller, - StakingBalance::Ring(10 * COIN), + StakingBalance::Ring(10), RewardDestination::Stash, 12, )); - let mut ledger = Staking::ledger(&controller).unwrap(); - let ts = (13 * MONTH_IN_SECONDS) as u64; - let promise_extra_value = 5 * COIN; + let mut ledger = Staking::ledger(controller).unwrap(); + let ts = 13 * MONTH_IN_MILLISECONDS; + let promise_extra_value = 5; Timestamp::set_timestamp(ts); assert_ok!(Staking::deposit_extra( @@ -486,7 +2568,7 @@ fn expired_ring_should_capable_to_promise_again() { start_time: ts, expire_time: 2 * ts, }]; - assert_eq!(Staking::ledger(&controller).unwrap(), ledger); + assert_eq!(Staking::ledger(controller).unwrap(), ledger); }); } @@ -541,89 +2623,130 @@ fn inflation_should_be_correct() { } #[test] -fn reward_and_slash_should_work() { +fn validator_prefs_should_work() { ExtBuilder::default().build().execute_with(|| { - gen_paired_account!(stash_1(123), _c(456), 12); - gen_paired_account!(stash_2(234), _c(567), 12); + gen_paired_account!(validator_stash(123), validator_controller(456), 0); + gen_paired_account!(nominator_stash(345), nominator_controller(678), 0); + + assert_ok!(Staking::validate( + Origin::signed(validator_controller), + ValidatorPrefs { + node_name: vec![0; 8], + validator_payment_ratio: 0, + }, + )); + assert_ok!(Staking::nominate( + Origin::signed(nominator_controller), + vec![validator_stash], + )); - >::insert( - &stash_1, - Exposure { - total: 1, - own: 1, - others: vec![], + assert_eq!(Staking::reward_validator(&validator_stash, COIN).peek(), 0); + + assert_ok!(Staking::chill(Origin::signed(validator_controller))); + assert_ok!(Staking::chill(Origin::signed(nominator_controller))); + + assert_ok!(Staking::validate( + Origin::signed(validator_controller), + ValidatorPrefs { + node_name: vec![0; 8], + validator_payment_ratio: 100, }, - ); - assert_eq!(Ring::free_balance(&stash_1), 100 * COIN); - let _ = Staking::reward_validator(&stash_1, 20 * COIN); - assert_eq!(Ring::free_balance(&stash_1), 120 * COIN); + )); + assert_ok!(Staking::nominate( + Origin::signed(nominator_controller), + vec![validator_stash], + )); - // FIXME: slash strategy - // >::insert( - // &stash_1, - // Exposure { - // total: 100 * COIN, - // own: 1, - // others: vec![IndividualExposure { - // who: stash_2, - // value: 100 * COIN - 1, - // }], - // }, - // ); - // let _ = Staking::slash_validator(&stash_1, 1, &Staking::stakers(&stash_1), &mut Vec::new()); - // assert_eq!(Ring::free_balance(&stash_1), 120 * COIN - 1); - // assert_eq!(Ring::free_balance(&stash_2), 1); + assert_eq!(Staking::reward_validator(&validator_stash, COIN).peek(), COIN); }); } -#[test] -fn set_controller_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let (stash, old_controller, new_controller) = (11, 10, 12); - let ledger = Staking::ledger(&old_controller).unwrap(); - - assert_ok!(Staking::set_controller(Origin::signed(stash), new_controller)); - assert_eq!(Staking::ledger(&old_controller), None); - assert_eq!(Staking::ledger(&new_controller).unwrap(), ledger); - }); -} +//#[test] +//fn reward_and_slash_should_work() { +// // ExtBuilder::default().build().execute_with(|| { +// // gen_paired_account!(stash_1(123), _c(456), 12); +// // gen_paired_account!(stash_2(234), _c(567), 12); +// // +// // >::insert( +// // &stash_1, +// // Exposure { +// // total: 1, +// // own: 1, +// // others: vec![], +// // }, +// // ); +// // assert_eq!(Ring::free_balance(&stash_1), 100 * COIN); +// // let _ = Staking::reward_validator(&stash_1, 20 * COIN); +// // assert_eq!(Ring::free_balance(&stash_1), 120 * COIN); +// // }); +// +// ExtBuilder::default().build().execute_with(|| { +// gen_paired_account!(validator_stash(123), validator_controller(456), 0); +// gen_paired_account!(nominator_stash(345), nominator_controller(678), 0); +// +// println!( +// "{}, {}", +// Ring::free_balance(&validator_stash), +// Kton::free_balance(&validator_stash) +// ); +// println!("{:#?}", Staking::ledger(&validator_controller)); +// println!( +// "{}, {}", +// Ring::free_balance(&nominator_stash), +// Kton::free_balance(&nominator_stash) +// ); +// println!("{:#?}", Staking::ledger(&nominator_controller)); +// +// assert_ok!(Staking::validate( +// Origin::signed(validator_controller), +// ValidatorPrefs { +// node_name: vec![0; 8], +// ..Default::default() +// }, +// )); +// assert_ok!(Staking::nominate( +// Origin::signed(nominator_controller), +// vec![validator_stash], +// )); +// +// println!("{:#?}", Staking::stakers(validator_stash)); +// start_era(1); +// println!("{:#?}", Staking::stakers(validator_stash)); +// }); +//} #[test] fn slash_should_not_touch_unbondings() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { + ExtBuilder::default().build().execute_with(|| { let (stash, controller) = (11, 10); - let ledger = Staking::ledger(&controller).unwrap(); - // only deposit_ring, no normal_ring - assert_eq!( - (ledger.active_ring, ledger.active_deposit_ring), - (100 * COIN, 100 * COIN), - ); + assert_ok!(Staking::deposit_extra(Origin::signed(controller), 1000, 12)); + let ledger = Staking::ledger(controller).unwrap(); + // Only deposit_ring, no normal_ring. + assert_eq!((ledger.active_ring, ledger.active_deposit_ring), (1000, 1000)); + let _ = Ring::deposit_creating(&stash, 1000); assert_ok!(Staking::bond_extra( Origin::signed(stash), - StakingBalance::Ring(100 * COIN), + StakingBalance::Ring(1000), 0, )); - Kton::deposit_creating(&stash, 10 * COIN); + Kton::deposit_creating(&stash, 1000); assert_ok!(Staking::bond_extra( Origin::signed(stash), - StakingBalance::Kton(10 * COIN), + StakingBalance::Kton(1000), 0, )); - assert_ok!(Staking::unbond( - Origin::signed(controller), - StakingBalance::Ring(10 * COIN), - )); - let ledger = Staking::ledger(&controller).unwrap(); + assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(10))); + let ledger = Staking::ledger(controller).unwrap(); let unbondings = ( ledger.ring_staking_lock.unbondings.clone(), ledger.kton_staking_lock.unbondings.clone(), ); assert_eq!( (ledger.active_ring, ledger.active_deposit_ring), - (190 * COIN, 100 * COIN), + (1000 + 1000 - 10, 1000), ); >::insert( @@ -641,7 +2764,7 @@ fn slash_should_not_touch_unbondings() { &Staking::stakers(&stash), &mut vec![], ); - let ledger = Staking::ledger(&controller).unwrap(); + let ledger = Staking::ledger(controller).unwrap(); assert_eq!( ( ledger.ring_staking_lock.unbondings.clone(), @@ -653,29 +2776,6 @@ fn slash_should_not_touch_unbondings() { }); } -#[test] -fn bond_over_max_promise_month_should_fail() { - ExtBuilder::default().build().execute_with(|| { - gen_paired_account!(stash(123), controller(456)); - assert_err!( - Staking::bond( - Origin::signed(stash), - controller, - StakingBalance::Ring(COIN), - RewardDestination::Stash, - 37, - ), - "months at most is 36.", - ); - - gen_paired_account!(stash(123), controller(456), promise_month(12)); - assert_err!( - Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(COIN), 37), - "months at most is 36.", - ); - }); -} - #[test] fn check_stash_already_bonded_and_controller_already_paired() { ExtBuilder::default().build().execute_with(|| { @@ -688,7 +2788,7 @@ fn check_stash_already_bonded_and_controller_already_paired() { RewardDestination::Stash, 0, ), - "stash already bonded", + err::STASH_ALREADY_BONDED, ); assert_err!( Staking::bond( @@ -698,7 +2798,7 @@ fn check_stash_already_bonded_and_controller_already_paired() { RewardDestination::Stash, 0, ), - "controller already paired", + err::CONTROLLER_ALREADY_PAIRED, ); }); } @@ -741,12 +2841,12 @@ fn pool_should_be_increased_and_decreased_correctly() { assert_eq!(Staking::kton_pool(), kton_pool); // claim: 50Ring - assert_ok!(Staking::claim_deposits_with_punish( + assert_ok!(Staking::try_claim_deposits_with_punish( Origin::signed(controller_2), - (promise_month * MONTH_IN_SECONDS) as u64, + promise_month * MONTH_IN_MILLISECONDS, )); // unbond deposit items: 12.5Ring - Timestamp::set_timestamp((promise_month * MONTH_IN_SECONDS) as u64); + Timestamp::set_timestamp(promise_month * MONTH_IN_MILLISECONDS); assert_ok!(Staking::unbond( Origin::signed(controller_2), StakingBalance::Ring(125 * COIN / 10), @@ -794,7 +2894,7 @@ fn pool_should_be_increased_and_decreased_correctly() { #[test] fn unbond_over_max_unbondings_chunks_should_fail() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { + ExtBuilder::default().build().execute_with(|| { gen_paired_account!(stash(123), controller(456)); assert_ok!(Staking::bond( Origin::signed(stash), @@ -811,7 +2911,7 @@ fn unbond_over_max_unbondings_chunks_should_fail() { assert_err!( Staking::unbond(Origin::signed(controller), StakingBalance::Ring(1)), - "can not schedule more unlock chunks", + err::UNLOCK_CHUNKS_REACH_MAX, ); }); } @@ -821,7 +2921,7 @@ fn promise_extra_should_not_remove_unexpired_items() { ExtBuilder::default().build().execute_with(|| { gen_paired_account!(stash(123), controller(456), promise_month(12)); let expired_items_len = 3; - let expiry_date = (promise_month * MONTH_IN_SECONDS) as u64; + let expiry_date = promise_month * MONTH_IN_MILLISECONDS; assert_ok!(Staking::bond_extra( Origin::signed(stash), @@ -839,7 +2939,7 @@ fn promise_extra_should_not_remove_unexpired_items() { promise_month, )); assert_eq!( - Staking::ledger(&controller).unwrap().deposit_items.len(), + Staking::ledger(controller).unwrap().deposit_items.len(), 2 + expired_items_len, ); @@ -849,7 +2949,7 @@ fn promise_extra_should_not_remove_unexpired_items() { 2 * COIN, promise_month, )); - assert_eq!(Staking::ledger(&controller).unwrap().deposit_items.len(), 2); + assert_eq!(Staking::ledger(controller).unwrap().deposit_items.len(), 2); }); } @@ -857,12 +2957,12 @@ fn promise_extra_should_not_remove_unexpired_items() { fn unbond_zero() { ExtBuilder::default().build().execute_with(|| { gen_paired_account!(stash(123), controller(456), promise_month(12)); - let ledger = Staking::ledger(&controller).unwrap(); + let ledger = Staking::ledger(controller).unwrap(); - Timestamp::set_timestamp((promise_month * MONTH_IN_SECONDS) as u64); + Timestamp::set_timestamp(promise_month * MONTH_IN_MILLISECONDS); assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(0))); assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Kton(0))); - assert_eq!(Staking::ledger(&controller).unwrap(), ledger); + assert_eq!(Staking::ledger(controller).unwrap(), ledger); }); } @@ -893,23 +2993,23 @@ fn yakio_q1() { assert_eq!(Kton::free_balance(&stash), 4); assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(1), 36)); - assert_eq!(Staking::ledger(&controller).unwrap().active_kton, 1); + assert_eq!(Staking::ledger(controller).unwrap().active_kton, 1); assert_ok!(Staking::nominate(Origin::signed(controller), vec![controller])); - assert_ok!(Staking::claim_deposits_with_punish( + assert_ok!(Staking::try_claim_deposits_with_punish( Origin::signed(controller), - (12 * MONTH_IN_SECONDS) as u64, + 12 * MONTH_IN_MILLISECONDS, )); assert_eq!(Kton::free_balance(&stash), 1); - let ledger = Staking::ledger(&controller).unwrap(); + let ledger = Staking::ledger(controller).unwrap(); // not enough Kton to unbond - assert_ok!(Staking::claim_deposits_with_punish( + assert_ok!(Staking::try_claim_deposits_with_punish( Origin::signed(controller), - (36 * MONTH_IN_SECONDS) as u64, + 36 * MONTH_IN_MILLISECONDS, )); - assert_eq!(Staking::ledger(&controller).unwrap(), ledger); + assert_eq!(Staking::ledger(controller).unwrap(), ledger); }); } @@ -1053,7 +3153,7 @@ fn xavier_q1() { // println!("Unlocking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); // println!( // "Unlocking Transfer - Kton StakingLedger: {:#?}", - // Staking::ledger(&controller) + // Staking::ledger(controller) // ); // println!(); assert_eq!(Timestamp::get(), BondingDuration::get() + unbond_start); @@ -1091,7 +3191,7 @@ fn xavier_q1() { }] ); assert_eq!( - Staking::ledger(&controller).unwrap(), + Staking::ledger(controller).unwrap(), StakingLedger { stash: 123, active_ring: 0, @@ -1112,7 +3212,7 @@ fn xavier_q1() { // println!("Unlocking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); // println!( // "Unlocking Transfer - Kton StakingLedger: {:#?}", - // Staking::ledger(&controller) + // Staking::ledger(controller) // ); // println!(); }); @@ -1203,7 +3303,7 @@ fn xavier_q1() { // println!("Unlocking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); // println!( // "Unlocking Transfer - Ring StakingLedger: {:#?}", - // Staking::ledger(&controller) + // Staking::ledger(controller) // ); // println!(); assert_eq!(Timestamp::get(), BondingDuration::get() + unbond_start); @@ -1241,7 +3341,7 @@ fn xavier_q1() { }] ); assert_eq!( - Staking::ledger(&controller).unwrap(), + Staking::ledger(controller).unwrap(), StakingLedger { stash: 123, active_ring: 20, @@ -1253,7 +3353,7 @@ fn xavier_q1() { unbondings: vec![NormalLock { amount: 9, until: BondingDuration::get() + unbond_start, - }] + }], }, kton_staking_lock: Default::default(), } @@ -1262,7 +3362,7 @@ fn xavier_q1() { // println!("Unlocking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); // println!( // "Unlocking Transfer - Ring StakingLedger: {:#?}", - // Staking::ledger(&controller) + // Staking::ledger(controller) // ); // println!(); }); @@ -1694,7 +3794,7 @@ fn xavier_q3() { )); assert_eq!(Timestamp::get(), 1); assert_eq!( - Staking::ledger(&controller).unwrap(), + Staking::ledger(controller).unwrap(), StakingLedger { stash: 123, active_ring: 0, @@ -1709,12 +3809,12 @@ fn xavier_q3() { } ); // println!("Locks: {:#?}", Kton::locks(stash)); - // println!("StakingLedger: {:#?}", Staking::ledger(&controller)); + // println!("StakingLedger: {:#?}", Staking::ledger(controller)); // println!(); assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(5))); assert_eq!( - Staking::ledger(&controller).unwrap(), + Staking::ledger(controller).unwrap(), StakingLedger { stash: 123, active_ring: 0, @@ -1729,14 +3829,14 @@ fn xavier_q3() { } ); // println!("Locks: {:#?}", Kton::locks(stash)); - // println!("StakingLedger: {:#?}", Staking::ledger(&controller)); + // println!("StakingLedger: {:#?}", Staking::ledger(controller)); // println!(); Timestamp::set_timestamp(61); assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(1), 0)); assert_eq!(Timestamp::get(), 61); assert_eq!( - Staking::ledger(&controller).unwrap(), + Staking::ledger(controller).unwrap(), StakingLedger { stash: 123, active_ring: 0, @@ -1751,7 +3851,7 @@ fn xavier_q3() { } ); // println!("Locks: {:#?}", Kton::locks(stash)); - // println!("StakingLedger: {:#?}", Staking::ledger(&controller)); + // println!("StakingLedger: {:#?}", Staking::ledger(controller)); // println!(); }); @@ -1770,7 +3870,7 @@ fn xavier_q3() { )); assert_eq!(Timestamp::get(), 1); assert_eq!( - Staking::ledger(&controller).unwrap(), + Staking::ledger(controller).unwrap(), StakingLedger { stash: 123, active_ring: 5, @@ -1785,12 +3885,12 @@ fn xavier_q3() { } ); // println!("Locks: {:#?}", Ring::locks(stash)); - // println!("StakingLedger: {:#?}", Staking::ledger(&controller)); + // println!("StakingLedger: {:#?}", Staking::ledger(controller)); // println!(); assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(5))); assert_eq!( - Staking::ledger(&controller).unwrap(), + Staking::ledger(controller).unwrap(), StakingLedger { stash: 123, active_ring: 0, @@ -1805,14 +3905,14 @@ fn xavier_q3() { } ); // println!("Locks: {:#?}", Ring::locks(stash)); - // println!("StakingLedger: {:#?}", Staking::ledger(&controller)); + // println!("StakingLedger: {:#?}", Staking::ledger(controller)); // println!(); Timestamp::set_timestamp(61); assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(1), 0)); assert_eq!(Timestamp::get(), 61); assert_eq!( - Staking::ledger(&controller).unwrap(), + Staking::ledger(controller).unwrap(), StakingLedger { stash: 123, active_ring: 1, @@ -1827,7 +3927,7 @@ fn xavier_q3() { } ); // println!("Locks: {:#?}", Ring::locks(stash)); - // println!("StakingLedger: {:#?}", Staking::ledger(&controller)); + // println!("StakingLedger: {:#?}", Staking::ledger(controller)); // println!(); }); } diff --git a/srml/support/src/lib.rs b/srml/support/src/lib.rs index 8309d24bc..67da0551f 100644 --- a/srml/support/src/lib.rs +++ b/srml/support/src/lib.rs @@ -6,17 +6,12 @@ pub use srml_support::traits::{LockIdentifier, WithdrawReason, WithdrawReasons}; pub use structs::*; pub use traits::*; -pub type TimeStamp = u64; - mod structs { use codec::{Decode, Encode}; - use rstd::{convert::TryInto, vec::Vec}; - use sr_primitives::{ - traits::{SaturatedConversion, SimpleArithmetic}, - RuntimeDebug, - }; + use rstd::vec::Vec; + use sr_primitives::{traits::SimpleArithmetic, RuntimeDebug}; - use super::{LockIdentifier, TimeStamp, WithdrawReasons}; + use crate::{LockIdentifier, WithdrawReasons}; #[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug)] pub struct BalanceLock { @@ -28,18 +23,18 @@ mod structs { #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] pub enum WithdrawLock { Normal(NormalLock), - WithStaking(StakingLock), + WithStaking(StakingLock), } impl WithdrawLock where Balance: Copy + Default + SimpleArithmetic, - Moment: Copy + PartialOrd + TryInto, + Moment: Copy + PartialOrd, { pub fn can_withdraw(&self, at: Moment, new_balance: Balance) -> bool { match self { WithdrawLock::Normal(lock) => lock.can_withdraw(at, new_balance), - WithdrawLock::WithStaking(lock) => lock.can_withdraw(at.saturated_into::(), new_balance), + WithdrawLock::WithStaking(lock) => lock.can_withdraw(at, new_balance), } } } @@ -90,13 +85,18 @@ mod structs { new_balance >= locked_amount } + + #[inline] + pub fn shrink(&mut self, at: Moment) { + self.unbondings.retain(|unbonding| unbonding.valid_at(at)); + } } } mod traits { use srml_support::traits::Currency; - use super::{LockIdentifier, WithdrawLock, WithdrawReasons}; + use crate::{LockIdentifier, WithdrawLock, WithdrawReasons}; pub trait OnMinted { fn on_minted(value: Balance); diff --git a/types.json b/types.json index af8406f14..dc6ee0639 100644 --- a/types.json +++ b/types.json @@ -1,6 +1,5 @@ { "EpochDuration": "u64", - "TimeStamp": "u64", "BalanceLock": { "id": "LockIdentifier", "withdraw_lock": "WithdrawLock", @@ -113,7 +112,6 @@ }, "ValidatorPrefs": { "node_name": "Vec", - "unstake_threshold": "Compact", "validator_payment_ratio": "Compact" } } \ No newline at end of file