mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 14:11:09 +00:00
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:
Generated
+6
@@ -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",
|
||||
|
||||
@@ -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)?);
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Generated
+3
@@ -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",
|
||||
|
||||
BIN
Binary file not shown.
Binary file not shown.
@@ -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()
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Reference in New Issue
Block a user