Rewards and slashing, early-exit of bad sessions (#234)

* Recompile runtime.

* Introduce and enforce block time

* Introduce early session ending.

* Report most of staking module

* rewards, proper early exit and slashing

* Fix build & session logic, introduce tests

* Fixed staking tests.

* Initial test for reward

* Fix test

* Tests for slashing

* Update/fix preset configs

* Fix some tests.

* Fix some staking tests

* Minor fix

* minor cleanups

* Fix build

* Rename timestamp::Value -> Moment

* Require final block of session to be timely
This commit is contained in:
Gav Wood
2018-06-28 16:41:11 +02:00
committed by GitHub
parent f7db1a0db2
commit 079a448454
35 changed files with 905 additions and 474 deletions
+6
View File
@@ -2351,6 +2351,7 @@ dependencies = [
"substrate-runtime-std 0.1.0",
"substrate-runtime-support 0.1.0",
"substrate-runtime-system 0.1.0",
"substrate-runtime-timestamp 0.1.0",
]
[[package]]
@@ -2371,6 +2372,7 @@ dependencies = [
"substrate-runtime-std 0.1.0",
"substrate-runtime-support 0.1.0",
"substrate-runtime-system 0.1.0",
"substrate-runtime-timestamp 0.1.0",
]
[[package]]
@@ -2390,6 +2392,7 @@ dependencies = [
"substrate-runtime-std 0.1.0",
"substrate-runtime-support 0.1.0",
"substrate-runtime-system 0.1.0",
"substrate-runtime-timestamp 0.1.0",
]
[[package]]
@@ -2452,6 +2455,7 @@ dependencies = [
"substrate-runtime-std 0.1.0",
"substrate-runtime-support 0.1.0",
"substrate-runtime-system 0.1.0",
"substrate-runtime-timestamp 0.1.0",
]
[[package]]
@@ -2474,6 +2478,7 @@ dependencies = [
"substrate-runtime-std 0.1.0",
"substrate-runtime-support 0.1.0",
"substrate-runtime-system 0.1.0",
"substrate-runtime-timestamp 0.1.0",
"wabt 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2525,6 +2530,7 @@ dependencies = [
"serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-codec 0.1.0",
"substrate-primitives 0.1.0",
"substrate-runtime-consensus 0.1.0",
"substrate-runtime-io 0.1.0",
"substrate-runtime-primitives 0.1.0",
"substrate-runtime-std 0.1.0",
+9 -4
View File
@@ -49,9 +49,9 @@ pub mod error;
use std::sync::Arc;
use demo_primitives::Hash;
use demo_runtime::{GenesisConfig, ConsensusConfig, CouncilConfig, DemocracyConfig,
SessionConfig, StakingConfig};
use demo_runtime::{Block, BlockId, UncheckedExtrinsic, BuildStorage};
use demo_runtime::{Block, BlockId, UncheckedExtrinsic, BuildStorage, GenesisConfig,
ConsensusConfig, CouncilConfig, DemocracyConfig, SessionConfig, StakingConfig,
TimestampConfig};
use futures::{Future, Sink, Stream};
struct DummyPool;
@@ -107,10 +107,10 @@ pub fn run<I, T>(args: I) -> error::Result<()> where
authorities: vec![god_key.clone()],
}),
system: None,
// block_time: 5, // 5 second block time.
session: Some(SessionConfig {
validators: vec![god_key.clone().into()],
session_length: 720, // that's 1 hour per session.
broken_percent_late: 30,
}),
staking: Some(StakingConfig {
current_era: 0,
@@ -126,6 +126,8 @@ pub fn run<I, T>(args: I) -> error::Result<()> where
validator_count: 12,
sessions_per_era: 24, // 24 hours per era.
bonding_duration: 90, // 90 days per bond.
early_era_slash: 10000,
session_reward: 100,
}),
democracy: Some(DemocracyConfig {
launch_period: 120 * 24 * 14, // 2 weeks per public referendum
@@ -147,6 +149,9 @@ pub fn run<I, T>(args: I) -> error::Result<()> where
cooloff_period: 90 * 120 * 24, // 90 day cooling off period if council member vetoes a proposal.
voting_period: 7 * 120 * 24, // 7 day voting period for council members.
}),
timestamp: Some(TimestampConfig {
period: 5, // 5 second block time.
}),
}.build_storage();
let client = Arc::new(client::new_in_mem::<_, Block, _>(executor, genesis_storage)?);
+6 -2
View File
@@ -185,6 +185,7 @@ mod tests {
session: Some(SessionConfig {
session_length: 2,
validators: vec![One.to_raw_public().into(), Two.to_raw_public().into(), three],
broken_percent_late: 100,
}),
staking: Some(StakingConfig {
sessions_per_era: 2,
@@ -200,9 +201,12 @@ mod tests {
creation_fee: 0,
contract_fee: 0,
reclaim_rebate: 0,
early_era_slash: 0,
session_reward: 0,
}),
democracy: Some(Default::default()),
council: Some(Default::default()),
timestamp: Some(Default::default()),
}.build_storage()
}
@@ -238,7 +242,7 @@ mod tests {
construct_block(
1,
[69u8; 32].into(),
hex!("4f7a61bceecddc19d49fbee53f82402c2a8727c1b2aeb5e5070a59f0777a203b").into(),
hex!("786071057714fdd6ea4595eecd4a0f327908d65f462ff5bca0f700fafce588c9").into(),
vec![BareExtrinsic {
signed: alice(),
index: 0,
@@ -251,7 +255,7 @@ mod tests {
construct_block(
2,
block1().1,
hex!("67c588603dd727601263cf8d6138a2003ffc0df793c5ea34e7defc945da24bf0").into(),
hex!("a7f1259cc6b2fa758542f2996e737f8f0de9dec3a9d32641da348178f48b9fc2").into(),
vec![
BareExtrinsic {
signed: bob(),
+4 -2
View File
@@ -85,9 +85,9 @@ impl consensus::Trait for Concrete {
pub type Consensus = consensus::Module<Concrete>;
impl timestamp::Trait for Concrete {
const SET_POSITION: u32 = 0;
const TIMESTAMP_SET_POSITION: u32 = 0;
type Value = u64;
type Moment = u64;
}
/// Timestamp module for this concrete runtime.
@@ -103,6 +103,7 @@ impl Convert<AccountId, SessionKey> for SessionKeyConversion {
impl session::Trait for Concrete {
type ConvertAccountIdToSessionKey = SessionKeyConversion;
type OnSessionChange = Staking;
}
/// Session module for this concrete runtime.
@@ -182,6 +183,7 @@ impl_outer_config! {
StakingConfig => staking,
DemocracyConfig => democracy,
CouncilConfig => council,
TimestampConfig => timestamp,
}
}
+3
View File
@@ -905,6 +905,7 @@ dependencies = [
"substrate-runtime-std 0.1.0",
"substrate-runtime-support 0.1.0",
"substrate-runtime-system 0.1.0",
"substrate-runtime-timestamp 0.1.0",
]
[[package]]
@@ -927,6 +928,7 @@ dependencies = [
"substrate-runtime-std 0.1.0",
"substrate-runtime-support 0.1.0",
"substrate-runtime-system 0.1.0",
"substrate-runtime-timestamp 0.1.0",
]
[[package]]
@@ -977,6 +979,7 @@ dependencies = [
"serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-codec 0.1.0",
"substrate-primitives 0.1.0",
"substrate-runtime-consensus 0.1.0",
"substrate-runtime-io 0.1.0",
"substrate-runtime-primitives 0.1.0",
"substrate-runtime-std 0.1.0",
+3 -1
View File
@@ -112,7 +112,7 @@ impl<B: LocalBackend<Block>> PolkadotApi for Client<B, LocalCallExecutor<B, Nati
}
fn timestamp(&self, at: &CheckedId) -> Result<Timestamp> {
with_runtime!(self, at, ::runtime::Timestamp::now)
with_runtime!(self, at, ::runtime::Timestamp::get)
}
fn evaluate_block(&self, at: &CheckedId, block: Block) -> Result<bool> {
@@ -216,11 +216,13 @@ mod tests {
session: Some(SessionConfig {
validators: validators(),
session_length: 100,
broken_percent_late: 100,
}),
council: Some(Default::default()),
democracy: Some(Default::default()),
parachains: Some(Default::default()),
staking: Some(Default::default()),
timestamp: Some(Default::default()),
};
::client::new_in_mem(LocalDispatch::new(), genesis_config.build_storage()).unwrap()
+27 -15
View File
@@ -22,7 +22,7 @@ use serde_json;
use substrate_primitives::{AuthorityId, storage::{StorageKey, StorageData}};
use runtime_primitives::{MakeStorage, BuildStorage, StorageMap};
use polkadot_runtime::{GenesisConfig, ConsensusConfig, CouncilConfig, DemocracyConfig,
SessionConfig, StakingConfig};
SessionConfig, StakingConfig, TimestampConfig};
use chain_spec::ChainSpec;
enum Config {
@@ -98,7 +98,8 @@ impl PresetConfig {
system: None,
session: Some(SessionConfig {
validators: initial_authorities.iter().cloned().map(Into::into).collect(),
session_length: 720, // that's 1 hour per session.
session_length: 60, // that's 5 minutes per session.
broken_percent_late: 50,
}),
staking: Some(StakingConfig {
current_era: 0,
@@ -110,32 +111,37 @@ impl PresetConfig {
creation_fee: 0,
contract_fee: 0,
reclaim_rebate: 0,
early_era_slash: 10000,
session_reward: 100,
balances: endowed_accounts.iter().map(|&k|(k, 1u128 << 60)).collect(),
validator_count: 12,
sessions_per_era: 24, // 24 hours per era.
bonding_duration: 90, // 90 days per bond.
sessions_per_era: 12, // 1 hour per era
bonding_duration: 24, // 1 day per bond.
}),
democracy: Some(DemocracyConfig {
launch_period: 120 * 24 * 14, // 2 weeks per public referendum
voting_period: 120 * 24 * 28, // 4 weeks to discuss & vote on an active referendum
minimum_deposit: 1000, // 1000 as the minimum deposit for a referendum
launch_period: 12 * 60 * 24, // 1 day per public referendum
voting_period: 12 * 60 * 24 * 3, // 3 days to discuss & vote on an active referendum
minimum_deposit: 5000, // 12000 as the minimum deposit for a referendum
}),
council: Some(CouncilConfig {
active_council: vec![],
candidacy_bond: 1000, // 1000 to become a council candidate
voter_bond: 100, // 100 down to vote for a candidate
candidacy_bond: 5000, // 5000 to become a council candidate
voter_bond: 1000, // 1000 down to vote for a candidate
present_slash_per_voter: 1, // slash by 1 per voter for an invalid presentation.
carry_count: 24, // carry over the 24 runners-up to the next council election
presentation_duration: 120 * 24, // one day for presenting winners.
approval_voting_period: 7 * 120 * 24, // one week period between possible council elections.
term_duration: 180 * 120 * 24, // 180 day term duration for the council.
carry_count: 6, // carry over the 6 runners-up to the next council election
presentation_duration: 12 * 60 * 24, // one day for presenting winners.
approval_voting_period: 12 * 60 * 24 * 2, // two days period between possible council elections.
term_duration: 12 * 60 * 24 * 24, // 24 day term duration for the council.
desired_seats: 0, // start with no council: we'll raise this once the stake has been dispersed a bit.
inactive_grace_period: 1, // one addition vote should go by before an inactive voter can be reaped.
cooloff_period: 90 * 120 * 24, // 90 day cooling off period if council member vetoes a proposal.
voting_period: 7 * 120 * 24, // 7 day voting period for council members.
cooloff_period: 12 * 60 * 24 * 4, // 4 day cooling off period if council member vetoes a proposal.
voting_period: 12 * 60 * 24, // 1 day voting period for council members.
}),
parachains: Some(Default::default()),
timestamp: Some(TimestampConfig {
period: 5, // 5 second block time.
}),
});
let boot_nodes = vec![
"enode://a93a29fa68d965452bf0ff8c1910f5992fe2273a72a1ee8d3a3482f68512a61974211ba32bb33f051ceb1530b8ba3527fc36224ba6b9910329025e6d9153cf50@104.211.54.233:30333".into(),
@@ -163,6 +169,7 @@ impl PresetConfig {
session: Some(SessionConfig {
validators: initial_authorities.iter().cloned().map(Into::into).collect(),
session_length: 10,
broken_percent_late: 30,
}),
staking: Some(StakingConfig {
current_era: 0,
@@ -178,6 +185,8 @@ impl PresetConfig {
validator_count: 2,
sessions_per_era: 5,
bonding_duration: 2,
early_era_slash: 0,
session_reward: 0,
}),
democracy: Some(DemocracyConfig {
launch_period: 9,
@@ -200,6 +209,9 @@ impl PresetConfig {
voting_period: 20,
}),
parachains: Some(Default::default()),
timestamp: Some(TimestampConfig {
period: 5, // 5 second block time.
}),
});
let boot_nodes = Vec::new();
PresetConfig { genesis_config, boot_nodes }
+4 -2
View File
@@ -126,8 +126,8 @@ impl consensus::Trait for Concrete {
pub type Consensus = consensus::Module<Concrete>;
impl timestamp::Trait for Concrete {
const SET_POSITION: u32 = TIMESTAMP_SET_POSITION;
type Value = u64;
const TIMESTAMP_SET_POSITION: u32 = TIMESTAMP_SET_POSITION;
type Moment = u64;
}
/// Timestamp module for this concrete runtime.
pub type Timestamp = timestamp::Module<Concrete>;
@@ -142,6 +142,7 @@ impl Convert<AccountId, SessionKey> for SessionKeyConversion {
impl session::Trait for Concrete {
type ConvertAccountIdToSessionKey = SessionKeyConversion;
type OnSessionChange = Staking;
}
/// Session module for this concrete runtime.
pub type Session = session::Module<Concrete>;
@@ -213,6 +214,7 @@ impl_outer_config! {
StakingConfig => staking,
DemocracyConfig => democracy,
CouncilConfig => council,
TimestampConfig => timestamp,
ParachainsConfig => parachains,
}
}
+8 -2
View File
@@ -33,7 +33,7 @@ use rstd::marker::PhantomData;
#[cfg(any(feature = "std", test))]
use {runtime_io, runtime_primitives};
pub trait Trait: system::Trait<Hash = primitives::Hash> + session::Trait {
pub trait Trait: session::Trait<Hash = primitives::Hash> {
/// The position of the set_heads call in the block.
const SET_POSITION: u32;
@@ -232,7 +232,7 @@ mod tests {
use runtime_primitives::BuildStorage;
use runtime_primitives::traits::{HasPublicAux, Identity, BlakeTwo256};
use runtime_primitives::testing::{Digest, Header};
use consensus;
use {consensus, timestamp};
#[derive(Clone, Eq, PartialEq)]
pub struct Test;
@@ -254,6 +254,11 @@ mod tests {
}
impl session::Trait for Test {
type ConvertAccountIdToSessionKey = Identity;
type OnSessionChange = ();
}
impl timestamp::Trait for Test {
const TIMESTAMP_SET_POSITION: u32 = 0;
type Moment = u64;
}
impl Trait for Test {
const SET_POSITION: u32 = 0;
@@ -272,6 +277,7 @@ mod tests {
t.extend(session::GenesisConfig::<Test>{
session_length: 1000,
validators: vec![1, 2, 3, 4, 5, 6, 7, 8],
broken_percent_late: 100,
}.build_storage());
t.extend(GenesisConfig::<Test>{
parachains: parachains,
+3
View File
@@ -905,6 +905,7 @@ dependencies = [
"substrate-runtime-std 0.1.0",
"substrate-runtime-support 0.1.0",
"substrate-runtime-system 0.1.0",
"substrate-runtime-timestamp 0.1.0",
]
[[package]]
@@ -927,6 +928,7 @@ dependencies = [
"substrate-runtime-std 0.1.0",
"substrate-runtime-support 0.1.0",
"substrate-runtime-system 0.1.0",
"substrate-runtime-timestamp 0.1.0",
]
[[package]]
@@ -977,6 +979,7 @@ dependencies = [
"serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-codec 0.1.0",
"substrate-primitives 0.1.0",
"substrate-runtime-consensus 0.1.0",
"substrate-runtime-io 0.1.0",
"substrate-runtime-primitives 0.1.0",
"substrate-runtime-std 0.1.0",
@@ -42,7 +42,7 @@ use rstd::prelude::*;
use runtime_support::{storage, Parameter};
use runtime_support::dispatch::Result;
use runtime_support::storage::unhashed::StorageVec;
use primitives::traits::RefInto;
use primitives::traits::{RefInto, MaybeEmpty};
use primitives::bft::MisbehaviorReport;
pub const AUTHORITY_AT: &'static [u8] = b":auth:";
@@ -59,7 +59,7 @@ pub const CODE: &'static [u8] = b":code";
pub type KeyValue = (Vec<u8>, Vec<u8>);
pub trait Trait: system::Trait {
type PublicAux: RefInto<Self::AccountId>;
type PublicAux: RefInto<Self::AccountId> + MaybeEmpty; // MaybeEmpty is for Timestamp's usage.
type SessionKey: Parameter + Default;
}
@@ -22,6 +22,9 @@ substrate-runtime-session = { path = "../session", default_features = false }
substrate-runtime-staking = { path = "../staking", default_features = false }
substrate-runtime-system = { path = "../system", default_features = false }
[dev-dependencies]
substrate-runtime-timestamp = { path = "../timestamp" }
[features]
default = ["std"]
std = [
@@ -38,6 +38,8 @@ extern crate substrate_runtime_democracy as democracy;
extern crate substrate_runtime_session as session;
extern crate substrate_runtime_staking as staking;
extern crate substrate_runtime_system as system;
#[cfg(test)]
extern crate substrate_runtime_timestamp as timestamp;
use rstd::prelude::*;
use primitives::traits::{Zero, One, RefInto, As, AuxLookup};
@@ -646,6 +648,7 @@ mod tests {
}
impl session::Trait for Test {
type ConvertAccountIdToSessionKey = Identity;
type OnSessionChange = staking::Module<Test>;
}
impl staking::Trait for Test {
type Balance = u64;
@@ -655,6 +658,10 @@ mod tests {
impl democracy::Trait for Test {
type Proposal = Proposal;
}
impl timestamp::Trait for Test {
const TIMESTAMP_SET_POSITION: u32 = 0;
type Moment = u64;
}
impl Trait for Test {}
pub fn new_test_ext(with_council: bool) -> runtime_io::TestExternalities {
@@ -666,6 +673,7 @@ mod tests {
t.extend(session::GenesisConfig::<Test>{
session_length: 1, //??? or 2?
validators: vec![10, 20],
broken_percent_late: 100,
}.build_storage());
t.extend(staking::GenesisConfig::<Test>{
sessions_per_era: 1,
@@ -681,6 +689,8 @@ mod tests {
creation_fee: 0,
contract_fee: 0,
reclaim_rebate: 0,
early_era_slash: 0,
session_reward: 0,
}.build_storage());
t.extend(democracy::GenesisConfig::<Test>{
launch_period: 1,
@@ -705,6 +715,7 @@ mod tests {
cooloff_period: 2,
voting_period: 1,
}.build_storage());
t.extend(timestamp::GenesisConfig::<Test>::default().build_storage());
t
}
@@ -19,6 +19,9 @@ substrate-runtime-session = { path = "../session", default_features = false }
substrate-runtime-staking = { path = "../staking", default_features = false }
substrate-runtime-system = { path = "../system", default_features = false }
[dev-dependencies]
substrate-runtime-timestamp = { path = "../timestamp" }
[features]
default = ["std"]
std = [
@@ -41,6 +41,8 @@ extern crate substrate_runtime_consensus as consensus;
extern crate substrate_runtime_session as session;
extern crate substrate_runtime_staking as staking;
extern crate substrate_runtime_system as system;
#[cfg(test)]
extern crate substrate_runtime_timestamp as timestamp;
use rstd::prelude::*;
use rstd::result;
@@ -356,6 +358,7 @@ mod tests {
use primitives::BuildStorage;
use primitives::traits::{HasPublicAux, Identity, BlakeTwo256};
use primitives::testing::{Digest, Header};
use session::OnSessionChange;
impl_outer_dispatch! {
#[derive(Debug, Clone, Eq, Serialize, Deserialize, PartialEq)]
@@ -387,12 +390,17 @@ mod tests {
}
impl session::Trait for Test {
type ConvertAccountIdToSessionKey = Identity;
type OnSessionChange = staking::Module<Test>;
}
impl staking::Trait for Test {
type Balance = u64;
type DetermineContractAddress = staking::DummyContractAddressFor;
type AccountIndex = u64;
}
impl timestamp::Trait for Test {
const TIMESTAMP_SET_POSITION: u32 = 0;
type Moment = u64;
}
impl Trait for Test {
type Proposal = Proposal;
}
@@ -406,6 +414,7 @@ mod tests {
t.extend(session::GenesisConfig::<Test>{
session_length: 1, //??? or 2?
validators: vec![10, 20],
broken_percent_late: 100,
}.build_storage());
t.extend(staking::GenesisConfig::<Test>{
sessions_per_era: 1,
@@ -421,12 +430,15 @@ mod tests {
creation_fee: 0,
contract_fee: 0,
reclaim_rebate: 0,
early_era_slash: 0,
session_reward: 0,
}.build_storage());
t.extend(GenesisConfig::<Test>{
launch_period: 1,
voting_period: 1,
minimum_deposit: 1,
}.build_storage());
t.extend(timestamp::GenesisConfig::<Test>::default().build_storage());
t
}
@@ -481,7 +493,7 @@ mod tests {
assert_eq!(Democracy::tally(r), (10, 0));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
Staking::check_new_era();
Staking::on_session_change(true, 0);
assert_eq!(Staking::era_length(), 2);
});
@@ -559,19 +571,19 @@ mod tests {
System::set_block_number(1);
assert_ok!(Democracy::vote(&1, 0, true));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
Staking::check_new_era();
Staking::on_session_change(true, 0);
assert_eq!(Staking::bonding_duration(), 4);
System::set_block_number(2);
assert_ok!(Democracy::vote(&1, 1, true));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
Staking::check_new_era();
Staking::on_session_change(true, 0);
assert_eq!(Staking::bonding_duration(), 3);
System::set_block_number(3);
assert_ok!(Democracy::vote(&1, 2, true));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
Staking::check_new_era();
Staking::on_session_change(true, 0);
assert_eq!(Staking::bonding_duration(), 2);
});
}
@@ -592,7 +604,7 @@ mod tests {
assert_eq!(Democracy::tally(r), (10, 0));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
Staking::check_new_era();
Staking::on_session_change(true, 0);
assert_eq!(Staking::era_length(), 2);
});
@@ -607,7 +619,7 @@ mod tests {
assert_ok!(Democracy::cancel_referendum(r));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
Staking::check_new_era();
Staking::on_session_change(true, 0);
assert_eq!(Staking::era_length(), 1);
});
@@ -625,7 +637,7 @@ mod tests {
assert_eq!(Democracy::tally(r), (0, 10));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
Staking::check_new_era();
Staking::on_session_change(true, 0);
assert_eq!(Staking::era_length(), 1);
});
@@ -646,7 +658,7 @@ mod tests {
assert_eq!(Democracy::tally(r), (110, 100));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
Staking::check_new_era();
Staking::on_session_change(true, 0);
assert_eq!(Staking::era_length(), 2);
});
@@ -663,7 +675,7 @@ mod tests {
assert_eq!(Democracy::tally(r), (60, 50));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
Staking::check_new_era();
Staking::on_session_change(true, 0);
assert_eq!(Staking::era_length(), 1);
});
@@ -684,7 +696,7 @@ mod tests {
assert_eq!(Democracy::tally(r), (100, 50));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
Staking::check_new_era();
Staking::on_session_change(true, 0);
assert_eq!(Staking::era_length(), 2);
});
@@ -19,6 +19,7 @@ substrate-primitives = { path = "../../primitives" }
substrate-runtime-session = { path = "../session" }
substrate-runtime-staking = { path = "../staking" }
substrate-runtime-consensus = { path = "../consensus" }
substrate-runtime-timestamp = { path = "../timestamp" }
[features]
default = ["std"]
@@ -30,6 +30,8 @@ extern crate substrate_runtime_io as runtime_io;
extern crate substrate_codec as codec;
extern crate substrate_runtime_primitives as primitives;
extern crate substrate_runtime_system as system;
#[cfg(test)]
extern crate substrate_runtime_timestamp as timestamp;
#[cfg(test)]
#[macro_use]
@@ -250,12 +252,17 @@ mod tests {
}
impl session::Trait for Test {
type ConvertAccountIdToSessionKey = Identity;
type OnSessionChange = staking::Module<Test>;
}
impl staking::Trait for Test {
type Balance = u64;
type DetermineContractAddress = staking::DummyContractAddressFor;
type AccountIndex = u64;
}
impl timestamp::Trait for Test {
const TIMESTAMP_SET_POSITION: u32 = 0;
type Moment = u64;
}
type TestXt = primitives::testing::TestXt<Call<Test>>;
type Executive = super::Executive<Test, Block<TestXt>, NullLookup, staking::Module<Test>, (session::Module<Test>, staking::Module<Test>)>;
@@ -277,6 +284,8 @@ mod tests {
creation_fee: 0,
contract_fee: 0,
reclaim_rebate: 0,
early_era_slash: 0,
session_reward: 0,
}.build_storage());
let xt = primitives::testing::TestXt((1, 0, Call::transfer(2.into(), 69)));
with_externalities(&mut t, || {
@@ -292,6 +301,7 @@ mod tests {
t.extend(consensus::GenesisConfig::<Test>::default().build_storage());
t.extend(session::GenesisConfig::<Test>::default().build_storage());
t.extend(staking::GenesisConfig::<Test>::default().build_storage());
t.extend(timestamp::GenesisConfig::<Test>::default().build_storage());
t
}
@@ -302,7 +312,7 @@ mod tests {
header: Header {
parent_hash: [69u8; 32].into(),
number: 1,
state_root: hex!("4fd406d2d62a841f7e2f956b52ce9ed98111c9eb6b3a9051aa4667b470030832").into(),
state_root: hex!("b47a0bfc249af6e00c71a45fcd5619c47b6f71cb4d5c62ab7bf1fe9601d5efc4").into(),
extrinsics_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(),
digest: Digest { logs: vec![], },
},
@@ -336,7 +346,7 @@ mod tests {
header: Header {
parent_hash: [69u8; 32].into(),
number: 1,
state_root: hex!("4fd406d2d62a841f7e2f956b52ce9ed98111c9eb6b3a9051aa4667b470030832").into(),
state_root: hex!("b47a0bfc249af6e00c71a45fcd5619c47b6f71cb4d5c62ab7bf1fe9601d5efc4").into(),
extrinsics_root: [0u8; 32].into(),
digest: Digest { logs: vec![], },
},
@@ -17,6 +17,7 @@ substrate-runtime-support = { path = "../../runtime-support", default_features =
substrate-runtime-primitives = { path = "../primitives", default_features = false }
substrate-runtime-consensus = { path = "../consensus", default_features = false }
substrate-runtime-system = { path = "../system", default_features = false }
substrate-runtime-timestamp = { path = "../timestamp", default_features = false }
[features]
default = ["std"]
+105 -9
View File
@@ -43,14 +43,26 @@ extern crate substrate_codec as codec;
extern crate substrate_runtime_primitives as primitives;
extern crate substrate_runtime_consensus as consensus;
extern crate substrate_runtime_system as system;
extern crate substrate_runtime_timestamp as timestamp;
use rstd::prelude::*;
use primitives::traits::{Zero, One, RefInto, Executable, Convert};
use primitives::traits::{Zero, One, RefInto, Executable, Convert, As};
use runtime_support::{StorageValue, StorageMap};
use runtime_support::dispatch::Result;
pub trait Trait: consensus::Trait {
/// A session has changed.
pub trait OnSessionChange<T> {
/// Session has changed.
fn on_session_change(normal_rotation: bool, time_elapsed: T);
}
impl<T> OnSessionChange<T> for () {
fn on_session_change(_: bool, _: T) {}
}
pub trait Trait: timestamp::Trait {
type ConvertAccountIdToSessionKey: Convert<Self::AccountId, Self::SessionKey>;
type OnSessionChange: OnSessionChange<Self::Moment>;
}
decl_module! {
@@ -64,7 +76,7 @@ decl_module! {
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub enum PrivCall {
fn set_length(new: T::BlockNumber) -> Result = 0;
fn force_new_session() -> Result = 1;
fn force_new_session(normal_rotation: bool) -> Result = 1;
}
}
decl_storage! {
@@ -76,6 +88,10 @@ decl_storage! {
pub SessionLength get(length): b"ses:len" => required T::BlockNumber;
// Current index of the session.
pub CurrentIndex get(current_index): b"ses:ind" => required T::BlockNumber;
// Timestamp when current session started.
pub CurrentStart get(current_start): b"ses:current_start" => required T::Moment;
// Percent by which the session must necessarily finish late before we early-exit the session.
pub BrokenPercentLate get(broken_percent_late): b"ses:broken_percent_late" => required T::Moment;
// Block at which the session length last changed.
LastLengthChange: b"ses:llc" => T::BlockNumber;
@@ -111,8 +127,8 @@ impl<T: Trait> Module<T> {
}
/// Forces a new session.
fn force_new_session() -> Result {
Self::rotate_session();
fn force_new_session(normal_rotation: bool) -> Result {
Self::rotate_session(normal_rotation);
Ok(())
}
@@ -135,15 +151,21 @@ impl<T: Trait> Module<T> {
// new set.
// check block number and call next_session if necessary.
let block_number = <system::Module<T>>::block_number();
if ((block_number - Self::last_length_change()) % Self::length()).is_zero() {
Self::rotate_session();
let is_final_block = ((block_number - Self::last_length_change()) % Self::length()).is_zero();
let broken_validation = Self::broken_validation();
if is_final_block || broken_validation {
Self::rotate_session(!broken_validation);
}
}
/// Move onto next session: register the new authority set.
pub fn rotate_session() {
pub fn rotate_session(normal_rotation: bool) {
let now = <timestamp::Module<T>>::get();
let time_elapsed = now.clone() - Self::current_start();
// Increment current session index.
<CurrentIndex<T>>::put(<CurrentIndex<T>>::get() + One::one());
<CurrentStart<T>>::put(now);
// Enact era length change.
if let Some(next_len) = <NextSessionLength<T>>::take() {
@@ -152,6 +174,8 @@ impl<T: Trait> Module<T> {
<LastLengthChange<T>>::put(block_number);
}
T::OnSessionChange::on_session_change(normal_rotation, time_elapsed);
// Update any changes in session keys.
Self::validators().iter().enumerate().for_each(|(i, v)| {
if let Some(n) = <NextKeyFor<T>>::take(v) {
@@ -159,6 +183,34 @@ impl<T: Trait> Module<T> {
}
});
}
/// Get the time that should have elapsed over a session if everything was working perfectly.
pub fn ideal_session_duration() -> T::Moment {
let block_period = <timestamp::Module<T>>::block_period();
let session_length = <T::Moment as As<T::BlockNumber>>::sa(Self::length());
session_length * block_period
}
/// Number of blocks remaining in this session, not counting this one. If the session is
/// due to rotate at the end of this block, then it will return 0. If the just began, then
/// it will return `Self::length() - 1`.
pub fn blocks_remaining() -> T::BlockNumber {
let length = Self::length();
let length_minus_1 = length - One::one();
let block_number = <system::Module<T>>::block_number();
length_minus_1 - (block_number - Self::last_length_change() + length_minus_1) % length
}
/// Returns `true` if the current validator set is taking took long to validate blocks.
pub fn broken_validation() -> bool {
let now = <timestamp::Module<T>>::get();
let block_period = <timestamp::Module<T>>::block_period();
let blocks_remaining = Self::blocks_remaining();
let blocks_remaining = <T::Moment as As<T::BlockNumber>>::sa(blocks_remaining);
now + blocks_remaining * block_period >
Self::current_start() + Self::ideal_session_duration() *
(T::Moment::sa(100) + Self::broken_percent_late()) / T::Moment::sa(100)
}
}
impl<T: Trait> Executable for Module<T> {
@@ -171,6 +223,7 @@ impl<T: Trait> Executable for Module<T> {
pub struct GenesisConfig<T: Trait> {
pub session_length: T::BlockNumber,
pub validators: Vec<T::AccountId>,
pub broken_percent_late: T::Moment,
}
#[cfg(any(feature = "std", test))]
@@ -180,6 +233,7 @@ impl<T: Trait> Default for GenesisConfig<T> {
GenesisConfig {
session_length: T::BlockNumber::sa(1000),
validators: vec![],
broken_percent_late: T::Moment::sa(30),
}
}
}
@@ -194,7 +248,9 @@ impl<T: Trait> primitives::BuildStorage for GenesisConfig<T>
map![
twox_128(<SessionLength<T>>::key()).to_vec() => self.session_length.encode(),
twox_128(<CurrentIndex<T>>::key()).to_vec() => T::BlockNumber::sa(0).encode(),
twox_128(<Validators<T>>::key()).to_vec() => self.validators.encode()
twox_128(<CurrentStart<T>>::key()).to_vec() => T::Moment::zero().encode(),
twox_128(<Validators<T>>::key()).to_vec() => self.validators.encode(),
twox_128(<BrokenPercentLate<T>>::key()).to_vec() => self.broken_percent_late.encode()
]
}
}
@@ -226,12 +282,18 @@ mod tests {
type AccountId = u64;
type Header = Header;
}
impl timestamp::Trait for Test {
const TIMESTAMP_SET_POSITION: u32 = 0;
type Moment = u64;
}
impl Trait for Test {
type ConvertAccountIdToSessionKey = Identity;
type OnSessionChange = ();
}
type System = system::Module<Test>;
type Consensus = consensus::Module<Test>;
type Timestamp = timestamp::Module<Test>;
type Session = Module<Test>;
fn new_test_ext() -> runtime_io::TestExternalities {
@@ -240,9 +302,13 @@ mod tests {
code: vec![],
authorities: vec![1, 2, 3],
}.build_storage());
t.extend(timestamp::GenesisConfig::<Test>{
period: 5,
}.build_storage());
t.extend(GenesisConfig::<Test>{
session_length: 2,
validators: vec![1, 2, 3],
broken_percent_late: 30,
}.build_storage());
t
}
@@ -256,6 +322,36 @@ mod tests {
});
}
#[test]
fn should_identify_broken_validation() {
with_externalities(&mut new_test_ext(), || {
System::set_block_number(2);
assert_eq!(Session::blocks_remaining(), 0);
Timestamp::set_timestamp(0);
assert_ok!(Session::set_length(3));
Session::check_rotate_session();
assert_eq!(Session::current_index(), 1);
assert_eq!(Session::length(), 3);
assert_eq!(Session::current_start(), 0);
assert_eq!(Session::ideal_session_duration(), 15);
// ideal end = 0 + 15 * 3 = 15
// broken_limit = 15 * 130 / 100 = 19
System::set_block_number(3);
assert_eq!(Session::blocks_remaining(), 2);
Timestamp::set_timestamp(9); // earliest end = 9 + 2 * 5 = 19; OK.
assert!(!Session::broken_validation());
Session::check_rotate_session();
System::set_block_number(4);
assert_eq!(Session::blocks_remaining(), 1);
Timestamp::set_timestamp(15); // another 1 second late. earliest end = 15 + 1 * 5 = 20; broken.
assert!(Session::broken_validation());
Session::check_rotate_session();
assert_eq!(Session::current_index(), 2);
});
}
#[test]
fn session_length_change_should_work() {
with_externalities(&mut new_test_ext(), || {
@@ -20,6 +20,7 @@ substrate-runtime-primitives = { path = "../primitives", default_features = fals
substrate-runtime-consensus = { path = "../consensus", default_features = false }
substrate-runtime-system = { path = "../system", default_features = false }
substrate-runtime-session = { path = "../session", default_features = false }
substrate-runtime-timestamp = { path = "../timestamp", default_features = false }
[dev-dependencies]
wabt = "0.1.7"
@@ -0,0 +1,224 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate Demo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate Demo. If not, see <http://www.gnu.org/licenses/>.
//! Auxilliaries to help with managing partial changes to accounts state.
use rstd::prelude::*;
use rstd::cell::RefCell;
use rstd::collections::btree_map::{BTreeMap, Entry};
use runtime_support::StorageMap;
use super::*;
pub struct ChangeEntry<T: Trait> {
balance: Option<T::Balance>,
code: Option<Vec<u8>>,
storage: BTreeMap<Vec<u8>, Option<Vec<u8>>>,
}
// Cannot derive(Default) since it erroneously bounds T by Default.
impl<T: Trait> Default for ChangeEntry<T> {
fn default() -> Self {
ChangeEntry {
balance: Default::default(),
code: Default::default(),
storage: Default::default(),
}
}
}
impl<T: Trait> ChangeEntry<T> {
pub fn contract_created(b: T::Balance, c: Vec<u8>) -> Self {
ChangeEntry { balance: Some(b), code: Some(c), storage: Default::default() }
}
pub fn balance_changed(b: T::Balance) -> Self {
ChangeEntry { balance: Some(b), code: None, storage: Default::default() }
}
}
pub type State<T> = BTreeMap<<T as system::Trait>::AccountId, ChangeEntry<T>>;
pub trait AccountDb<T: Trait> {
fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option<Vec<u8>>;
fn get_code(&self, account: &T::AccountId) -> Vec<u8>;
fn get_balance(&self, account: &T::AccountId) -> T::Balance;
fn merge(&mut self, state: State<T>);
}
pub struct DirectAccountDb;
impl<T: Trait> AccountDb<T> for DirectAccountDb {
fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option<Vec<u8>> {
<StorageOf<T>>::get(&(account.clone(), location.to_vec()))
}
fn get_code(&self, account: &T::AccountId) -> Vec<u8> {
<CodeOf<T>>::get(account)
}
fn get_balance(&self, account: &T::AccountId) -> T::Balance {
<FreeBalance<T>>::get(account)
}
fn merge(&mut self, s: State<T>) {
let ed = <Module<T>>::existential_deposit();
for (address, changed) in s.into_iter() {
if let Some(balance) = changed.balance {
// If the balance is too low, then the account is reaped.
// NOTE: There are two balances for every account: `reserved_balance` and
// `free_balance`. This contract subsystem only cares about the latter: whenever
// the term "balance" is used *here* it should be assumed to mean "free balance"
// in the rest of the module.
// Free balance can never be less than ED. If that happens, it gets reduced to zero
// and the account information relevant to this subsystem is deleted (i.e. the
// account is reaped).
// NOTE: This is orthogonal to the `Bondage` value that an account has, a high
// value of which makes even the `free_balance` unspendable.
// TODO: enforce this for the other balance-altering functions.
if balance < ed {
<Module<T>>::on_free_too_low(&address);
continue;
} else {
if !<FreeBalance<T>>::exists(&address) {
let outcome = <Module<T>>::new_account(&address, balance);
let credit = match outcome {
NewAccountOutcome::GoodHint => balance + <Module<T>>::reclaim_rebate(),
_ => balance,
};
<FreeBalance<T>>::insert(&address, credit);
} else {
<FreeBalance<T>>::insert(&address, balance);
}
}
}
if let Some(code) = changed.code {
<CodeOf<T>>::insert(&address, &code);
}
for (k, v) in changed.storage.into_iter() {
if let Some(value) = v {
<StorageOf<T>>::insert((address.clone(), k), &value);
} else {
<StorageOf<T>>::remove((address.clone(), k));
}
}
}
}
}
pub struct OverlayAccountDb<'a, T: Trait + 'a> {
local: RefCell<State<T>>,
underlying: &'a AccountDb<T>,
}
impl<'a, T: Trait> OverlayAccountDb<'a, T> {
pub fn new(underlying: &'a AccountDb<T>) -> OverlayAccountDb<'a, T> {
OverlayAccountDb {
local: RefCell::new(State::new()),
underlying,
}
}
pub fn into_state(self) -> State<T> {
self.local.into_inner()
}
fn set_storage(&mut self, account: &T::AccountId, location: Vec<u8>, value: Option<Vec<u8>>) {
self.local
.borrow_mut()
.entry(account.clone())
.or_insert(Default::default())
.storage
.insert(location, value);
}
pub fn set_balance(&mut self, account: &T::AccountId, balance: T::Balance) {
self.local
.borrow_mut()
.entry(account.clone())
.or_insert(Default::default())
.balance = Some(balance);
}
}
impl<'a, T: Trait> AccountDb<T> for OverlayAccountDb<'a, T> {
fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option<Vec<u8>> {
self.local
.borrow()
.get(account)
.and_then(|a| a.storage.get(location))
.cloned()
.unwrap_or_else(|| self.underlying.get_storage(account, location))
}
fn get_code(&self, account: &T::AccountId) -> Vec<u8> {
self.local
.borrow()
.get(account)
.and_then(|a| a.code.clone())
.unwrap_or_else(|| self.underlying.get_code(account))
}
fn get_balance(&self, account: &T::AccountId) -> T::Balance {
self.local
.borrow()
.get(account)
.and_then(|a| a.balance)
.unwrap_or_else(|| self.underlying.get_balance(account))
}
fn merge(&mut self, s: State<T>) {
let mut local = self.local.borrow_mut();
for (address, changed) in s.into_iter() {
match local.entry(address) {
Entry::Occupied(e) => {
let mut value = e.into_mut();
if changed.balance.is_some() {
value.balance = changed.balance;
}
if changed.code.is_some() {
value.code = changed.code;
}
value.storage.extend(changed.storage.into_iter());
}
Entry::Vacant(e) => {
e.insert(changed);
}
}
}
}
}
pub(crate) struct StakingExt<'a, 'b: 'a, T: Trait + 'b> {
pub account_db: &'a mut OverlayAccountDb<'b, T>,
pub account: T::AccountId,
}
impl<'a, 'b: 'a, T: Trait> contract::Ext for StakingExt<'a, 'b, T> {
type AccountId = T::AccountId;
type Balance = T::Balance;
fn get_storage(&self, key: &[u8]) -> Option<Vec<u8>> {
self.account_db.get_storage(&self.account, key)
}
fn set_storage(&mut self, key: &[u8], value: Option<Vec<u8>>) {
self.account_db.set_storage(&self.account, key.to_vec(), value);
}
fn create(&mut self, code: &[u8], value: Self::Balance) {
if let Ok(Some(commit_state)) =
Module::<T>::effect_create(&self.account, code, value, self.account_db)
{
self.account_db.merge(commit_state);
}
}
fn transfer(&mut self, to: &Self::AccountId, value: Self::Balance) {
if let Ok(Some(commit_state)) =
Module::<T>::effect_transfer(&self.account, to, value, self.account_db)
{
self.account_db.merge(commit_state);
}
}
}
@@ -0,0 +1,155 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate Demo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate Demo. If not, see <http://www.gnu.org/licenses/>.
//! Build a staking genesis block.
#![cfg(feature = "std")]
use rstd::prelude::*;
use runtime_io::twox_128;
use codec::Slicable;
use runtime_support::{StorageValue, StorageMap};
use primitives::traits::{Zero, As};
use {runtime_io, primitives};
use super::{Trait, ENUM_SET_SIZE, EnumSet, NextEnumSet, Intentions, CurrentEra,
BondingDuration, ContractFee, CreationFee, TransferFee, ReclaimRebate,
ExistentialDeposit, TransactionByteFee, TransactionBaseFee, TotalStake,
SessionsPerEra, ValidatorCount, FreeBalance, SessionReward, EarlyEraSlash};
pub struct GenesisConfig<T: Trait> {
pub sessions_per_era: T::BlockNumber,
pub current_era: T::BlockNumber,
pub balances: Vec<(T::AccountId, T::Balance)>,
pub intentions: Vec<T::AccountId>,
pub validator_count: u64,
pub bonding_duration: T::BlockNumber,
pub transaction_base_fee: T::Balance,
pub transaction_byte_fee: T::Balance,
pub transfer_fee: T::Balance,
pub creation_fee: T::Balance,
pub contract_fee: T::Balance,
pub reclaim_rebate: T::Balance,
pub existential_deposit: T::Balance,
pub session_reward: T::Balance,
pub early_era_slash: T::Balance,
}
impl<T: Trait> GenesisConfig<T> where T::AccountId: From<u64> {
pub fn simple() -> Self {
GenesisConfig {
sessions_per_era: T::BlockNumber::sa(2),
current_era: T::BlockNumber::sa(0),
balances: vec![(T::AccountId::from(1), T::Balance::sa(111))],
intentions: vec![T::AccountId::from(1), T::AccountId::from(2), T::AccountId::from(3)],
validator_count: 3,
bonding_duration: T::BlockNumber::sa(0),
transaction_base_fee: T::Balance::sa(0),
transaction_byte_fee: T::Balance::sa(0),
transfer_fee: T::Balance::sa(0),
creation_fee: T::Balance::sa(0),
contract_fee: T::Balance::sa(0),
existential_deposit: T::Balance::sa(0),
reclaim_rebate: T::Balance::sa(0),
session_reward: T::Balance::sa(0),
early_era_slash: T::Balance::sa(0),
}
}
pub fn extended() -> Self {
GenesisConfig {
sessions_per_era: T::BlockNumber::sa(3),
current_era: T::BlockNumber::sa(1),
balances: vec![
(T::AccountId::from(1), T::Balance::sa(10)),
(T::AccountId::from(2), T::Balance::sa(20)),
(T::AccountId::from(3), T::Balance::sa(30)),
(T::AccountId::from(4), T::Balance::sa(40)),
(T::AccountId::from(5), T::Balance::sa(50)),
(T::AccountId::from(6), T::Balance::sa(60)),
(T::AccountId::from(7), T::Balance::sa(1))
],
intentions: vec![T::AccountId::from(1), T::AccountId::from(2), T::AccountId::from(3)],
validator_count: 3,
bonding_duration: T::BlockNumber::sa(0),
transaction_base_fee: T::Balance::sa(1),
transaction_byte_fee: T::Balance::sa(0),
transfer_fee: T::Balance::sa(0),
creation_fee: T::Balance::sa(0),
contract_fee: T::Balance::sa(0),
existential_deposit: T::Balance::sa(0),
reclaim_rebate: T::Balance::sa(0),
session_reward: T::Balance::sa(0),
early_era_slash: T::Balance::sa(0),
}
}
}
impl<T: Trait> Default for GenesisConfig<T> {
fn default() -> Self {
GenesisConfig {
sessions_per_era: T::BlockNumber::sa(1000),
current_era: T::BlockNumber::sa(0),
balances: vec![],
intentions: vec![],
validator_count: 0,
bonding_duration: T::BlockNumber::sa(1000),
transaction_base_fee: T::Balance::sa(0),
transaction_byte_fee: T::Balance::sa(0),
transfer_fee: T::Balance::sa(0),
creation_fee: T::Balance::sa(0),
contract_fee: T::Balance::sa(0),
existential_deposit: T::Balance::sa(0),
reclaim_rebate: T::Balance::sa(0),
session_reward: T::Balance::sa(0),
early_era_slash: T::Balance::sa(0),
}
}
}
impl<T: Trait> primitives::BuildStorage for GenesisConfig<T> {
fn build_storage(self) -> runtime_io::TestExternalities {
let total_stake: T::Balance = self.balances.iter().fold(Zero::zero(), |acc, &(_, n)| acc + n);
let mut r: runtime_io::TestExternalities = map![
twox_128(<NextEnumSet<T>>::key()).to_vec() => T::AccountIndex::sa(self.balances.len() / ENUM_SET_SIZE).encode(),
twox_128(<Intentions<T>>::key()).to_vec() => self.intentions.encode(),
twox_128(<SessionsPerEra<T>>::key()).to_vec() => self.sessions_per_era.encode(),
twox_128(<ValidatorCount<T>>::key()).to_vec() => self.validator_count.encode(),
twox_128(<BondingDuration<T>>::key()).to_vec() => self.bonding_duration.encode(),
twox_128(<TransactionBaseFee<T>>::key()).to_vec() => self.transaction_base_fee.encode(),
twox_128(<TransactionByteFee<T>>::key()).to_vec() => self.transaction_byte_fee.encode(),
twox_128(<TransferFee<T>>::key()).to_vec() => self.transfer_fee.encode(),
twox_128(<CreationFee<T>>::key()).to_vec() => self.creation_fee.encode(),
twox_128(<ContractFee<T>>::key()).to_vec() => self.contract_fee.encode(),
twox_128(<ExistentialDeposit<T>>::key()).to_vec() => self.existential_deposit.encode(),
twox_128(<ReclaimRebate<T>>::key()).to_vec() => self.reclaim_rebate.encode(),
twox_128(<CurrentEra<T>>::key()).to_vec() => self.current_era.encode(),
twox_128(<SessionReward<T>>::key()).to_vec() => self.session_reward.encode(),
twox_128(<EarlyEraSlash<T>>::key()).to_vec() => self.early_era_slash.encode(),
twox_128(<TotalStake<T>>::key()).to_vec() => total_stake.encode()
];
let ids: Vec<_> = self.balances.iter().map(|x| x.0.clone()).collect();
for i in 0..(ids.len() + ENUM_SET_SIZE - 1) / ENUM_SET_SIZE {
r.insert(twox_128(&<EnumSet<T>>::key_for(T::AccountIndex::sa(i))).to_vec(),
ids[i * ENUM_SET_SIZE..ids.len().min((i + 1) * ENUM_SET_SIZE)].to_owned().encode());
}
for (who, value) in self.balances.into_iter() {
r.insert(twox_128(&<FreeBalance<T>>::key_for(who)).to_vec(), value.encode());
}
r
}
}
+76 -362
View File
@@ -43,24 +43,31 @@ extern crate substrate_runtime_consensus as consensus;
extern crate substrate_runtime_sandbox as sandbox;
extern crate substrate_runtime_session as session;
extern crate substrate_runtime_system as system;
extern crate substrate_runtime_timestamp as timestamp;
#[cfg(test)] use std::fmt::Debug;
use account_db::State;
use rstd::prelude::*;
use rstd::{cmp, result};
use rstd::cell::RefCell;
use rstd::collections::btree_map::{BTreeMap, Entry};
use rstd::collections::btree_map::BTreeMap;
use codec::{Input, Slicable};
use runtime_support::{StorageValue, StorageMap, Parameter};
use runtime_support::dispatch::Result;
use session::OnSessionChange;
use primitives::traits::{Zero, One, Bounded, RefInto, SimpleArithmetic, Executable, MakePayment,
As, AuxLookup, Hashing as HashingT, Member};
use address::Address as RawAddress;
pub mod address;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
mod genesis_config;
mod account_db;
#[cfg(feature = "std")]
pub use genesis_config::GenesisConfig;
pub use account_db::*;
/// Number of account IDs stored per enum set.
const ENUM_SET_SIZE: usize = 64;
@@ -90,6 +97,15 @@ pub trait ContractAddressFor<AccountId: Sized> {
fn contract_address_for(code: &[u8], origin: &AccountId) -> AccountId;
}
#[cfg(feature = "std")]
pub struct DummyContractAddressFor;
#[cfg(feature = "std")]
impl ContractAddressFor<u64> for DummyContractAddressFor {
fn contract_address_for(_code: &[u8], origin: &u64) -> u64 {
origin + 1
}
}
impl<Hashing, AccountId> ContractAddressFor<AccountId> for Hashing where
Hashing: HashingT,
AccountId: Sized + Slicable + From<Hashing::Output>,
@@ -157,13 +173,18 @@ decl_storage! {
// The fee required to create a contract. At least as big as ReclaimRebate.
pub ContractFee get(contract_fee): b"sta:contract_fee" => required T::Balance;
// Maximum reward, per validator, that is provided per acceptable session.
pub SessionReward get(session_reward): b"sta:session_reward" => required T::Balance;
// Slash, per validator that is taken per abnormal era end.
pub EarlyEraSlash get(early_era_slash): b"sta:early_era_slash" => required T::Balance;
// The current era index.
pub CurrentEra get(current_era): b"sta:era" => required T::BlockNumber;
// All the accounts with a desire to stake.
pub Intentions: b"sta:wil:" => default Vec<T::AccountId>;
// The next value of sessions per era.
pub NextSessionsPerEra get(next_sessions_per_era): b"sta:nse" => T::BlockNumber;
// The block number at which the era length last changed.
// The session index at which the era length last changed.
pub LastEraLengthChange get(last_era_length_change): b"sta:lec" => default T::BlockNumber;
// The next free enumeration set.
@@ -327,8 +348,7 @@ impl<T: Trait> Module<T> {
/// Force there to be a new era. This also forces a new session immediately after.
fn force_new_era() -> Result {
Self::new_era();
<session::Module<T>>::rotate_session();
<session::Module<T>>::rotate_session(false);
Ok(())
}
@@ -379,6 +399,17 @@ impl<T: Trait> Module<T> {
}
}
/// Adds up to `value` to the free balance of `who`.
///
/// If `who` doesn't exist, nothing is done and an Err returned.
pub fn reward(who: &T::AccountId, value: T::Balance) -> Result {
if Self::voting_balance(who).is_zero() {
return Err("beneficiary account must pre-exist");
}
Self::set_free_balance(who, Self::free_balance(who) + value);
Ok(())
}
/// Moves `value` from balance to reserved balance.
///
/// If the free balance is lower than `value`, then no funds will be moved and an `Err` will
@@ -453,10 +484,28 @@ impl<T: Trait> Module<T> {
}
}
/// Hook to be called after to transaction processing.
pub fn check_new_era() {
// check block number and call new_era if necessary.
if (<system::Module<T>>::block_number() - Self::last_era_length_change()) % Self::era_length() == Zero::zero() {
/// Session has just changed. We need to determine whether we pay a reward, slash and/or
/// move to a new era.
fn new_session(normal_rotation: bool, actual_elapsed: T::Moment) {
let session_index = <session::Module<T>>::current_index();
if normal_rotation {
// reward
let ideal_elapsed = <session::Module<T>>::ideal_session_duration();
let percent: usize = (T::Moment::sa(65536usize) * ideal_elapsed.clone() / actual_elapsed.max(ideal_elapsed)).as_();
let reward = Self::session_reward() * T::Balance::sa(percent) / T::Balance::sa(65536usize);
// apply good session reward
for v in <session::Module<T>>::validators().iter() {
let _ = Self::reward(v, reward); // will never fail as validator accounts must be created, but even if it did, it's just a missed reward.
}
} else {
// slash
let early_era_slash = Self::early_era_slash();
for v in <session::Module<T>>::validators().iter() {
Self::slash(v, early_era_slash);
}
}
if ((session_index - Self::last_era_length_change()) % Self::sessions_per_era()).is_zero() || !normal_rotation {
Self::new_era();
}
}
@@ -473,7 +522,7 @@ impl<T: Trait> Module<T> {
if let Some(next_spe) = Self::next_sessions_per_era() {
if next_spe != Self::sessions_per_era() {
<SessionsPerEra<T>>::put(&next_spe);
<LastEraLengthChange<T>>::put(&<system::Module<T>>::block_number());
<LastEraLengthChange<T>>::put(&<session::Module<T>>::current_index());
}
}
@@ -601,203 +650,7 @@ impl<T: Trait> Module<T> {
<system::AccountNonce<T>>::remove(who);
}
}
}
impl<T: Trait> Executable for Module<T> {
fn execute() {
Self::check_new_era();
}
}
impl<T: Trait> AuxLookup for Module<T> {
type Source = address::Address<T::AccountId, T::AccountIndex>;
type Target = T::AccountId;
fn lookup(a: Self::Source) -> result::Result<Self::Target, &'static str> {
match a {
address::Address::Id(i) => Ok(i),
address::Address::Index(i) => <Module<T>>::lookup_index(i).ok_or("invalid account index"),
}
}
}
// Each identity's stake may be in one of three bondage states, given by an integer:
// - n | n <= <CurrentEra<T>>::get(): inactive: free to be transferred.
// - ~0: active: currently representing a validator.
// - n | n > <CurrentEra<T>>::get(): deactivating: recently representing a validator and not yet
// ready for transfer.
struct ChangeEntry<T: Trait> {
balance: Option<T::Balance>,
code: Option<Vec<u8>>,
storage: BTreeMap<Vec<u8>, Option<Vec<u8>>>,
}
// Cannot derive(Default) since it erroneously bounds T by Default.
impl<T: Trait> Default for ChangeEntry<T> {
fn default() -> Self {
ChangeEntry {
balance: Default::default(),
code: Default::default(),
storage: Default::default(),
}
}
}
impl<T: Trait> ChangeEntry<T> {
pub fn contract_created(b: T::Balance, c: Vec<u8>) -> Self {
ChangeEntry { balance: Some(b), code: Some(c), storage: Default::default() }
}
pub fn balance_changed(b: T::Balance) -> Self {
ChangeEntry { balance: Some(b), code: None, storage: Default::default() }
}
}
type State<T> = BTreeMap<<T as system::Trait>::AccountId, ChangeEntry<T>>;
trait AccountDb<T: Trait> {
fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option<Vec<u8>>;
fn get_code(&self, account: &T::AccountId) -> Vec<u8>;
fn get_balance(&self, account: &T::AccountId) -> T::Balance;
fn merge(&mut self, state: State<T>);
}
struct DirectAccountDb;
impl<T: Trait> AccountDb<T> for DirectAccountDb {
fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option<Vec<u8>> {
<StorageOf<T>>::get(&(account.clone(), location.to_vec()))
}
fn get_code(&self, account: &T::AccountId) -> Vec<u8> {
<CodeOf<T>>::get(account)
}
fn get_balance(&self, account: &T::AccountId) -> T::Balance {
<FreeBalance<T>>::get(account)
}
fn merge(&mut self, s: State<T>) {
let ed = <Module<T>>::existential_deposit();
for (address, changed) in s.into_iter() {
if let Some(balance) = changed.balance {
// If the balance is too low, then the account is reaped.
// NOTE: There are two balances for every account: `reserved_balance` and
// `free_balance`. This contract subsystem only cares about the latter: whenever
// the term "balance" is used *here* it should be assumed to mean "free balance"
// in the rest of the module.
// Free balance can never be less than ED. If that happens, it gets reduced to zero
// and the account information relevant to this subsystem is deleted (i.e. the
// account is reaped).
// NOTE: This is orthogonal to the `Bondage` value that an account has, a high
// value of which makes even the `free_balance` unspendable.
// TODO: enforce this for the other balance-altering functions.
if balance < ed {
<Module<T>>::on_free_too_low(&address);
continue;
} else {
if !<FreeBalance<T>>::exists(&address) {
let outcome = <Module<T>>::new_account(&address, balance);
let credit = match outcome {
NewAccountOutcome::GoodHint => balance + <Module<T>>::reclaim_rebate(),
_ => balance,
};
<FreeBalance<T>>::insert(&address, credit);
} else {
<FreeBalance<T>>::insert(&address, balance);
}
}
}
if let Some(code) = changed.code {
<CodeOf<T>>::insert(&address, &code);
}
for (k, v) in changed.storage.into_iter() {
if let Some(value) = v {
<StorageOf<T>>::insert((address.clone(), k), &value);
} else {
<StorageOf<T>>::remove((address.clone(), k));
}
}
}
}
}
struct OverlayAccountDb<'a, T: Trait + 'a> {
local: RefCell<State<T>>,
underlying: &'a AccountDb<T>,
}
impl<'a, T: Trait> OverlayAccountDb<'a, T> {
fn new(underlying: &'a AccountDb<T>) -> OverlayAccountDb<'a, T> {
OverlayAccountDb {
local: RefCell::new(State::new()),
underlying,
}
}
fn into_state(self) -> State<T> {
self.local.into_inner()
}
fn set_storage(&mut self, account: &T::AccountId, location: Vec<u8>, value: Option<Vec<u8>>) {
self.local
.borrow_mut()
.entry(account.clone())
.or_insert(Default::default())
.storage
.insert(location, value);
}
fn set_balance(&mut self, account: &T::AccountId, balance: T::Balance) {
self.local
.borrow_mut()
.entry(account.clone())
.or_insert(Default::default())
.balance = Some(balance);
}
}
impl<'a, T: Trait> AccountDb<T> for OverlayAccountDb<'a, T> {
fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option<Vec<u8>> {
self.local
.borrow()
.get(account)
.and_then(|a| a.storage.get(location))
.cloned()
.unwrap_or_else(|| self.underlying.get_storage(account, location))
}
fn get_code(&self, account: &T::AccountId) -> Vec<u8> {
self.local
.borrow()
.get(account)
.and_then(|a| a.code.clone())
.unwrap_or_else(|| self.underlying.get_code(account))
}
fn get_balance(&self, account: &T::AccountId) -> T::Balance {
self.local
.borrow()
.get(account)
.and_then(|a| a.balance)
.unwrap_or_else(|| self.underlying.get_balance(account))
}
fn merge(&mut self, s: State<T>) {
let mut local = self.local.borrow_mut();
for (address, changed) in s.into_iter() {
match local.entry(address) {
Entry::Occupied(e) => {
let mut value = e.into_mut();
if changed.balance.is_some() {
value.balance = changed.balance;
}
if changed.code.is_some() {
value.code = changed.code;
}
value.storage.extend(changed.storage.into_iter());
}
Entry::Vacant(e) => {
e.insert(changed);
}
}
}
}
}
impl<T: Trait> Module<T> {
fn effect_create<DB: AccountDb<T>>(
transactor: &T::AccountId,
code: &[u8],
@@ -894,32 +747,24 @@ impl<T: Trait> Module<T> {
}
}
struct StakingExt<'a, 'b: 'a, T: Trait + 'b> {
account_db: &'a mut OverlayAccountDb<'b, T>,
account: T::AccountId,
impl<T: Trait> Executable for Module<T> {
fn execute() {
}
}
impl<'a, 'b: 'a, T: Trait> contract::Ext for StakingExt<'a, 'b, T> {
type AccountId = T::AccountId;
type Balance = T::Balance;
fn get_storage(&self, key: &[u8]) -> Option<Vec<u8>> {
self.account_db.get_storage(&self.account, key)
impl<T: Trait> OnSessionChange<T::Moment> for Module<T> {
fn on_session_change(normal_rotation: bool, elapsed: T::Moment) {
Self::new_session(normal_rotation, elapsed);
}
fn set_storage(&mut self, key: &[u8], value: Option<Vec<u8>>) {
self.account_db.set_storage(&self.account, key.to_vec(), value);
}
fn create(&mut self, code: &[u8], value: Self::Balance) {
if let Ok(Some(commit_state)) =
Module::<T>::effect_create(&self.account, code, value, self.account_db)
{
self.account_db.merge(commit_state);
}
}
fn transfer(&mut self, to: &Self::AccountId, value: Self::Balance) {
if let Ok(Some(commit_state)) =
Module::<T>::effect_transfer(&self.account, to, value, self.account_db)
{
self.account_db.merge(commit_state);
}
impl<T: Trait> AuxLookup for Module<T> {
type Source = address::Address<T::AccountId, T::AccountIndex>;
type Target = T::AccountId;
fn lookup(a: Self::Source) -> result::Result<Self::Target, &'static str> {
match a {
address::Address::Id(i) => Ok(i),
address::Address::Index(i) => <Module<T>>::lookup_index(i).ok_or("invalid account index"),
}
}
}
@@ -935,134 +780,3 @@ impl<T: Trait> MakePayment<T::AccountId> for Module<T> {
Ok(())
}
}
#[cfg(any(feature = "std", test))]
pub struct DummyContractAddressFor;
#[cfg(any(feature = "std", test))]
impl ContractAddressFor<u64> for DummyContractAddressFor {
fn contract_address_for(_code: &[u8], origin: &u64) -> u64 {
origin + 1
}
}
#[cfg(any(feature = "std", test))]
pub struct GenesisConfig<T: Trait> {
pub sessions_per_era: T::BlockNumber,
pub current_era: T::BlockNumber,
pub balances: Vec<(T::AccountId, T::Balance)>,
pub intentions: Vec<T::AccountId>,
pub validator_count: u64,
pub bonding_duration: T::BlockNumber,
pub transaction_base_fee: T::Balance,
pub transaction_byte_fee: T::Balance,
pub transfer_fee: T::Balance,
pub creation_fee: T::Balance,
pub contract_fee: T::Balance,
pub reclaim_rebate: T::Balance,
pub existential_deposit: T::Balance,
}
#[cfg(any(feature = "std", test))]
impl<T: Trait> GenesisConfig<T> where T::AccountId: From<u64> {
pub fn simple() -> Self {
GenesisConfig {
sessions_per_era: T::BlockNumber::sa(2),
current_era: T::BlockNumber::sa(0),
balances: vec![(T::AccountId::from(1), T::Balance::sa(111))],
intentions: vec![T::AccountId::from(1), T::AccountId::from(2), T::AccountId::from(3)],
validator_count: 3,
bonding_duration: T::BlockNumber::sa(0),
transaction_base_fee: T::Balance::sa(0),
transaction_byte_fee: T::Balance::sa(0),
transfer_fee: T::Balance::sa(0),
creation_fee: T::Balance::sa(0),
contract_fee: T::Balance::sa(0),
existential_deposit: T::Balance::sa(0),
reclaim_rebate: T::Balance::sa(0),
}
}
pub fn extended() -> Self {
GenesisConfig {
sessions_per_era: T::BlockNumber::sa(3),
current_era: T::BlockNumber::sa(1),
balances: vec![
(T::AccountId::from(1), T::Balance::sa(10)),
(T::AccountId::from(2), T::Balance::sa(20)),
(T::AccountId::from(3), T::Balance::sa(30)),
(T::AccountId::from(4), T::Balance::sa(40)),
(T::AccountId::from(5), T::Balance::sa(50)),
(T::AccountId::from(6), T::Balance::sa(60)),
(T::AccountId::from(7), T::Balance::sa(1))
],
intentions: vec![T::AccountId::from(1), T::AccountId::from(2), T::AccountId::from(3)],
validator_count: 3,
bonding_duration: T::BlockNumber::sa(0),
transaction_base_fee: T::Balance::sa(1),
transaction_byte_fee: T::Balance::sa(0),
transfer_fee: T::Balance::sa(0),
creation_fee: T::Balance::sa(0),
contract_fee: T::Balance::sa(0),
existential_deposit: T::Balance::sa(0),
reclaim_rebate: T::Balance::sa(0),
}
}
}
#[cfg(any(feature = "std", test))]
impl<T: Trait> Default for GenesisConfig<T> {
fn default() -> Self {
GenesisConfig {
sessions_per_era: T::BlockNumber::sa(1000),
current_era: T::BlockNumber::sa(0),
balances: vec![],
intentions: vec![],
validator_count: 0,
bonding_duration: T::BlockNumber::sa(1000),
transaction_base_fee: T::Balance::sa(0),
transaction_byte_fee: T::Balance::sa(0),
transfer_fee: T::Balance::sa(0),
creation_fee: T::Balance::sa(0),
contract_fee: T::Balance::sa(0),
existential_deposit: T::Balance::sa(0),
reclaim_rebate: T::Balance::sa(0),
}
}
}
#[cfg(any(feature = "std", test))]
impl<T: Trait> primitives::BuildStorage for GenesisConfig<T> {
fn build_storage(self) -> runtime_io::TestExternalities {
use runtime_io::twox_128;
use codec::Slicable;
let total_stake: T::Balance = self.balances.iter().fold(Zero::zero(), |acc, &(_, n)| acc + n);
let mut r: runtime_io::TestExternalities = map![
twox_128(<NextEnumSet<T>>::key()).to_vec() => T::AccountIndex::sa(self.balances.len() / ENUM_SET_SIZE).encode(),
twox_128(<Intentions<T>>::key()).to_vec() => self.intentions.encode(),
twox_128(<SessionsPerEra<T>>::key()).to_vec() => self.sessions_per_era.encode(),
twox_128(<ValidatorCount<T>>::key()).to_vec() => self.validator_count.encode(),
twox_128(<BondingDuration<T>>::key()).to_vec() => self.bonding_duration.encode(),
twox_128(<TransactionBaseFee<T>>::key()).to_vec() => self.transaction_base_fee.encode(),
twox_128(<TransactionByteFee<T>>::key()).to_vec() => self.transaction_byte_fee.encode(),
twox_128(<TransferFee<T>>::key()).to_vec() => self.transfer_fee.encode(),
twox_128(<CreationFee<T>>::key()).to_vec() => self.creation_fee.encode(),
twox_128(<ContractFee<T>>::key()).to_vec() => self.contract_fee.encode(),
twox_128(<ExistentialDeposit<T>>::key()).to_vec() => self.existential_deposit.encode(),
twox_128(<ReclaimRebate<T>>::key()).to_vec() => self.reclaim_rebate.encode(),
twox_128(<CurrentEra<T>>::key()).to_vec() => self.current_era.encode(),
twox_128(<TotalStake<T>>::key()).to_vec() => total_stake.encode()
];
let ids: Vec<_> = self.balances.iter().map(|x| x.0.clone()).collect();
for i in 0..(ids.len() + ENUM_SET_SIZE - 1) / ENUM_SET_SIZE {
r.insert(twox_128(&<EnumSet<T>>::key_for(T::AccountIndex::sa(i))).to_vec(),
ids[i * ENUM_SET_SIZE..ids.len().min((i + 1) * ENUM_SET_SIZE)].to_owned().encode());
}
for (who, value) in self.balances.into_iter() {
r.insert(twox_128(&<FreeBalance<T>>::key_for(who)).to_vec(), value.encode());
}
r
}
}
@@ -23,7 +23,8 @@ use primitives::traits::{HasPublicAux, Identity};
use primitives::testing::{Digest, Header};
use substrate_primitives::H256;
use runtime_io;
use {DummyContractAddressFor, GenesisConfig, Module, Trait, consensus, session, system};
use {GenesisConfig, Module, Trait, consensus, session, system, timestamp};
use super::DummyContractAddressFor;
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
@@ -46,6 +47,11 @@ impl system::Trait for Test {
}
impl session::Trait for Test {
type ConvertAccountIdToSessionKey = Identity;
type OnSessionChange = Staking;
}
impl timestamp::Trait for Test {
const TIMESTAMP_SET_POSITION: u32 = 0;
type Moment = u64;
}
impl Trait for Test {
type Balance = u64;
@@ -53,7 +59,7 @@ impl Trait for Test {
type AccountIndex = u64;
}
pub fn new_test_ext(ext_deposit: u64, session_length: u64, sessions_per_era: u64, current_era: u64, monied: bool) -> runtime_io::TestExternalities {
pub fn new_test_ext(ext_deposit: u64, session_length: u64, sessions_per_era: u64, current_era: u64, monied: bool, reward: u64) -> runtime_io::TestExternalities {
let mut t = system::GenesisConfig::<Test>::default().build_storage();
let balance_factor = if ext_deposit > 0 {
256
@@ -67,11 +73,20 @@ pub fn new_test_ext(ext_deposit: u64, session_length: u64, sessions_per_era: u64
t.extend(session::GenesisConfig::<Test>{
session_length,
validators: vec![10, 20],
broken_percent_late: 30,
}.build_storage());
t.extend(GenesisConfig::<Test>{
sessions_per_era,
current_era,
balances: if monied { vec![(1, 10 * balance_factor), (2, 20 * balance_factor), (3, 30 * balance_factor), (4, 40 * balance_factor)] } else { vec![] },
balances: if monied {
if reward > 0 {
vec![(1, 10 * balance_factor), (2, 20 * balance_factor), (3, 30 * balance_factor), (4, 40 * balance_factor), (10, balance_factor), (20, balance_factor)]
} else {
vec![(1, 10 * balance_factor), (2, 20 * balance_factor), (3, 30 * balance_factor), (4, 40 * balance_factor)]
}
} else {
vec![(10, balance_factor), (20, balance_factor)]
},
intentions: vec![],
validator_count: 2,
bonding_duration: 3,
@@ -82,10 +97,16 @@ pub fn new_test_ext(ext_deposit: u64, session_length: u64, sessions_per_era: u64
creation_fee: 0,
contract_fee: 0,
reclaim_rebate: 0,
session_reward: reward,
early_era_slash: if monied { 10 } else { 0 },
}.build_storage());
t.extend(timestamp::GenesisConfig::<Test>{
period: 5
}.build_storage());
t
}
pub type System = system::Module<Test>;
pub type Session = session::Module<Test>;
pub type Timestamp = timestamp::Module<Test>;
pub type Staking = Module<Test>;
+120 -39
View File
@@ -16,13 +16,81 @@
//! Tests for the module.
#![cfg(test)]
use super::*;
use runtime_io::with_externalities;
use mock::*;
use mock::{Session, Staking, System, Timestamp, Test, new_test_ext};
#[test]
fn reward_should_work() {
with_externalities(&mut new_test_ext(0, 3, 3, 0, true, 10), || {
assert_eq!(Staking::voting_balance(&10), 1);
assert_ok!(Staking::reward(&10, 10));
assert_eq!(Staking::voting_balance(&10), 11);
});
}
#[test]
fn rewards_should_work() {
with_externalities(&mut new_test_ext(0, 3, 3, 0, true, 10), || {
assert_eq!(Staking::era_length(), 9);
assert_eq!(Staking::sessions_per_era(), 3);
assert_eq!(Staking::last_era_length_change(), 0);
assert_eq!(Staking::current_era(), 0);
assert_eq!(Session::current_index(), 0);
assert_eq!(Staking::voting_balance(&10), 1);
System::set_block_number(3);
Timestamp::set_timestamp(15); // on time.
Session::check_rotate_session();
assert_eq!(Staking::current_era(), 0);
assert_eq!(Session::current_index(), 1);
assert_eq!(Staking::voting_balance(&10), 11);
System::set_block_number(6);
Timestamp::set_timestamp(31); // a little late
Session::check_rotate_session();
assert_eq!(Staking::current_era(), 0);
assert_eq!(Session::current_index(), 2);
assert_eq!(Staking::voting_balance(&10), 20); // less reward
System::set_block_number(9);
Timestamp::set_timestamp(50); // very late
Session::check_rotate_session();
assert_eq!(Staking::current_era(), 1);
assert_eq!(Session::current_index(), 3);
assert_eq!(Staking::voting_balance(&10), 27); // much less reward
});
}
#[test]
fn slashing_should_work() {
with_externalities(&mut new_test_ext(0, 3, 3, 0, true, 10), || {
assert_eq!(Staking::era_length(), 9);
assert_eq!(Staking::sessions_per_era(), 3);
assert_eq!(Staking::last_era_length_change(), 0);
assert_eq!(Staking::current_era(), 0);
assert_eq!(Session::current_index(), 0);
assert_eq!(Staking::voting_balance(&10), 1);
System::set_block_number(3);
Timestamp::set_timestamp(15); // on time.
Session::check_rotate_session();
assert_eq!(Staking::current_era(), 0);
assert_eq!(Session::current_index(), 1);
assert_eq!(Staking::voting_balance(&10), 11);
System::set_block_number(4);
Timestamp::set_timestamp(100); // way too late - early exit.
Session::check_rotate_session();
assert_eq!(Staking::current_era(), 1);
assert_eq!(Session::current_index(), 2);
assert_eq!(Staking::voting_balance(&10), 1);
});
}
#[test]
fn indexing_lookup_should_work() {
with_externalities(&mut new_test_ext(10, 1, 2, 0, true), || {
with_externalities(&mut new_test_ext(10, 1, 2, 0, true, 0), || {
assert_eq!(Staking::lookup_index(0), Some(1));
assert_eq!(Staking::lookup_index(1), Some(2));
assert_eq!(Staking::lookup_index(2), Some(3));
@@ -33,7 +101,7 @@ fn indexing_lookup_should_work() {
#[test]
fn default_indexing_on_new_accounts_should_work() {
with_externalities(&mut new_test_ext(10, 1, 2, 0, true), || {
with_externalities(&mut new_test_ext(10, 1, 2, 0, true, 0), || {
assert_eq!(Staking::lookup_index(4), None);
assert_ok!(Staking::transfer(&1, 5.into(), 10));
assert_eq!(Staking::lookup_index(4), Some(5));
@@ -42,7 +110,7 @@ fn default_indexing_on_new_accounts_should_work() {
#[test]
fn dust_account_removal_should_work() {
with_externalities(&mut new_test_ext(256 * 10, 1, 2, 0, true), || {
with_externalities(&mut new_test_ext(256 * 10, 1, 2, 0, true, 0), || {
System::inc_account_nonce(&2);
assert_eq!(System::account_nonce(&2), 1);
assert_eq!(Staking::voting_balance(&2), 256 * 20);
@@ -56,7 +124,7 @@ fn dust_account_removal_should_work() {
#[test]
fn reclaim_indexing_on_new_accounts_should_work() {
with_externalities(&mut new_test_ext(256 * 1, 1, 2, 0, true), || {
with_externalities(&mut new_test_ext(256 * 1, 1, 2, 0, true, 0), || {
assert_eq!(Staking::lookup_index(1), Some(2));
assert_eq!(Staking::lookup_index(4), None);
assert_eq!(Staking::voting_balance(&2), 256 * 20);
@@ -72,7 +140,7 @@ fn reclaim_indexing_on_new_accounts_should_work() {
#[test]
fn reserved_balance_should_prevent_reclaim_count() {
with_externalities(&mut new_test_ext(256 * 1, 1, 2, 0, true), || {
with_externalities(&mut new_test_ext(256 * 1, 1, 2, 0, true, 0), || {
System::inc_account_nonce(&2);
assert_eq!(Staking::lookup_index(1), Some(2));
assert_eq!(Staking::lookup_index(4), None);
@@ -100,7 +168,7 @@ fn reserved_balance_should_prevent_reclaim_count() {
#[test]
fn staking_should_work() {
with_externalities(&mut new_test_ext(0, 1, 2, 0, true), || {
with_externalities(&mut new_test_ext(0, 1, 2, 0, true, 0), || {
assert_eq!(Staking::era_length(), 2);
assert_eq!(Staking::validator_count(), 2);
assert_eq!(Staking::bonding_duration(), 3);
@@ -111,66 +179,73 @@ fn staking_should_work() {
assert_ok!(Staking::stake(&1));
assert_ok!(Staking::stake(&2));
assert_ok!(Staking::stake(&4));
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Staking::current_era(), 0);
assert_eq!(Session::validators(), vec![10, 20]);
// Block 2: New validator set now.
System::set_block_number(2);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Staking::current_era(), 1);
assert_eq!(Session::validators(), vec![4, 2]);
// Block 3: Unstake highest, introduce another staker. No change yet.
System::set_block_number(3);
assert_ok!(Staking::stake(&3));
assert_ok!(Staking::unstake(&4));
Staking::check_new_era();
assert_eq!(Staking::current_era(), 1);
Session::check_rotate_session();
// Block 4: New era - validators change.
System::set_block_number(4);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Staking::current_era(), 2);
assert_eq!(Session::validators(), vec![3, 2]);
// Block 5: Transfer stake from highest to lowest. No change yet.
System::set_block_number(5);
assert_ok!(Staking::transfer(&4, 1.into(), 40));
Staking::check_new_era();
Session::check_rotate_session();
// Block 6: Lowest now validator.
System::set_block_number(6);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Session::validators(), vec![1, 3]);
// Block 7: Unstake three. No change yet.
System::set_block_number(7);
assert_ok!(Staking::unstake(&3));
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Session::validators(), vec![1, 3]);
// Block 8: Back to one and two.
System::set_block_number(8);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Session::validators(), vec![1, 2]);
});
}
#[test]
fn staking_eras_work() {
with_externalities(&mut new_test_ext(0, 1, 2, 0, true), || {
with_externalities(&mut new_test_ext(0, 1, 2, 0, true, 0), || {
assert_eq!(Staking::era_length(), 2);
assert_eq!(Staking::sessions_per_era(), 2);
assert_eq!(Staking::last_era_length_change(), 0);
assert_eq!(Staking::current_era(), 0);
assert_eq!(Session::current_index(), 0);
// Block 1: No change.
System::set_block_number(1);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Session::current_index(), 1);
assert_eq!(Staking::sessions_per_era(), 2);
assert_eq!(Staking::last_era_length_change(), 0);
assert_eq!(Staking::current_era(), 0);
// Block 2: Simple era change.
System::set_block_number(2);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Session::current_index(), 2);
assert_eq!(Staking::sessions_per_era(), 2);
assert_eq!(Staking::last_era_length_change(), 0);
assert_eq!(Staking::current_era(), 1);
@@ -178,35 +253,41 @@ fn staking_eras_work() {
// Block 3: Schedule an era length change; no visible changes.
System::set_block_number(3);
assert_ok!(Staking::set_sessions_per_era(3));
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Session::current_index(), 3);
assert_eq!(Staking::sessions_per_era(), 2);
assert_eq!(Staking::last_era_length_change(), 0);
assert_eq!(Staking::current_era(), 1);
// Block 4: Era change kicks in.
System::set_block_number(4);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Session::current_index(), 4);
assert_eq!(Staking::sessions_per_era(), 3);
assert_eq!(Staking::last_era_length_change(), 4);
assert_eq!(Staking::current_era(), 2);
// Block 5: No change.
System::set_block_number(5);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Session::current_index(), 5);
assert_eq!(Staking::sessions_per_era(), 3);
assert_eq!(Staking::last_era_length_change(), 4);
assert_eq!(Staking::current_era(), 2);
// Block 6: No change.
System::set_block_number(6);
Staking::check_new_era();
assert!(!Session::broken_validation());
Session::check_rotate_session();
assert_eq!(Session::current_index(), 6);
assert_eq!(Staking::sessions_per_era(), 3);
assert_eq!(Staking::last_era_length_change(), 4);
assert_eq!(Staking::current_era(), 2);
// Block 7: Era increment.
System::set_block_number(7);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Session::current_index(), 7);
assert_eq!(Staking::sessions_per_era(), 3);
assert_eq!(Staking::last_era_length_change(), 4);
assert_eq!(Staking::current_era(), 3);
@@ -215,7 +296,7 @@ fn staking_eras_work() {
#[test]
fn staking_balance_works() {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || {
<FreeBalance<Test>>::insert(1, 42);
assert_eq!(Staking::free_balance(&1), 42);
assert_eq!(Staking::reserved_balance(&1), 0);
@@ -228,7 +309,7 @@ fn staking_balance_works() {
#[test]
fn staking_balance_transfer_works() {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || {
<FreeBalance<Test>>::insert(1, 111);
assert_ok!(Staking::transfer(&1, 2.into(), 69));
assert_eq!(Staking::voting_balance(&1), 42);
@@ -238,7 +319,7 @@ fn staking_balance_transfer_works() {
#[test]
fn staking_balance_transfer_when_bonded_should_not_work() {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || {
<FreeBalance<Test>>::insert(1, 111);
assert_ok!(Staking::stake(&1));
assert_noop!(Staking::transfer(&1, 2.into(), 69), "bondage too high to send value");
@@ -247,7 +328,7 @@ fn staking_balance_transfer_when_bonded_should_not_work() {
#[test]
fn reserving_balance_should_work() {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || {
<FreeBalance<Test>>::insert(1, 111);
assert_eq!(Staking::voting_balance(&1), 111);
@@ -264,7 +345,7 @@ fn reserving_balance_should_work() {
#[test]
fn staking_balance_transfer_when_reserved_should_not_work() {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || {
<FreeBalance<Test>>::insert(1, 111);
assert_ok!(Staking::reserve(&1, 69));
assert_noop!(Staking::transfer(&1, 2.into(), 69), "balance too low to send value");
@@ -273,7 +354,7 @@ fn staking_balance_transfer_when_reserved_should_not_work() {
#[test]
fn deducting_balance_should_work() {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || {
<FreeBalance<Test>>::insert(1, 111);
assert_ok!(Staking::reserve(&1, 69));
assert_eq!(Staking::free_balance(&1), 42);
@@ -282,7 +363,7 @@ fn deducting_balance_should_work() {
#[test]
fn deducting_balance_when_bonded_should_not_work() {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || {
<FreeBalance<Test>>::insert(1, 111);
<Bondage<Test>>::insert(1, 2);
System::set_block_number(1);
@@ -293,7 +374,7 @@ fn deducting_balance_when_bonded_should_not_work() {
#[test]
fn refunding_balance_should_work() {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || {
<FreeBalance<Test>>::insert(1, 42);
<ReservedBalance<Test>>::insert(1, 69);
Staking::unreserve(&1, 69);
@@ -304,7 +385,7 @@ fn refunding_balance_should_work() {
#[test]
fn slashing_balance_should_work() {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || {
<FreeBalance<Test>>::insert(1, 111);
assert_ok!(Staking::reserve(&1, 69));
assert!(Staking::slash(&1, 69).is_none());
@@ -315,7 +396,7 @@ fn slashing_balance_should_work() {
#[test]
fn slashing_incomplete_balance_should_work() {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || {
<FreeBalance<Test>>::insert(1, 42);
assert_ok!(Staking::reserve(&1, 21));
assert!(Staking::slash(&1, 69).is_some());
@@ -326,7 +407,7 @@ fn slashing_incomplete_balance_should_work() {
#[test]
fn unreserving_balance_should_work() {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || {
<FreeBalance<Test>>::insert(1, 111);
assert_ok!(Staking::reserve(&1, 111));
Staking::unreserve(&1, 42);
@@ -337,7 +418,7 @@ fn unreserving_balance_should_work() {
#[test]
fn slashing_reserved_balance_should_work() {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || {
<FreeBalance<Test>>::insert(1, 111);
assert_ok!(Staking::reserve(&1, 111));
assert!(Staking::slash_reserved(&1, 42).is_none());
@@ -348,7 +429,7 @@ fn slashing_reserved_balance_should_work() {
#[test]
fn slashing_incomplete_reserved_balance_should_work() {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || {
<FreeBalance<Test>>::insert(1, 111);
assert_ok!(Staking::reserve(&1, 42));
assert!(Staking::slash_reserved(&1, 69).is_some());
@@ -359,7 +440,7 @@ fn slashing_incomplete_reserved_balance_should_work() {
#[test]
fn transferring_reserved_balance_should_work() {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || {
<FreeBalance<Test>>::insert(1, 110);
<FreeBalance<Test>>::insert(2, 1);
assert_ok!(Staking::reserve(&1, 110));
@@ -373,7 +454,7 @@ fn transferring_reserved_balance_should_work() {
#[test]
fn transferring_reserved_balance_to_nonexistent_should_fail() {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || {
<FreeBalance<Test>>::insert(1, 111);
assert_ok!(Staking::reserve(&1, 111));
assert_noop!(Staking::transfer_reserved(&1, &2, 42), "beneficiary account must pre-exist");
@@ -382,7 +463,7 @@ fn transferring_reserved_balance_to_nonexistent_should_fail() {
#[test]
fn transferring_incomplete_reserved_balance_should_work() {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || {
with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || {
<FreeBalance<Test>>::insert(1, 110);
<FreeBalance<Test>>::insert(2, 1);
assert_ok!(Staking::reserve(&1, 41));
@@ -14,6 +14,7 @@ substrate-runtime-primitives = { path = "../primitives", default_features = fals
substrate-codec = { path = "../../codec", default_features = false }
substrate-primitives = { path = "../../primitives", default_features = false }
substrate-runtime-system = { path = "../system", default_features = false }
substrate-runtime-consensus = { path = "../consensus", default_features = false }
[dev-dependencies]
substrate-runtime-io = { path = "../../runtime-io", default_features = true }
@@ -35,17 +35,20 @@ extern crate serde_derive;
extern crate substrate_primitives;
extern crate substrate_runtime_primitives as runtime_primitives;
extern crate substrate_runtime_system as system;
extern crate substrate_runtime_consensus as consensus;
extern crate substrate_codec as codec;
use runtime_support::{StorageValue, Parameter};
use runtime_support::dispatch::Result;
use runtime_primitives::traits::{HasPublicAux, Executable, MaybeEmpty};
use runtime_primitives::traits::{Executable, MaybeEmpty, SimpleArithmetic, As, Zero};
pub trait Trait: HasPublicAux + system::Trait {
pub trait Trait: consensus::Trait where
<Self as consensus::Trait>::PublicAux: MaybeEmpty
{
// the position of the required timestamp-set extrinsic.
const SET_POSITION: u32;
const TIMESTAMP_SET_POSITION: u32;
type Value: Parameter + Default;
type Moment: Parameter + Default + SimpleArithmetic + As<Self::BlockNumber>;
}
decl_module! {
@@ -53,36 +56,48 @@ decl_module! {
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub enum Call where aux: T::PublicAux {
fn set(aux, now: T::Value) -> Result = 0;
fn set(aux, now: T::Moment) -> Result = 0;
}
}
decl_storage! {
trait Store for Module<T: Trait>;
pub Now get(now): b"tim:val" => required T::Value;
pub Now get(now): b"tim:val" => required T::Moment;
// The minimum (and advised) period between blocks.
pub BlockPeriod get(block_period): b"tim:block_period" => required T::Moment;
// Did the timestamp get updated in this block?
DidUpdate: b"tim:did" => default bool;
}
impl<T: Trait> Module<T> {
pub fn get() -> T::Value {
<Self as Store>::Now::get()
pub fn get() -> T::Moment {
Self::now()
}
/// Set the current time.
fn set(aux: &T::PublicAux, now: T::Value) -> Result {
fn set(aux: &T::PublicAux, now: T::Moment) -> Result {
assert!(aux.is_empty());
assert!(!<Self as Store>::DidUpdate::exists(), "Timestamp must be updated only once in the block");
assert!(
<system::Module<T>>::extrinsic_index() == T::SET_POSITION,
<system::Module<T>>::extrinsic_index() == T::TIMESTAMP_SET_POSITION,
"Timestamp extrinsic must be at position {} in the block",
T::SET_POSITION
T::TIMESTAMP_SET_POSITION
);
assert!(
Self::now().is_zero() || now >= Self::now() + Self::block_period(),
"Timestamp but increment by at least <BlockPeriod> between sequential blocks"
);
<Self as Store>::Now::put(now);
<Self as Store>::DidUpdate::put(true);
Ok(())
}
/// Set the timestamp to something in particular. Only used for tests.
#[cfg(any(feature = "std", test))]
pub fn set_timestamp(now: T::Moment) {
<Self as Store>::Now::put(now);
}
}
impl<T: Trait> Executable for Module<T> {
@@ -92,9 +107,17 @@ impl<T: Trait> Executable for Module<T> {
}
#[cfg(any(feature = "std", test))]
#[derive(Default)]
pub struct GenesisConfig<T: Trait> {
pub now: T::Value,
pub period: T::Moment,
}
#[cfg(any(feature = "std", test))]
impl<T: Trait> Default for GenesisConfig<T> {
fn default() -> Self {
GenesisConfig {
period: T::Moment::sa(5),
}
}
}
#[cfg(any(feature = "std", test))]
@@ -104,7 +127,8 @@ impl<T: Trait> runtime_primitives::BuildStorage for GenesisConfig<T>
use runtime_io::twox_128;
use codec::Slicable;
map![
twox_128(<Now<T>>::key()).to_vec() => self.now.encode()
twox_128(<BlockPeriod<T>>::key()).to_vec() => self.period.encode(),
twox_128(<Now<T>>::key()).to_vec() => T::Moment::sa(0).encode()
]
}
}
@@ -114,7 +138,6 @@ mod tests {
use super::*;
use runtime_io::with_externalities;
use runtime_support::storage::StorageValue;
use substrate_primitives::H256;
use runtime_primitives::BuildStorage;
use runtime_primitives::traits::{HasPublicAux, BlakeTwo256};
@@ -134,21 +157,50 @@ mod tests {
type AccountId = u64;
type Header = Header;
}
impl consensus::Trait for Test {
type PublicAux = u64;
type SessionKey = u64;
}
impl Trait for Test {
const SET_POSITION: u32 = 0;
type Value = u64;
const TIMESTAMP_SET_POSITION: u32 = 0;
type Moment = u64;
}
type Timestamp = Module<Test>;
#[test]
fn timestamp_works() {
let mut t = system::GenesisConfig::<Test>::default().build_storage();
t.extend(GenesisConfig::<Test> { now: 42 }.build_storage());
t.extend(GenesisConfig::<Test> { period: 0 }.build_storage());
with_externalities(&mut t, || {
assert_eq!(<Timestamp as Store>::Now::get(), 42);
Timestamp::set_timestamp(42);
assert_ok!(Timestamp::aux_dispatch(Call::set(69), &0));
assert_eq!(Timestamp::now(), 69);
});
}
#[test]
#[should_panic(expected = "Timestamp must be updated only once in the block")]
fn double_timestamp_should_fail() {
let mut t = system::GenesisConfig::<Test>::default().build_storage();
t.extend(GenesisConfig::<Test> { period: 5 }.build_storage());
with_externalities(&mut t, || {
Timestamp::set_timestamp(42);
assert_ok!(Timestamp::aux_dispatch(Call::set(69), &0));
let _ = Timestamp::aux_dispatch(Call::set(70), &0);
});
}
#[test]
#[should_panic(expected = "Timestamp but increment by at least <BlockPeriod> between sequential blocks")]
fn block_period_is_enforced() {
let mut t = system::GenesisConfig::<Test>::default().build_storage();
t.extend(GenesisConfig::<Test> { period: 5 }.build_storage());
with_externalities(&mut t, || {
Timestamp::set_timestamp(42);
let _ = Timestamp::aux_dispatch(Call::set(46), &0);
});
}
}