diff --git a/Cargo.lock b/Cargo.lock index 995c0525af84c..880e992909336 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -228,7 +228,10 @@ dependencies = [ "substrate-keyring 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-io 0.1.0", + "substrate-runtime-primitives 0.1.0", + "substrate-runtime-staking 0.1.0", "substrate-runtime-support 0.1.0", + "substrate-runtime-system 0.1.0", "substrate-state-machine 0.1.0", "triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -242,6 +245,7 @@ dependencies = [ "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-primitives 0.1.0", + "substrate-runtime-primitives 0.1.0", "substrate-runtime-std 0.1.0", "substrate-serializer 0.1.0", ] @@ -252,17 +256,26 @@ version = "0.1.0" dependencies = [ "demo-primitives 0.1.0", "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "integer-sqrt 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "safe-mix 0.1.0", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-keyring 0.1.0", "substrate-primitives 0.1.0", + "substrate-runtime-consensus 0.1.0", + "substrate-runtime-council 0.1.0", + "substrate-runtime-democracy 0.1.0", + "substrate-runtime-executive 0.1.0", "substrate-runtime-io 0.1.0", + "substrate-runtime-primitives 0.1.0", + "substrate-runtime-session 0.1.0", + "substrate-runtime-staking 0.1.0", "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]] @@ -971,6 +984,11 @@ name = "num-traits" version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "num-traits" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "num_cpus" version = "1.8.0" @@ -1162,6 +1180,7 @@ dependencies = [ "substrate-keyring 0.1.0", "substrate-network 0.1.0", "substrate-primitives 0.1.0", + "substrate-runtime-support 0.1.0", "tokio-core 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1491,6 +1510,13 @@ dependencies = [ "semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "safe-mix" +version = "0.1.0" +dependencies = [ + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "safemem" version = "0.2.0" @@ -1633,6 +1659,7 @@ dependencies = [ "substrate-executor 0.1.0", "substrate-keyring 0.1.0", "substrate-primitives 0.1.0", + "substrate-runtime-support 0.1.0", "tokio-core 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1780,6 +1807,78 @@ dependencies = [ "substrate-rpc 0.1.0", ] +[[package]] +name = "substrate-runtime-consensus" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-primitives 0.1.0", + "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", +] + +[[package]] +name = "substrate-runtime-council" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "integer-sqrt 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "safe-mix 0.1.0", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-keyring 0.1.0", + "substrate-primitives 0.1.0", + "substrate-runtime-consensus 0.1.0", + "substrate-runtime-democracy 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-primitives 0.1.0", + "substrate-runtime-session 0.1.0", + "substrate-runtime-staking 0.1.0", + "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", + "substrate-runtime-system 0.1.0", +] + +[[package]] +name = "substrate-runtime-democracy" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "safe-mix 0.1.0", + "serde 1.0.27 (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-session 0.1.0", + "substrate-runtime-staking 0.1.0", + "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", + "substrate-runtime-system 0.1.0", +] + +[[package]] +name = "substrate-runtime-executive" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.27 (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-session 0.1.0", + "substrate-runtime-staking 0.1.0", + "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", + "substrate-runtime-system 0.1.0", +] + [[package]] name = "substrate-runtime-io" version = "0.1.0" @@ -1794,6 +1893,58 @@ dependencies = [ "triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "substrate-runtime-primitives" +version = "0.1.0" +dependencies = [ + "integer-sqrt 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-primitives 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", +] + +[[package]] +name = "substrate-runtime-session" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "safe-mix 0.1.0", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-keyring 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", + "substrate-runtime-support 0.1.0", + "substrate-runtime-system 0.1.0", +] + +[[package]] +name = "substrate-runtime-staking" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "safe-mix 0.1.0", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-keyring 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-session 0.1.0", + "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", + "substrate-runtime-system 0.1.0", +] + [[package]] name = "substrate-runtime-std" version = "0.1.0" @@ -1808,12 +1959,41 @@ name = "substrate-runtime-support" version = "0.1.0" dependencies = [ "ed25519 0.1.0", - "environmental 0.1.0", "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-primitives 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-std 0.1.0", +] + +[[package]] +name = "substrate-runtime-system" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "safe-mix 0.1.0", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-io 0.1.0", + "substrate-runtime-primitives 0.1.0", + "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", +] + +[[package]] +name = "substrate-runtime-timestamp" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-primitives 0.1.0", "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", ] [[package]] @@ -2302,6 +2482,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "3a80f842784ef6c9a958b68b7516bc7e35883c614004dd94959a4dca1b716c09" "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" "checksum num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "9936036cc70fe4a8b2d338ab665900323290efb03983c86cbe235ae800ad8017" +"checksum num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dee092fcdf725aee04dd7da1d21debff559237d49ef1cb3e69bcb8ece44c7364" "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" "checksum odds 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)" = "4eae0151b9dacf24fcc170d9995e511669a082856a91f958a2fe380bfab3fb22" "checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c" diff --git a/Cargo.toml b/Cargo.toml index b4d18443da82c..4c5c69275042e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,15 @@ members = [ "substrate/runtime-io", "substrate/runtime-std", "substrate/runtime-support", + "substrate/runtime/consensus", + "substrate/runtime/council", + "substrate/runtime/democracy", + "substrate/runtime/executive", + "substrate/runtime/primitives", + "substrate/runtime/session", + "substrate/runtime/staking", + "substrate/runtime/system", + "substrate/runtime/timestamp", "substrate/serializer", "substrate/state-machine", "substrate/test-runtime", @@ -46,6 +55,7 @@ members = [ "demo/primitives", "demo/executor", "demo/cli", + "safe-mix", ] exclude = [ "polkadot/runtime/wasm", diff --git a/build.sh b/build.sh index d6545f4f4fb8f..43d8deb8c1549 100755 --- a/build.sh +++ b/build.sh @@ -1,6 +1,6 @@ #!/bin/sh +cd demo/runtime/wasm && ./build.sh && cd ../../.. cd substrate/executor/wasm && ./build.sh && cd ../../.. cd substrate/test-runtime/wasm && ./build.sh && cd ../../.. cd polkadot/runtime/wasm && ./build.sh && cd ../../.. -cd demo/runtime/wasm && ./build.sh && cd ../../.. diff --git a/demo/cli/src/lib.rs b/demo/cli/src/lib.rs index 63ee1e3811506..b58295775bc8b 100644 --- a/demo/cli/src/lib.rs +++ b/demo/cli/src/lib.rs @@ -22,6 +22,7 @@ extern crate env_logger; extern crate ed25519; extern crate triehash; extern crate substrate_codec as codec; +extern crate substrate_runtime_io as runtime_io; extern crate substrate_state_machine as state_machine; extern crate substrate_client as client; extern crate substrate_primitives as primitives; @@ -43,7 +44,9 @@ pub mod error; use std::sync::Arc; use codec::Slicable; -use demo_runtime::genesismap::{additional_storage_with_genesis, GenesisConfig}; +use runtime_io::with_externalities; +use demo_runtime::{GenesisConfig, ConsensusConfig, CouncilConfig, DemocracyConfig, + SessionConfig, StakingConfig, BuildExternalities}; use client::genesis; /// Parse command line arguments and start the node. @@ -71,32 +74,52 @@ pub fn run(args: I) -> error::Result<()> where let god_key = hex!["3d866ec8a9190c8343c2fc593d21d8a6d0c5c4763aaab2349de3a6111d64d124"]; let genesis_config = GenesisConfig { - validators: vec![god_key.clone()], - authorities: vec![god_key.clone()], - balances: vec![(god_key.clone(), 1u64 << 63)].into_iter().collect(), - block_time: 5, // 5 second block time. - session_length: 720, // that's 1 hour per session. - sessions_per_era: 24, // 24 hours per era. - bonding_duration: 90, // 90 days per bond. - 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 - candidacy_bond: 1000, // 1000 to become a council candidate - voter_bond: 100, // 100 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. - council_election_voting_period: 7 * 120 * 24, // one week period between possible council elections. - council_term_duration: 180 * 120 * 24, // 180 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. - council_proposal_voting_period: 7 * 120 * 24, // 7 day voting period for council members. + consensus: Some(ConsensusConfig { + authorities: vec![god_key.clone()], + }), + system: None, +// block_time: 5, // 5 second block time. + session: Some(SessionConfig { + validators: vec![god_key.clone()], + session_length: 720, // that's 1 hour per session. + }), + staking: Some(StakingConfig { + current_era: 0, + intentions: vec![], + transaction_fee: 100, + balances: vec![(god_key.clone(), 1u64 << 63)].into_iter().collect(), + validator_count: 12, + sessions_per_era: 24, // 24 hours per era. + bonding_duration: 90, // 90 days 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 + }), + 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 + 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. + 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. + }), }; let prepare_genesis = || { - storage = genesis_config.genesis_map(); + storage = genesis_config.build_externalities(); let block = genesis::construct_genesis_block(&storage); - storage.extend(additional_storage_with_genesis(&block)); + with_externalities(&mut storage, || + // TODO: use api.rs to dispatch instead + demo_runtime::System::initialise_genesis_state(&block.header) + ); (primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect()) }; let client = Arc::new(client::new_in_mem(executor, prepare_genesis)?); diff --git a/demo/executor/Cargo.toml b/demo/executor/Cargo.toml index ad2d53f9921b3..2cdc0088399a3 100644 --- a/demo/executor/Cargo.toml +++ b/demo/executor/Cargo.toml @@ -19,3 +19,6 @@ demo-runtime = { path = "../runtime" } [dev-dependencies] substrate-keyring = { path = "../../substrate/keyring" } +substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" } +substrate-runtime-staking = { path = "../../substrate/runtime/staking" } +substrate-runtime-system = { path = "../../substrate/runtime/system" } diff --git a/demo/executor/src/lib.rs b/demo/executor/src/lib.rs index 8d94b475acbdd..c64855a3d35fc 100644 --- a/demo/executor/src/lib.rs +++ b/demo/executor/src/lib.rs @@ -28,7 +28,10 @@ extern crate ed25519; extern crate triehash; #[cfg(test)] extern crate substrate_keyring as keyring; +#[cfg(test)] extern crate substrate_runtime_primitives as runtime_primitives; #[cfg(test)] extern crate substrate_runtime_support as runtime_support; +#[cfg(test)] extern crate substrate_runtime_staking as staking; +#[cfg(test)] extern crate substrate_runtime_system as system; #[cfg(test)] #[macro_use] extern crate hex_literal; native_executor_instance!(pub Executor, demo_runtime::api::dispatch, include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm")); @@ -43,12 +46,11 @@ mod tests { use runtime_support::{Hashable, StorageValue, StorageMap}; use state_machine::{CodeExecutor, TestExternalities}; use primitives::twox_128; - use demo_primitives::{Hash, Header, BlockNumber, Digest}; - use demo_runtime::transaction::{Transaction, UncheckedTransaction}; - use demo_runtime::block::Block; - use demo_runtime::runtime::staking::{FreeBalanceOf, balance}; - use demo_runtime::runtime::{staking, system}; - use demo_runtime::dispatch; + use demo_primitives::{Hash, BlockNumber}; + use runtime_primitives::traits::Header as HeaderT; + use {staking, system}; + use demo_runtime::{Header, Block, UncheckedExtrinsic, Extrinsic, Call, Concrete, Staking, + BuildExternalities, GenesisConfig, SessionConfig, StakingConfig}; use ed25519::{Public, Pair}; const BLOATY_CODE: &[u8] = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm"); @@ -61,121 +63,145 @@ mod tests { ) } - fn tx() -> UncheckedTransaction { - let transaction = Transaction { + fn xt() -> UncheckedExtrinsic { + let extrinsic = Extrinsic { signed: Alice.into(), - nonce: 0, - function: dispatch::PubCall::Staking(staking::public::Call::transfer(Bob.into(), 69)), + index: 0, + function: Call::Staking(staking::Call::transfer::(Bob.into(), 69)), }; - let signature = Keyring::from_raw_public(transaction.signed).unwrap() - .sign(&transaction.encode()); + let signature = Keyring::from_raw_public(extrinsic.signed).unwrap() + .sign(&extrinsic.encode()).into(); - UncheckedTransaction { transaction, signature } + UncheckedExtrinsic { extrinsic, signature } + } + + fn from_block_number(n: u64) -> Header { + Header::new(n, Default::default(), Default::default(), [69; 32].into(), Default::default()) } #[test] fn panic_execution_with_foreign_code_gives_error() { let mut t: TestExternalities = map![ - twox_128(&FreeBalanceOf::key_for(*Alice)).to_vec() => vec![68u8, 0, 0, 0, 0, 0, 0, 0], - twox_128(staking::TransactionFee::key()).to_vec() => vec![0u8; 8], - twox_128(&system::BlockHashAt::key_for(0)).to_vec() => vec![0u8; 32] + twox_128(&>::key_for(*Alice)).to_vec() => vec![68u8, 0, 0, 0, 0, 0, 0, 0], + twox_128(>::key()).to_vec() => vec![0u8; 8], + twox_128(&>::key_for(0)).to_vec() => vec![0u8; 32] ]; - let r = Executor::new().call(&mut t, BLOATY_CODE, "initialise_block", &vec![].and(&Header::from_block_number(1u64))); + let r = Executor::new().call(&mut t, BLOATY_CODE, "initialise_block", &vec![].and(&from_block_number(1u64))); assert!(r.is_ok()); - let r = Executor::new().call(&mut t, BLOATY_CODE, "execute_transaction", &vec![].and(&tx())); + let r = Executor::new().call(&mut t, BLOATY_CODE, "apply_extrinsic", &vec![].and(&xt())); assert!(r.is_err()); } #[test] fn panic_execution_with_native_equivalent_code_gives_error() { let mut t: TestExternalities = map![ - twox_128(&FreeBalanceOf::key_for(*Alice)).to_vec() => vec![68u8, 0, 0, 0, 0, 0, 0, 0], - twox_128(staking::TransactionFee::key()).to_vec() => vec![0u8; 8], - twox_128(&system::BlockHashAt::key_for(0)).to_vec() => vec![0u8; 32] + twox_128(&>::key_for(*Alice)).to_vec() => vec![68u8, 0, 0, 0, 0, 0, 0, 0], + twox_128(>::key()).to_vec() => vec![0u8; 8], + twox_128(&>::key_for(0)).to_vec() => vec![0u8; 32] ]; - let r = Executor::new().call(&mut t, COMPACT_CODE, "initialise_block", &vec![].and(&Header::from_block_number(1u64))); + let r = Executor::new().call(&mut t, COMPACT_CODE, "initialise_block", &vec![].and(&from_block_number(1u64))); assert!(r.is_ok()); - let r = Executor::new().call(&mut t, COMPACT_CODE, "execute_transaction", &vec![].and(&tx())); + let r = Executor::new().call(&mut t, COMPACT_CODE, "apply_extrinsic", &vec![].and(&xt())); assert!(r.is_err()); } #[test] fn successful_execution_with_native_equivalent_code_gives_ok() { let mut t: TestExternalities = map![ - twox_128(&FreeBalanceOf::key_for(*Alice)).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0], - twox_128(staking::TransactionFee::key()).to_vec() => vec![0u8; 8], - twox_128(&system::BlockHashAt::key_for(0)).to_vec() => vec![0u8; 32] + twox_128(&>::key_for(*Alice)).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0], + twox_128(>::key()).to_vec() => vec![0u8; 8], + twox_128(&>::key_for(0)).to_vec() => vec![0u8; 32] ]; - let r = Executor::new().call(&mut t, COMPACT_CODE, "initialise_block", &vec![].and(&Header::from_block_number(1u64))); + let r = Executor::new().call(&mut t, COMPACT_CODE, "initialise_block", &vec![].and(&from_block_number(1u64))); assert!(r.is_ok()); - let r = Executor::new().call(&mut t, COMPACT_CODE, "execute_transaction", &vec![].and(&tx())); + let r = Executor::new().call(&mut t, COMPACT_CODE, "apply_extrinsic", &vec![].and(&xt())); assert!(r.is_ok()); runtime_io::with_externalities(&mut t, || { - assert_eq!(balance(&Alice), 42); - assert_eq!(balance(&Bob), 69); + assert_eq!(Staking::balance(&Alice), 42); + assert_eq!(Staking::balance(&Bob), 69); }); } #[test] fn successful_execution_with_foreign_code_gives_ok() { let mut t: TestExternalities = map![ - twox_128(&FreeBalanceOf::key_for(*Alice)).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0], - twox_128(staking::TransactionFee::key()).to_vec() => vec![0u8; 8], - twox_128(&system::BlockHashAt::key_for(0)).to_vec() => vec![0u8; 32] + twox_128(&>::key_for(*Alice)).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0], + twox_128(>::key()).to_vec() => vec![0u8; 8], + twox_128(&>::key_for(0)).to_vec() => vec![0u8; 32] ]; - let r = Executor::new().call(&mut t, BLOATY_CODE, "initialise_block", &vec![].and(&Header::from_block_number(1u64))); + let r = Executor::new().call(&mut t, BLOATY_CODE, "initialise_block", &vec![].and(&from_block_number(1u64))); assert!(r.is_ok()); - let r = Executor::new().call(&mut t, BLOATY_CODE, "execute_transaction", &vec![].and(&tx())); + let r = Executor::new().call(&mut t, BLOATY_CODE, "apply_extrinsic", &vec![].and(&xt())); assert!(r.is_ok()); runtime_io::with_externalities(&mut t, || { - assert_eq!(balance(&Alice), 42); - assert_eq!(balance(&Bob), 69); + assert_eq!(Staking::balance(&Alice), 42); + assert_eq!(Staking::balance(&Bob), 69); }); } fn new_test_ext() -> TestExternalities { - staking::testing::externalities(2, 2, 0) + use keyring::Keyring::*; + let three = [3u8; 32]; + GenesisConfig { + consensus: Some(Default::default()), + system: Some(Default::default()), + session: Some(SessionConfig { + session_length: 2, + validators: vec![One.into(), Two.into(), three], + }), + staking: Some(StakingConfig { + sessions_per_era: 2, + current_era: 0, + balances: vec![(Alice.into(), 111)], + intentions: vec![Alice.into(), Bob.into(), Charlie.into()], + validator_count: 3, + bonding_duration: 0, + transaction_fee: 1, + }), + democracy: Some(Default::default()), + council: Some(Default::default()), + }.build_externalities() } - fn construct_block(number: BlockNumber, parent_hash: Hash, state_root: Hash, txs: Vec) -> (Vec, Hash) { + fn construct_block(number: BlockNumber, parent_hash: Hash, state_root: Hash, extrinsics: Vec) -> (Vec, Hash) { use triehash::ordered_trie_root; - let transactions = txs.into_iter().map(|transaction| { - let signature = Pair::from(Keyring::from_public(Public::from_raw(transaction.signed)).unwrap()) - .sign(&transaction.encode()); + let extrinsics = extrinsics.into_iter().map(|extrinsic| { + let signature = Pair::from(Keyring::from_public(Public::from_raw(extrinsic.signed)).unwrap()) + .sign(&extrinsic.encode()).into(); - UncheckedTransaction { transaction, signature } + UncheckedExtrinsic { extrinsic, signature } }).collect::>(); - let transaction_root = ordered_trie_root(transactions.iter().map(Slicable::encode)).0.into(); + let extrinsics_root = ordered_trie_root(extrinsics.iter().map(Slicable::encode)).0.into(); let header = Header { parent_hash, number, state_root, - transaction_root, - digest: Digest { logs: vec![], }, + extrinsics_root, + digest: Default::default(), }; let hash = header.blake2_256(); - (Block { header, transactions }.encode(), hash.into()) + (Block { header, extrinsics }.encode(), hash.into()) } fn block1() -> (Vec, Hash) { construct_block( 1, [69u8; 32].into(), - hex!("7a388ce5b4eeadbb9268ae96e8822b223f4fd1841327d99f4e1c21fad81f97f2").into(), - vec![Transaction { + hex!("4d58afeca0dec7604a0bcfb29573e6ad202efe65c8535b013c0c79b5a8c9114d").into(), + vec![Extrinsic { signed: Alice.into(), - nonce: 0, - function: dispatch::PubCall::Staking(staking::public::Call::transfer(Bob.into(), 69)), + index: 0, + function: Call::Staking(staking::Call::transfer(Bob.into(), 69)), }] ) } @@ -184,17 +210,17 @@ mod tests { construct_block( 2, block1().1, - hex!("e4eb71be8b816f2061f32f284e9b429562cdc1b82f11725e5f965ff23439f5e9").into(), + hex!("218ce7bdf2350713aa52dbf0a12d1e8b0a3a9f1623e7c95aa4800886e96397d1").into(), vec![ - Transaction { + Extrinsic { signed: Bob.into(), - nonce: 0, - function: dispatch::PubCall::Staking(staking::public::Call::transfer(Alice.into(), 5)), + index: 0, + function: Call::Staking(staking::Call::transfer(Alice.into(), 5)), }, - Transaction { + Extrinsic { signed: Alice.into(), - nonce: 1, - function: dispatch::PubCall::Staking(staking::public::Call::transfer(Bob.into(), 15)), + index: 1, + function: Call::Staking(staking::Call::transfer(Bob.into(), 15)), } ] ) @@ -207,15 +233,15 @@ mod tests { Executor::new().call(&mut t, COMPACT_CODE, "execute_block", &block1().0).unwrap(); runtime_io::with_externalities(&mut t, || { - assert_eq!(balance(&Alice), 41); - assert_eq!(balance(&Bob), 69); + assert_eq!(Staking::balance(&Alice), 41); + assert_eq!(Staking::balance(&Bob), 69); }); Executor::new().call(&mut t, COMPACT_CODE, "execute_block", &block2().0).unwrap(); runtime_io::with_externalities(&mut t, || { - assert_eq!(balance(&Alice), 30); - assert_eq!(balance(&Bob), 78); + assert_eq!(Staking::balance(&Alice), 30); + assert_eq!(Staking::balance(&Bob), 78); }); } @@ -226,50 +252,50 @@ mod tests { WasmExecutor.call(&mut t, COMPACT_CODE, "execute_block", &block1().0).unwrap(); runtime_io::with_externalities(&mut t, || { - assert_eq!(balance(&Alice), 41); - assert_eq!(balance(&Bob), 69); + assert_eq!(Staking::balance(&Alice), 41); + assert_eq!(Staking::balance(&Bob), 69); }); WasmExecutor.call(&mut t, COMPACT_CODE, "execute_block", &block2().0).unwrap(); runtime_io::with_externalities(&mut t, || { - assert_eq!(balance(&Alice), 30); - assert_eq!(balance(&Bob), 78); + assert_eq!(Staking::balance(&Alice), 30); + assert_eq!(Staking::balance(&Bob), 78); }); } #[test] fn panic_execution_gives_error() { let mut t: TestExternalities = map![ - twox_128(&FreeBalanceOf::key_for(*Alice)).to_vec() => vec![68u8, 0, 0, 0, 0, 0, 0, 0], - twox_128(staking::TransactionFee::key()).to_vec() => vec![0u8; 8], - twox_128(&system::BlockHashAt::key_for(0)).to_vec() => vec![0u8; 32] + twox_128(&>::key_for(*Alice)).to_vec() => vec![68u8, 0, 0, 0, 0, 0, 0, 0], + twox_128(>::key()).to_vec() => vec![0u8; 8], + twox_128(&>::key_for(0)).to_vec() => vec![0u8; 32] ]; let foreign_code = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm"); - let r = WasmExecutor.call(&mut t, &foreign_code[..], "initialise_block", &vec![].and(&Header::from_block_number(1u64))); + let r = WasmExecutor.call(&mut t, &foreign_code[..], "initialise_block", &vec![].and(&from_block_number(1u64))); assert!(r.is_ok()); - let r = WasmExecutor.call(&mut t, &foreign_code[..], "execute_transaction", &vec![].and(&tx())); + let r = WasmExecutor.call(&mut t, &foreign_code[..], "apply_extrinsic", &vec![].and(&xt())); assert!(r.is_err()); } #[test] fn successful_execution_gives_ok() { let mut t: TestExternalities = map![ - twox_128(&FreeBalanceOf::key_for(*Alice)).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0], - twox_128(staking::TransactionFee::key()).to_vec() => vec![0u8; 8], - twox_128(&system::BlockHashAt::key_for(0)).to_vec() => vec![0u8; 32] + twox_128(&>::key_for(*Alice)).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0], + twox_128(>::key()).to_vec() => vec![0u8; 8], + twox_128(&>::key_for(0)).to_vec() => vec![0u8; 32] ]; let foreign_code = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm"); - let r = WasmExecutor.call(&mut t, &foreign_code[..], "initialise_block", &vec![].and(&Header::from_block_number(1u64))); + let r = WasmExecutor.call(&mut t, &foreign_code[..], "initialise_block", &vec![].and(&from_block_number(1u64))); assert!(r.is_ok()); - let r = WasmExecutor.call(&mut t, &foreign_code[..], "execute_transaction", &vec![].and(&tx())); + let r = WasmExecutor.call(&mut t, &foreign_code[..], "apply_extrinsic", &vec![].and(&xt())); assert!(r.is_ok()); runtime_io::with_externalities(&mut t, || { - assert_eq!(balance(&Alice), 42); - assert_eq!(balance(&Bob), 69); + assert_eq!(Staking::balance(&Alice), 42); + assert_eq!(Staking::balance(&Bob), 69); }); } } diff --git a/demo/primitives/Cargo.toml b/demo/primitives/Cargo.toml index a8365cf41fb51..1a359e06e7463 100644 --- a/demo/primitives/Cargo.toml +++ b/demo/primitives/Cargo.toml @@ -9,6 +9,7 @@ serde_derive = { version = "1.0", optional = true } substrate-codec = { path = "../../substrate/codec", default_features = false } substrate-primitives = { path = "../../substrate/primitives", default_features = false } substrate-runtime-std = { path = "../../substrate/runtime-std", default_features = false } +substrate-runtime-primitives = { path = "../../substrate/runtime/primitives", default_features = false } [dev-dependencies] substrate-serializer = { path = "../../substrate/serializer" } @@ -20,6 +21,7 @@ std = [ "substrate-codec/std", "substrate-primitives/std", "substrate-runtime-std/std", + "substrate-runtime-primitives/std", "serde_derive", "serde/std", ] diff --git a/demo/primitives/src/block.rs b/demo/primitives/src/block.rs deleted file mode 100644 index f5119ac4b3f22..0000000000000 --- a/demo/primitives/src/block.rs +++ /dev/null @@ -1,154 +0,0 @@ -// 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 . - -//! Block and header type definitions. - -#[cfg(feature = "std")] -use primitives::bytes; -use primitives::H256; -use rstd::vec::Vec; -use codec::{Input, Slicable}; - -pub use primitives::block::Id; - -/// Used to refer to a block number. -pub type Number = u64; - -/// Hash used to refer to a block hash. -pub type HeaderHash = H256; - -/// Execution log (event) -#[derive(PartialEq, Eq, Clone)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] -pub struct Log(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); - -impl Slicable for Log { - fn decode(input: &mut I) -> Option { - Vec::::decode(input).map(Log) - } - - fn using_encoded R>(&self, f: F) -> R { - self.0.using_encoded(f) - } -} - -impl ::codec::NonTrivialSlicable for Log { } - -/// The digest of a block, useful for light-clients. -#[derive(Clone, Default, PartialEq, Eq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] -pub struct Digest { - /// All logs that have happened in the block. - pub logs: Vec, -} - -impl Slicable for Digest { - fn decode(input: &mut I) -> Option { - Vec::::decode(input).map(|logs| Digest { logs }) - } - - fn using_encoded R>(&self, f: F) -> R { - self.logs.using_encoded(f) - } -} - -/// Header for a block. -#[derive(PartialEq, Eq, Clone)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] -#[cfg_attr(feature = "std", serde(deny_unknown_fields))] -pub struct Header { - /// Block parent's hash. - pub parent_hash: HeaderHash, - /// Block number. - pub number: Number, - /// State root after this transition. - pub state_root: H256, - /// The root of the trie that represents this block's transactions, indexed by a 32-byte integer. - pub transaction_root: H256, - /// The digest of activity on the block. - pub digest: Digest, -} - -impl Header { - /// Create a new instance with default fields except `number`, which is given as an argument. - pub fn from_block_number(number: Number) -> Self { - Header { - parent_hash: Default::default(), - number, - state_root: Default::default(), - transaction_root: Default::default(), - digest: Default::default(), - } - } -} - -impl Slicable for Header { - fn decode(input: &mut I) -> Option { - Some(Header { - parent_hash: try_opt!(Slicable::decode(input)), - number: try_opt!(Slicable::decode(input)), - state_root: try_opt!(Slicable::decode(input)), - transaction_root: try_opt!(Slicable::decode(input)), - digest: try_opt!(Slicable::decode(input)), - }) - } - - fn encode(&self) -> Vec { - let mut v = Vec::new(); - - self.parent_hash.using_encoded(|s| v.extend(s)); - self.number.using_encoded(|s| v.extend(s)); - self.state_root.using_encoded(|s| v.extend(s)); - self.transaction_root.using_encoded(|s| v.extend(s)); - self.digest.using_encoded(|s| v.extend(s)); - - v - } -} - -#[cfg(test)] -mod tests { - use super::*; - use codec::Slicable; - use substrate_serializer as ser; - - #[test] - fn test_header_serialization() { - let header = Header { - parent_hash: 5.into(), - number: 67, - state_root: 3.into(), - transaction_root: 6.into(), - digest: Digest { logs: vec![Log(vec![1])] }, - }; - - assert_eq!(ser::to_string_pretty(&header), r#"{ - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000005", - "number": 67, - "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000003", - "transactionRoot": "0x0000000000000000000000000000000000000000000000000000000000000006", - "digest": { - "logs": [ - "0x01" - ] - } -}"#); - - let v = header.encode(); - assert_eq!(Header::decode(&mut &v[..]).unwrap(), header); - } -} diff --git a/demo/primitives/src/lib.rs b/demo/primitives/src/lib.rs index c65d4a63ef700..ecfb5f7e7d214 100644 --- a/demo/primitives/src/lib.rs +++ b/demo/primitives/src/lib.rs @@ -21,47 +21,32 @@ #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(alloc))] - -#[cfg(feature = "std")] -#[macro_use] -extern crate serde_derive; -#[cfg(feature = "std")] -extern crate serde; +#[cfg(feature = "std")] extern crate serde; extern crate substrate_runtime_std as rstd; +extern crate substrate_runtime_primitives as runtime_primitives; extern crate substrate_primitives as primitives; -#[cfg(test)] -extern crate substrate_serializer; - extern crate substrate_codec as codec; -macro_rules! try_opt { - ($e: expr) => { - match $e { - Some(x) => x, - None => return None, - } - } -} - -pub mod block; - -pub use self::block::{Header, Log, Digest}; -pub use self::block::Number as BlockNumber; +/// An index to a block. +pub type BlockNumber = u64; /// Alias to Ed25519 pubkey that identifies an account on the relay chain. This will almost /// certainly continue to be the same as the substrate's `AuthorityId`. pub type AccountId = primitives::AuthorityId; +/// Balance of an account. +pub type Balance = u64; + /// The Ed25519 pub key of an session that belongs to an authority of the relay chain. This is /// exactly equivalent to what the substrate calls an "authority". pub type SessionKey = primitives::AuthorityId; /// Index of a transaction in the relay chain. -pub type TxOrder = u64; +pub type Index = u64; /// A hash of some data used by the relay chain. pub type Hash = primitives::H256; -/// Alias to 520-bit hash when used in the context of a signature on the relay chain. -pub type Signature = primitives::hash::H512; +/// Alias to 512-bit hash when used in the context of a signature on the relay chain. +pub type Signature = runtime_primitives::traits::Ed25519Signature; diff --git a/demo/runtime/Cargo.toml b/demo/runtime/Cargo.toml index 4033111d7d093..cb9988ffc9e01 100644 --- a/demo/runtime/Cargo.toml +++ b/demo/runtime/Cargo.toml @@ -9,25 +9,44 @@ hex-literal = "0.1.0" log = { version = "0.3", optional = true } serde = { version = "1.0", default_features = false } serde_derive = { version = "1.0", optional = true } +safe-mix = { path = "../../safe-mix", default_features = false} substrate-codec = { path = "../../substrate/codec" } substrate-runtime-std = { path = "../../substrate/runtime-std" } substrate-runtime-io = { path = "../../substrate/runtime-io" } substrate-runtime-support = { path = "../../substrate/runtime-support" } substrate-primitives = { path = "../../substrate/primitives" } substrate-keyring = { path = "../../substrate/keyring" } +substrate-runtime-consensus = { path = "../../substrate/runtime/consensus" } +substrate-runtime-council = { path = "../../substrate/runtime/council" } +substrate-runtime-democracy = { path = "../../substrate/runtime/democracy" } +substrate-runtime-executive = { path = "../../substrate/runtime/executive" } +substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" } +substrate-runtime-session = { path = "../../substrate/runtime/session" } +substrate-runtime-staking = { path = "../../substrate/runtime/staking" } +substrate-runtime-system = { path = "../../substrate/runtime/system" } +substrate-runtime-timestamp = { path = "../../substrate/runtime/timestamp" } demo-primitives = { path = "../primitives" } -integer-sqrt = "0.1.0" [features] default = ["std"] std = [ "substrate-codec/std", + "substrate-primitives/std", "substrate-runtime-std/std", "substrate-runtime-io/std", "substrate-runtime-support/std", - "substrate-primitives/std", + "substrate-runtime-consensus/std", + "substrate-runtime-council/std", + "substrate-runtime-democracy/std", + "substrate-runtime-executive/std", + "substrate-runtime-primitives/std", + "substrate-runtime-session/std", + "substrate-runtime-staking/std", + "substrate-runtime-system/std", + "substrate-runtime-timestamp/std", "demo-primitives/std", "serde_derive", "serde/std", - "log" + "log", + "safe-mix/std" ] diff --git a/demo/runtime/src/api.rs b/demo/runtime/src/api.rs deleted file mode 100644 index d590150061860..0000000000000 --- a/demo/runtime/src/api.rs +++ /dev/null @@ -1,27 +0,0 @@ -// 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 . - -use runtime::{system, consensus, session}; - -impl_stubs!( - execute_block => |block| system::internal::execute_block(block), - initialise_block => |header| system::internal::initialise_block(&header), - execute_transaction => |utx| system::internal::execute_transaction(utx), - finalise_block => |()| system::internal::finalise_block(), - validator_count => |()| session::validator_count(), - validators => |()| session::validators(), - authorities => |()| consensus::authorities() -); diff --git a/demo/runtime/src/block.rs b/demo/runtime/src/block.rs deleted file mode 100644 index 182f9ca89b67e..0000000000000 --- a/demo/runtime/src/block.rs +++ /dev/null @@ -1,52 +0,0 @@ -// 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 . - -//! Block and header type definitions. - -use rstd::prelude::*; -use codec::{Input, Slicable}; -use transaction::UncheckedTransaction; - -pub use demo_primitives::block::{Header, Digest, Log, Number, HeaderHash}; - -/// The block "body": A bunch of transactions. -pub type Body = Vec; - -/// A block on the chain. -#[derive(PartialEq, Eq, Clone)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] -pub struct Block { - /// The block header. - pub header: Header, - /// All relay-chain transactions. - pub transactions: Body, -} - -impl Slicable for Block { - fn decode(input: &mut I) -> Option { - let (header, transactions) = Slicable::decode(input)?; - Some(Block { header, transactions }) - } - - fn encode(&self) -> Vec { - let mut v = Vec::new(); - - v.extend(self.header.encode()); - v.extend(self.transactions.encode()); - - v - } -} diff --git a/demo/runtime/src/dispatch.rs b/demo/runtime/src/dispatch.rs deleted file mode 100644 index 8290d071aa0b1..0000000000000 --- a/demo/runtime/src/dispatch.rs +++ /dev/null @@ -1,246 +0,0 @@ -// 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 . - -//! Dispatch system. Just dispatches calls. - -use runtime::{staking, democracy}; -pub use rstd::prelude::Vec; -pub use codec::{Slicable, Input, NonTrivialSlicable}; - -/// Implement a dispatch module to create a pairing of a dispatch trait and enum. -#[macro_export] -macro_rules! impl_dispatch { - ( - pub mod $mod_name:ident; - $( - fn $fn_name:ident( - $( - $param_name:ident : $param:ty - ),* - ) - = $id:expr ; - )* - ) => { - pub mod $mod_name { - use super::*; - - #[derive(Clone, Copy, PartialEq, Eq)] - #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] - #[repr(u32)] - #[allow(non_camel_case_types)] - enum Id { - $( - #[allow(non_camel_case_types)] - $fn_name = $id, - )* - } - - impl Id { - /// Derive `Some` value from a `u8`, or `None` if it's invalid. - fn from_u8(value: u8) -> Option { - match value { - $( - $id => Some(Id::$fn_name), - )* - _ => None, - } - } - } - - #[derive(Clone, PartialEq, Eq)] - #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] - #[allow(missing_docs)] - pub enum Call { - $( - #[allow(non_camel_case_types)] - $fn_name ( $( $param ),* ) - ,)* - } - - pub trait Dispatch: Sized { - $( - fn $fn_name (self, $( $param_name: $param ),* ); - )* - } - - impl Call { - pub fn dispatch(self, d: D) { - match self { - $( - Call::$fn_name( $( $param_name ),* ) => - d.$fn_name( $( $param_name ),* ), - )* - } - } - } - - impl $crate::dispatch::Slicable for Call { - fn decode(input: &mut I) -> Option { - let id = u8::decode(input).and_then(Id::from_u8)?; - Some(match id { - $( - Id::$fn_name => { - $( - let $param_name = $crate::dispatch::Slicable::decode(input)?; - )* - Call :: $fn_name( $( $param_name ),* ) - } - )* - }) - } - - fn encode(&self) -> $crate::dispatch::Vec { - let mut v = $crate::dispatch::Vec::new(); - match *self { - $( - Call::$fn_name( - $( - ref $param_name - ),* - ) => { - (Id::$fn_name as u8).using_encoded(|s| v.extend(s)); - $( - $param_name.using_encoded(|s| v.extend(s)); - )* - } - )* - } - v - } - - fn using_encoded R>(&self, f: F) -> R { - f(self.encode().as_slice()) - } - } - impl $crate::dispatch::NonTrivialSlicable for Call {} - } - } -} - -macro_rules! impl_meta_dispatch { - ( - pub mod $super_name:ident; - path $path:ident; - trait $trait:ty; - $( - $camelcase:ident(mod $sub_name:ident) = $id:expr ; - )* - ) => { - pub mod $super_name { - use super::*; - - #[derive(Clone, Copy, PartialEq, Eq)] - #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] - #[repr(u32)] - #[allow(non_camel_case_types)] - enum Id { - $( - #[allow(non_camel_case_types)] - $camelcase = $id, - )* - } - - impl Id { - /// Derive `Some` value from a `u8`, or `None` if it's invalid. - fn from_u8(value: u8) -> Option { - match value { - $( - $id => Some(Id::$camelcase), - )* - _ => None, - } - } - } - - #[derive(Clone, PartialEq, Eq)] - #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] - #[allow(missing_docs)] - pub enum Call { - $( - #[allow(non_camel_case_types)] - $camelcase ( $crate::runtime::$sub_name::$path::Call ) - ,)* - } - - impl Call { - pub fn dispatch(self, d: $trait) { - match self { - $( - Call::$camelcase(x) => x.dispatch(d), - )* - } - } - } - - impl $crate::dispatch::Slicable for Call { - fn decode(input: &mut I) -> Option { - let id = u8::decode(input).and_then(Id::from_u8)?; - Some(match id { - $( - Id::$camelcase => - Call::$camelcase( $crate::dispatch::Slicable::decode(input)? ), - )* - }) - } - - fn encode(&self) -> Vec { - let mut v = $crate::dispatch::Vec::new(); - match *self { - $( - Call::$camelcase( ref sub ) => { - (Id::$camelcase as u8).using_encoded(|s| v.extend(s)); - sub.using_encoded(|s| v.extend(s)); - } - )* - } - v - } - - fn using_encoded R>(&self, f: F) -> R { - f(self.encode().as_slice()) - } - } - impl $crate::dispatch::NonTrivialSlicable for Call {} - } - } -} - -impl_meta_dispatch! { - pub mod public; - path public; - trait staking::PublicPass; - Session(mod session) = 1; - Staking(mod staking) = 2; - Timestamp(mod timestamp) = 3; - Democracy(mod democracy) = 5; - Council(mod council) = 6; - CouncilVote(mod council) = 7; -} - -impl_meta_dispatch! { - pub mod privileged; - path privileged; - trait democracy::PrivPass; - System(mod system) = 0; - Session(mod session) = 1; - Staking(mod staking) = 2; - Democracy(mod democracy) = 5; - Council(mod council) = 6; - CouncilVote(mod council) = 7; -} - -pub use self::privileged::Call as PrivCall; -pub use self::public::Call as PubCall; diff --git a/demo/runtime/src/genesismap.rs b/demo/runtime/src/genesismap.rs deleted file mode 100644 index 5fa83499b335a..0000000000000 --- a/demo/runtime/src/genesismap.rs +++ /dev/null @@ -1,129 +0,0 @@ -// 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 . - -//! Tool for creating the genesis block. - -use codec::{KeyedVec, Joiner}; -use std::collections::HashMap; -use runtime_io::twox_128; -use runtime_support::{Hashable, StorageMap, StorageList, StorageValue}; -use primitives::Block; -use demo_primitives::{BlockNumber, AccountId}; -use runtime::staking::Balance; -use runtime::{staking, session, consensus, system, democracy, council, council_vote}; - -/// Configuration of a general Substrate Demo genesis block. -pub struct GenesisConfig { - pub validators: Vec, - pub authorities: Vec, - pub balances: Vec<(AccountId, Balance)>, - pub block_time: u64, - pub session_length: BlockNumber, - pub sessions_per_era: BlockNumber, - pub bonding_duration: BlockNumber, - pub launch_period: BlockNumber, - pub voting_period: BlockNumber, - pub minimum_deposit: Balance, - pub candidacy_bond: Balance, - pub voter_bond: Balance, - pub present_slash_per_voter: Balance, - pub carry_count: u32, - pub presentation_duration: BlockNumber, - pub council_election_voting_period: BlockNumber, - pub council_term_duration: BlockNumber, - pub desired_seats: u32, - pub inactive_grace_period: BlockNumber, - pub cooloff_period: BlockNumber, - pub council_proposal_voting_period: BlockNumber, -} - -impl GenesisConfig { - pub fn new_simple(authorities_validators: Vec, balance: Balance) -> Self { - GenesisConfig { - validators: authorities_validators.clone(), - authorities: authorities_validators.clone(), - balances: authorities_validators.iter().map(|v| (v.clone(), balance)).collect(), - block_time: 30, // 30 second block time. - session_length: 120, // that's 1 hour per session. - sessions_per_era: 24, // 24 hours per era. - bonding_duration: 90, // 90 days per bond. - 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 - candidacy_bond: 1000, // 1000 to become a council candidate - voter_bond: 100, // 100 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. - council_election_voting_period: 7 * 120 * 24, // one week period between possible council elections. - council_term_duration: 180 * 120 * 24, // 180 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. - council_proposal_voting_period: 7 * 120 * 24, // 7 day voting period for council members. - } - } - - pub fn genesis_map(&self) -> HashMap, Vec> { - let wasm_runtime = include_bytes!("../wasm/genesis.wasm").to_vec(); - vec![ - (session::SessionLength::key(), vec![].and(&self.session_length)), - (session::Validators::key(), vec![].and(&self.validators)), - - (&staking::Intention::len_key()[..], vec![].and(&0u32)), - (&staking::SessionsPerEra::key()[..], vec![].and(&self.sessions_per_era)), - (&staking::CurrentEra::key()[..], vec![].and(&0u64)), - - (democracy::LaunchPeriod::key(), vec![].and(&self.launch_period)), - (democracy::VotingPeriod::key(), vec![].and(&self.voting_period)), - (democracy::MinimumDeposit::key(), vec![].and(&self.minimum_deposit)), - - (council::CandidacyBond::key(), vec![].and(&self.candidacy_bond)), - (council::VotingBond::key(), vec![].and(&self.voter_bond)), - (council::PresentSlashPerVoter::key(), vec![].and(&self.present_slash_per_voter)), - (council::CarryCount::key(), vec![].and(&self.carry_count)), - (council::PresentationDuration::key(), vec![].and(&self.presentation_duration)), - (council::VotingPeriod::key(), vec![].and(&self.council_election_voting_period)), - (council::TermDuration::key(), vec![].and(&self.council_term_duration)), - (council::DesiredSeats::key(), vec![].and(&self.desired_seats)), - (council::InactiveGracePeriod::key(), vec![].and(&self.inactive_grace_period)), - - (council_vote::CooloffPeriod::key(), vec![].and(&self.cooloff_period)), - (council_vote::VotingPeriod::key(), vec![].and(&self.council_proposal_voting_period)) - ].into_iter() - .map(|(k, v)| (k.into(), v)) - .chain(self.balances.iter() - .map(|&(account, balance)| (staking::FreeBalanceOf::key_for(&account), vec![].and(&balance))) - ) - .map(|(k, v)| (twox_128(&k[..])[..].to_vec(), v.to_vec())) - .chain(vec![ - (system::CODE.to_vec(), wasm_runtime), - (consensus::AUTHORITY_COUNT[..].into(), vec![].and(&(self.authorities.len() as u32))), - ].into_iter()) - .chain(self.authorities.iter() - .enumerate() - .map(|(i, account)| ((i as u32).to_keyed_vec(consensus::AUTHORITY_AT), vec![].and(account))) - ) - .collect() - } -} - -pub fn additional_storage_with_genesis(genesis_block: &Block) -> HashMap, Vec> { - use codec::Slicable; - map![ - system::BlockHashAt::key_for(&0) => genesis_block.header.blake2_256().encode() - ] -} diff --git a/demo/runtime/src/lib.rs b/demo/runtime/src/lib.rs index 9dc1ca8a13f0f..e6ac39823bdf4 100644 --- a/demo/runtime/src/lib.rs +++ b/demo/runtime/src/lib.rs @@ -18,30 +18,150 @@ #![cfg_attr(not(feature = "std"), no_std)] -#[allow(unused_imports)] #[macro_use] extern crate substrate_runtime_std as rstd; -#[macro_use] extern crate substrate_runtime_io as runtime_io; -#[macro_use] extern crate substrate_runtime_support as runtime_support; -#[cfg(any(feature = "std", test))] extern crate substrate_keyring as keyring; +#[macro_use] +extern crate substrate_runtime_io as runtime_io; -#[cfg(feature = "std")] #[macro_use] extern crate serde_derive; -#[cfg(feature = "std")] extern crate serde; +#[macro_use] +extern crate substrate_runtime_support as runtime_support; -#[cfg(feature = "std")] extern crate rustc_hex; +#[macro_use] +extern crate substrate_runtime_primitives as runtime_primitives; -extern crate substrate_codec as codec; -#[cfg(feature = "std")] #[macro_use] extern crate substrate_primitives as primitives; +extern crate substrate_runtime_std as rstd; +extern crate substrate_runtime_consensus as consensus; +extern crate substrate_runtime_council as council; +extern crate substrate_runtime_democracy as democracy; +extern crate substrate_runtime_executive as executive; +extern crate substrate_runtime_session as session; +extern crate substrate_runtime_staking as staking; +extern crate substrate_runtime_system as system; +extern crate substrate_runtime_timestamp as timestamp; extern crate demo_primitives; -#[cfg(test)] #[macro_use] extern crate hex_literal; +use rstd::prelude::*; +use runtime_io::BlakeTwo256; +use demo_primitives::{AccountId, Balance, BlockNumber, Hash, Index, SessionKey, Signature}; +use runtime_primitives::generic; +use runtime_primitives::traits::{Identity, HasPublicAux}; -extern crate integer_sqrt; +#[cfg(any(feature = "std", test))] +pub use runtime_primitives::BuildExternalities; -#[macro_use] pub mod dispatch; +/// Concrete runtime type used to parameterize the various modules. +pub struct Concrete; -pub mod safe_mix; -pub mod block; -pub mod transaction; -pub mod runtime; -pub mod api; +impl HasPublicAux for Concrete { + type PublicAux = AccountId; +} -#[cfg(feature = "std")] pub mod genesismap; +impl timestamp::Trait for Concrete { + type Value = u64; +} + +/// Timestamp module for this concrete runtime. +pub type Timestamp = timestamp::Module; + +impl consensus::Trait for Concrete { + type SessionKey = SessionKey; +} + +/// Consensus module for this concrete runtime. +pub type Consensus = consensus::Module; + +impl system::Trait for Concrete { + type Index = Index; + type BlockNumber = BlockNumber; + type Hash = Hash; + type Hashing = BlakeTwo256; + type Digest = generic::Digest>; + type AccountId = AccountId; + type Header = generic::Header>; +} + +/// System module for this concrete runtime. +pub type System = system::Module; + +impl session::Trait for Concrete { + type PublicAux = ::PublicAux; + type ConvertAccountIdToSessionKey = Identity; +} + +/// Session module for this concrete runtime. +pub type Session = session::Module; + +impl staking::Trait for Concrete { + type Balance = Balance; + type DetermineContractAddress = BlakeTwo256; +} + +/// Staking module for this concrete runtime. +pub type Staking = staking::Module; + +impl democracy::Trait for Concrete { + type Proposal = PrivCall; +} + +/// Democracy module for this concrete runtime. +pub type Democracy = democracy::Module; + +impl council::Trait for Concrete {} + +/// Council module for this concrete runtime. +pub type Council = council::Module; +/// Council voting module for this concrete runtime. +pub type CouncilVoting = council::voting::Module; + +impl_outer_dispatch! { + pub enum Call where aux: ::PublicAux { + Session = 1, + Staking = 2, + Timestamp = 3, + Democracy = 5, + Council = 6, + CouncilVoting = 7, + } + + pub enum PrivCall { + Consensus = 0, + Session = 1, + Staking = 2, + Democracy = 5, + Council = 6, + CouncilVoting = 7, + } +} + +/// Block header type as expected by this runtime. +pub type Header = generic::Header>; +/// Block type as expected by this runtime. +pub type Block = generic::Block, AccountId, Index, Call, Signature>; +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +/// Extrinsic type as expected by this runtime. +pub type Extrinsic = generic::Extrinsic; +/// Executive: handles dispatch to the various modules. +pub type Executive = executive::Executive; + +impl_outer_config! { + pub struct GenesisConfig for Concrete { + ConsensusConfig => consensus, + SystemConfig => system, + SessionConfig => session, + StakingConfig => staking, + DemocracyConfig => democracy, + CouncilConfig => council, + } +} + +pub mod api { + impl_stubs!( + authorities => |()| super::Consensus::authorities(), + initialise_block => |header| super::Executive::initialise_block(&header), + apply_extrinsic => |extrinsic| super::Executive::apply_extrinsic(extrinsic), + execute_block => |block| super::Executive::execute_block(block), + finalise_block => |()| super::Executive::finalise_block(), + validator_count => |()| super::Session::validator_count(), + validators => |()| super::Session::validators() + ); +} diff --git a/demo/runtime/src/runtime/consensus.rs b/demo/runtime/src/runtime/consensus.rs deleted file mode 100644 index 8d631d0bb8e9c..0000000000000 --- a/demo/runtime/src/runtime/consensus.rs +++ /dev/null @@ -1,51 +0,0 @@ -// 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 . - -//! Conensus module for runtime; manages the authority set ready for the native code. - -use rstd::prelude::*; -use runtime_support::storage::unhashed::StorageVec; -use demo_primitives::SessionKey; - -pub const AUTHORITY_AT: &'static[u8] = b":auth:"; -pub const AUTHORITY_COUNT: &'static[u8] = b":auth:len"; - -struct AuthorityStorageVec {} -impl StorageVec for AuthorityStorageVec { - type Item = SessionKey; - const PREFIX: &'static[u8] = AUTHORITY_AT; -} - -/// Get the current set of authorities. These are the session keys. -pub fn authorities() -> Vec { - AuthorityStorageVec::items() -} - -pub mod internal { - use super::*; - - /// Set the current set of authorities' session keys. - /// - /// Called by `next_session` only. - pub fn set_authorities(authorities: &[SessionKey]) { - AuthorityStorageVec::set_items(authorities); - } - - /// Set a single authority by index. - pub fn set_authority(index: u32, key: &SessionKey) { - AuthorityStorageVec::set_item(index, key); - } -} diff --git a/demo/runtime/src/runtime/council.rs b/demo/runtime/src/runtime/council.rs deleted file mode 100644 index 6f9c155839186..0000000000000 --- a/demo/runtime/src/runtime/council.rs +++ /dev/null @@ -1,1312 +0,0 @@ -// 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 . - -//! Council system: Handles the voting in and maintenance of council members. - -use rstd::prelude::*; -use codec::KeyedVec; -use runtime_support::{StorageMap, StorageValue}; -use demo_primitives::{AccountId, Hash, BlockNumber}; -use runtime::{staking, system, session}; -use runtime::democracy::PrivPass; -use runtime::staking::{PublicPass, Balance}; - -// no polynomial attacks: -// -// all unbonded public operations should be constant time. -// all other public operations must be linear time in terms of prior public operations and: -// - those "valid" ones that cost nothing be limited to a constant number per single protected operation -// - the rest costing the same order as the computational complexity -// all protected operations must complete in at most O(public operations) -// -// we assume "beneficial" transactions will have the same access as attack transactions. -// -// any storage requirements should be bonded by the same order as the volume. - -// public operations: -// - express approvals (you pay in a "voter" bond the first time you do this; O(1); one extra DB entry, one DB change) -// - remove active voter (you get your "voter" bond back; O(1); one fewer DB entry, one DB change) -// - remove inactive voter (either you or the target is removed; if the target, you get their "voter" bond back; O(1); one fewer DB entry, one DB change) -// - submit candidacy (you pay a "candidate" bond; O(1); one extra DB entry, two DB changes) -// - present winner/runner-up (you may pay a "presentation" bond of O(voters) if the presentation is invalid; O(voters) compute; ) -// protected operations: -// - remove candidacy (remove all votes for a candidate) (one fewer DB entry, two DB changes) - -// to avoid a potentially problematic case of not-enough approvals prior to voting causing a -// back-to-back votes that have no way of ending, then there's a forced grace period between votes. -// to keep the system as stateless as possible (making it a bit easier to reason about), we just -// restrict when votes can begin to blocks that lie on boundaries (`voting_period`). - -// for an approval vote of C councilers: - -// top K runners-up are maintained between votes. all others are discarded. -// - candidate removed & bond returned when elected. -// - candidate removed & bond burned when discarded. - -// at the point that the vote ends (), all voters' balances are snapshotted. - -// for B blocks following, there's a counting period whereby each of the candidates that believe -// they fall in the top K+C voted can present themselves. they get the total stake -// recorded (based on the snapshot); an ordered list is maintained (the leaderboard). Noone may -// present themselves that, if elected, would result in being included twice on the council -// (important since existing councilers will will have their approval votes as it may be that they -// don't get removed), nor if existing presenters would mean they're not in the top K+C. - -// following B blocks, the top C candidates are elected and have their bond returned. the top C -// candidates and all other candidates beyond the top C+K are cleared. - -// vote-clearing happens lazily; for an approval to count, the most recent vote at the time of the -// voter's most recent vote must be no later than the most recent vote at the time that the -// candidate in the approval position was registered there. as candidates are removed from the -// register and others join in their place, this prevent an approval meant for an earlier candidate -// being used to elect a new candidate. - -// the candidate list increases as needed, but the contents (though not really the capacity) reduce -// after each vote as all but K entries are cleared. newly registering candidates must use cleared -// entries before they increase the capacity. - -pub type VoteIndex = u32; - -storage_items! { - // parameters - // How much should be locked up in order to submit one's candidacy. - pub CandidacyBond get(candidacy_bond): b"cou:cbo" => required Balance; - // How much should be locked up in order to be able to submit votes. - pub VotingBond get(voting_bond): b"cou:vbo" => required Balance; - // The punishment, per voter, if you provide an invalid presentation. - pub PresentSlashPerVoter get(present_slash_per_voter): b"cou:pss" => required Balance; - // How many runners-up should have their approvals persist until the next vote. - pub CarryCount get(carry_count): b"cou:cco" => required u32; - // How long to give each top candidate to present themselves after the vote ends. - pub PresentationDuration get(presentation_duration): b"cou:pdu" => required BlockNumber; - // How many votes need to go by after a voter's last vote before they can be reaped if their - // approvals are moot. - pub InactiveGracePeriod get(inactivity_grace_period): b"cou:vgp" => required VoteIndex; - // How often (in blocks) to check for new votes. - pub VotingPeriod get(voting_period): b"cou:per" => required BlockNumber; - // How long each position is active for. - pub TermDuration get(term_duration): b"cou:trm" => required BlockNumber; - // Number of accounts that should be sitting on the council. - pub DesiredSeats get(desired_seats): b"cou:sts" => required u32; - - // permanent state (always relevant, changes only at the finalisation of voting) - // The current council. When there's a vote going on, this should still be used for executive - // matters. - pub ActiveCouncil get(active_council): b"cou:act" => default Vec<(AccountId, BlockNumber)>; - // The total number of votes that have happened or are in progress. - pub VoteCount get(vote_index): b"cou:vco" => default VoteIndex; - - // persistent state (always relevant, changes constantly) - // The last cleared vote index that this voter was last active at. - pub ApprovalsOf get(approvals_of): b"cou:apr" => default map [ AccountId => Vec ]; - // The vote index and list slot that the candidate `who` was registered or `None` if they are not - // currently registered. - pub RegisterInfoOf get(candidate_reg_info): b"cou:reg" => map [ AccountId => (VoteIndex, u32) ]; - // The last cleared vote index that this voter was last active at. - pub LastActiveOf get(voter_last_active): b"cou:lac" => map [ AccountId => VoteIndex ]; - // The present voter list. - pub Voters get(voters): b"cou:vrs" => default Vec; - // The present candidate list. - pub Candidates get(candidates): b"cou:can" => default Vec; // has holes - pub CandidateCount get(candidate_count): b"cou:cnc" => default u32; - - // temporary state (only relevant during finalisation/presentation) - // The accounts holding the seats that will become free on the next tally. - pub NextFinalise get(next_finalise): b"cou:nxt" => (BlockNumber, u32, Vec); - // The stakes as they were at the point that the vote ended. - pub SnapshotedStakes get(snapshoted_stakes): b"cou:sss" => required Vec; - // Get the leaderboard if we;re in the presentation phase. - pub Leaderboard get(leaderboard): b"cou:win" => Vec<(Balance, AccountId)>; // ORDERED low -> high -} - -/// True if we're currently in a presentation period. -pub fn presentation_active() -> bool { - NextFinalise::exists() -} - -/// If `who` a candidate at the moment? -pub fn is_a_candidate(who: &AccountId) -> bool { - RegisterInfoOf::exists(who) -} - -/// Determine the block that a vote can happen on which is no less than `n`. -pub fn next_vote_from(n: BlockNumber) -> BlockNumber { - let voting_period = voting_period(); - (n + voting_period - 1) / voting_period * voting_period -} - -/// The block number on which the tally for the next election will happen. `None` only if the -/// desired seats of the council is zero. -pub fn next_tally() -> Option { - let desired_seats = desired_seats(); - if desired_seats == 0 { - None - } else { - let c = active_council(); - let (next_possible, count, coming) = - if let Some((tally_end, comers, leavers)) = next_finalise() { - // if there's a tally in progress, then next tally can begin immediately afterwards - (tally_end, c.len() - leavers.len() + comers as usize, comers) - } else { - (system::block_number(), c.len(), 0) - }; - if count < desired_seats as usize { - Some(next_possible) - } else { - // next tally begins once enough council members expire to bring members below desired. - if desired_seats <= coming { - // the entire amount of desired seats is less than those new members - we'll have - // to wait until they expire. - Some(next_possible + term_duration()) - } else { - Some(c[c.len() - (desired_seats - coming) as usize].1) - } - }.map(next_vote_from) - } -} - -impl_dispatch! { - pub mod public; - fn set_approvals(votes: Vec, index: VoteIndex) = 0; - fn reap_inactive_voter(signed_index: u32, who: AccountId, who_index: u32, assumed_vote_index: VoteIndex) = 1; - fn retract_voter(index: u32) = 2; - fn submit_candidacy(slot: u32) = 3; - fn present_winner(candidate: AccountId, total: Balance, index: VoteIndex) = 4; -} - -impl<'a> public::Dispatch for PublicPass<'a> { - /// Set candidate approvals. Approval slots stay valid as long as candidates in those slots - /// are registered. - fn set_approvals(self, votes: Vec, index: VoteIndex) { - assert!(!presentation_active()); - assert_eq!(index, vote_index()); - if !LastActiveOf::exists(*self) { - // not yet a voter - deduct bond. - staking::internal::reserve_balance(&self, voting_bond()); - Voters::put({ - let mut v = Voters::get(); - v.push(self.clone()); - v - }); - } - ApprovalsOf::insert(*self, votes); - LastActiveOf::insert(*self, index); - } - - /// Remove a voter. For it not to be a bond-consuming no-op, all approved candidate indices - /// must now be either unregistered or registered to a candidate that registered the slot after - /// the voter gave their last approval set. - /// - /// May be called by anyone. Returns the voter deposit to `signed`. - fn reap_inactive_voter(self, signed_index: u32, who: AccountId, who_index: u32, assumed_vote_index: VoteIndex) { - assert!(!presentation_active(), "cannot reap during presentation period"); - assert!(voter_last_active(*self).is_some(), "reaper must be a voter"); - let last_active = voter_last_active(&who).expect("target for inactivity cleanup must be active"); - assert!(assumed_vote_index == vote_index(), "vote index not current"); - assert!(last_active < assumed_vote_index - inactivity_grace_period(), "cannot reap during grace perid"); - let voters = voters(); - let signed_index = signed_index as usize; - let who_index = who_index as usize; - assert!(signed_index < voters.len() && voters[signed_index] == *self, "bad reporter index"); - assert!(who_index < voters.len() && voters[who_index] == who, "bad target index"); - - // will definitely kill one of signed or who now. - - let valid = !approvals_of(&who).iter() - .zip(candidates().iter()) - .any(|(&appr, addr)| - appr && - *addr != AccountId::default() && - candidate_reg_info(addr) - .expect("all items in candidates list are registered").0 <= last_active); - - remove_voter( - if valid { &who } else { &self }, - if valid { who_index } else { signed_index }, - voters - ); - if valid { - staking::internal::transfer_reserved_balance(&who, &self, voting_bond()); - } else { - staking::internal::slash_reserved(&self, voting_bond()); - } - } - - /// Remove a voter. All votes are cancelled and the voter deposit is returned. - fn retract_voter(self, index: u32) { - assert!(!presentation_active(), "cannot retract when presenting"); - assert!(LastActiveOf::exists(*self), "cannot retract non-voter"); - let voters = voters(); - let index = index as usize; - assert!(index < voters.len(), "retraction index invalid"); - assert!(voters[index] == *self, "retraction index mismatch"); - remove_voter(&self, index, voters); - staking::internal::unreserve_balance(&self, voting_bond()); - } - - /// Submit oneself for candidacy. - /// - /// Account must have enough transferrable funds in it to pay the bond. - fn submit_candidacy(self, slot: u32) { - assert!(!is_a_candidate(&self), "duplicate candidate submission"); - assert!(staking::internal::deduct_unbonded(&self, candidacy_bond()), "candidate has not enough funds"); - - let slot = slot as usize; - let count = CandidateCount::get() as usize; - let candidates = Candidates::get(); - assert!( - (slot == count && count == candidates.len()) || - (slot < candidates.len() && candidates[slot] == AccountId::default()), - "invalid candidate slot" - ); - - let mut candidates = candidates; - if slot == candidates.len() { - candidates.push(self.clone()); - } else { - candidates[slot] = self.clone(); - } - Candidates::put(candidates); - CandidateCount::put(count as u32 + 1); - RegisterInfoOf::insert(*self, (vote_index(), slot as u32)); - } - - /// Claim that `signed` is one of the top carry_count() + current_vote().1 candidates. - /// Only works if the `block_number >= current_vote().0` and `< current_vote().0 + presentation_duration()`` - /// `signed` should have at least - fn present_winner(self, candidate: AccountId, total: Balance, index: VoteIndex) { - assert_eq!(index, vote_index(), "index not current"); - let (_, _, expiring) = NextFinalise::get() - .expect("cannot present outside of presentation period"); - let stakes = SnapshotedStakes::get(); - let voters = Voters::get(); - let bad_presentation_punishment = present_slash_per_voter() * voters.len() as Balance; - assert!(staking::can_slash(&self, bad_presentation_punishment), "presenter must have sufficient slashable funds"); - - let mut leaderboard = leaderboard().expect("leaderboard must exist while present phase active"); - assert!(total > leaderboard[0].0, "candidate not worthy of leaderboard"); - - if let Some(p) = active_council().iter().position(|&(ref c, _)| c == &candidate) { - assert!(p < expiring.len(), "candidate must not form a duplicated member if elected"); - } - - let (registered_since, candidate_index): (VoteIndex, u32) = - RegisterInfoOf::get(candidate).expect("presented candidate must be current"); - let actual_total = voters.iter() - .zip(stakes.iter()) - .filter_map(|(voter, stake)| - match voter_last_active(voter) { - Some(b) if b >= registered_since => - approvals_of(voter).get(candidate_index as usize) - .and_then(|approved| if *approved { Some(*stake) } else { None }), - _ => None, - }) - .sum(); - let dupe = leaderboard.iter().find(|&&(_, ref c)| c == &candidate).is_some(); - if total == actual_total && !dupe { - // insert into leaderboard - leaderboard[0] = (total, candidate.clone()); - leaderboard.sort_by_key(|&(t, _)| t); - Leaderboard::put(leaderboard); - } else { - staking::internal::slash(&self, bad_presentation_punishment); - } - } -} - -impl_dispatch! { - pub mod privileged; - fn set_desired_seats(count: u32) = 0; - fn remove_member(who: AccountId) = 1; - fn set_presentation_duration(count: BlockNumber) = 2; - fn set_term_duration(count: BlockNumber) = 3; -} - -impl privileged::Dispatch for PrivPass { - /// Set the desired member count; if lower than the current count, then seats will not be up - /// election when they expire. If more, then a new vote will be started if one is not already - /// in progress. - fn set_desired_seats(self, count: u32) { - DesiredSeats::put(count); - } - - /// Remove a particular member. A tally will happen instantly (if not already in a presentation - /// period) to fill the seat if removal means that the desired members are not met. - /// This is effective immediately. - fn remove_member(self, who: AccountId) { - let new_council: Vec<(AccountId, BlockNumber)> = active_council() - .into_iter() - .filter(|i| i.0 != who) - .collect(); - ActiveCouncil::put(new_council); - } - - /// Set the presentation duration. If there is current a vote being presented for, will - /// invoke `finalise_vote`. - fn set_presentation_duration(self, count: BlockNumber) { - PresentationDuration::put(count); - } - - /// Set the presentation duration. If there is current a vote being presented for, will - /// invoke `finalise_vote`. - fn set_term_duration(self, count: BlockNumber) { - TermDuration::put(count); - } -} - -pub mod internal { - use super::*; - - /// Check there's nothing to do this block - pub fn end_block() { - let block_number = system::block_number(); - if block_number % voting_period() == 0 { - if let Some(number) = next_tally() { - if block_number == number { - start_tally(); - } - } - } - if let Some((number, _, _)) = next_finalise() { - if block_number == number { - finalise_tally(); - } - } - } -} - -/// Remove a voter from the system. Trusts that voters()[index] != voter. -fn remove_voter(voter: &AccountId, index: usize, mut voters: Vec) { - Voters::put({ voters.swap_remove(index); voters }); - ApprovalsOf::remove(voter); - LastActiveOf::remove(voter); -} - -/// Close the voting, snapshot the staking and the number of seats that are actually up for grabs. -fn start_tally() { - let active_council = active_council(); - let desired_seats = desired_seats() as usize; - let number = system::block_number(); - let expiring = active_council.iter().take_while(|i| i.1 == number).map(|i| i.0).collect::>(); - if active_council.len() - expiring.len() < desired_seats { - let empty_seats = desired_seats - (active_council.len() - expiring.len()); - NextFinalise::put((number + presentation_duration(), empty_seats as u32, expiring)); - - let voters = Voters::get(); - let votes = voters.iter().map(staking::balance).collect::>(); - SnapshotedStakes::put(votes); - - // initialise leaderboard. - let leaderboard_size = empty_seats + carry_count() as usize; - Leaderboard::put(vec![(0 as Balance, AccountId::default()); leaderboard_size]); - } -} - -/// Finalise the vote, removing each of the `removals` and inserting `seats` of the most approved -/// candidates in their place. If the total council members is less than the desired membership -/// a new vote is started. -/// Clears all presented candidates, returning the bond of the elected ones. -fn finalise_tally() { - SnapshotedStakes::kill(); - let (_, coming, expiring): (BlockNumber, u32, Vec) = NextFinalise::take() - .expect("finalise can only be called after a tally is started."); - let leaderboard: Vec<(Balance, AccountId)> = Leaderboard::take().unwrap_or_default(); - let new_expiry = system::block_number() + term_duration(); - - // return bond to winners. - let candidacy_bond = candidacy_bond(); - for &(_, ref w) in leaderboard.iter() - .rev() - .take_while(|&&(b, _)| b != 0) - .take(coming as usize) - { - staking::internal::refund(w, candidacy_bond); - } - - // set the new council. - let mut new_council: Vec<_> = active_council() - .into_iter() - .skip(expiring.len()) - .chain(leaderboard.iter() - .rev() - .take_while(|&&(b, _)| b != 0) - .take(coming as usize) - .cloned() - .map(|(_, a)| (a, new_expiry))) - .collect(); - new_council.sort_by_key(|&(_, expiry)| expiry); - ActiveCouncil::put(new_council); - - // clear all except runners-up from candidate list. - let candidates = Candidates::get(); - let mut new_candidates = vec![AccountId::default(); candidates.len()]; // shrink later. - let runners_up = leaderboard.into_iter() - .rev() - .take_while(|&(b, _)| b != 0) - .skip(coming as usize) - .map(|(_, a)| (a, candidate_reg_info(&a).expect("runner up must be registered").1)); - let mut count = 0u32; - for (address, slot) in runners_up { - new_candidates[slot as usize] = address; - count += 1; - } - for (old, new) in candidates.iter().zip(new_candidates.iter()) { - if old != new { - // removed - kill it - RegisterInfoOf::remove(old); - } - } - // discard any superfluous slots. - if let Some(last_index) = new_candidates.iter().rposition(|c| *c != AccountId::default()) { - new_candidates.truncate(last_index + 1); - } - Candidates::put(new_candidates); - CandidateCount::put(count); - VoteCount::put(vote_index() + 1); -} - -#[cfg(test)] -pub mod testing { - use super::*; - use runtime_io::{twox_128, TestExternalities}; - use codec::Joiner; - use runtime::democracy; - - pub fn externalities() -> TestExternalities { - let extras: TestExternalities = map![ - twox_128(CandidacyBond::key()).to_vec() => vec![].and(&9u64), - twox_128(VotingBond::key()).to_vec() => vec![].and(&3u64), - twox_128(PresentSlashPerVoter::key()).to_vec() => vec![].and(&1u64), - twox_128(CarryCount::key()).to_vec() => vec![].and(&2u32), - twox_128(PresentationDuration::key()).to_vec() => vec![].and(&2u64), - twox_128(VotingPeriod::key()).to_vec() => vec![].and(&4u64), - twox_128(TermDuration::key()).to_vec() => vec![].and(&5u64), - twox_128(DesiredSeats::key()).to_vec() => vec![].and(&2u32), - twox_128(InactiveGracePeriod::key()).to_vec() => vec![].and(&1u32) - ]; - democracy::testing::externalities() - .into_iter().chain(extras.into_iter()).collect() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use runtime_io::{with_externalities, twox_128, TestExternalities}; - use codec::{KeyedVec, Joiner}; - use keyring::Keyring::*; - use demo_primitives::AccountId; - use runtime::{staking, session, democracy}; - use super::public::Dispatch; - use super::privileged::Dispatch as PrivDispatch; - - fn new_test_ext() -> TestExternalities { - testing::externalities() - } - - #[test] - fn basic_environment_works() { - let mut t = new_test_ext(); - with_externalities(&mut t, || { - system::testing::set_block_number(1); - assert_eq!(next_vote_from(1), 4); - assert_eq!(next_vote_from(4), 4); - assert_eq!(next_vote_from(5), 8); - assert_eq!(vote_index(), 0); - assert_eq!(candidacy_bond(), 9); - assert_eq!(voting_bond(), 3); - assert_eq!(present_slash_per_voter(), 1); - assert_eq!(presentation_duration(), 2); - assert_eq!(voting_period(), 4); - assert_eq!(term_duration(), 5); - assert_eq!(desired_seats(), 2); - assert_eq!(carry_count(), 2); - - assert_eq!(active_council(), vec![]); - assert_eq!(next_tally(), Some(4)); - assert_eq!(presentation_active(), false); - assert_eq!(next_finalise(), None); - - assert_eq!(candidates(), Vec::::new()); - assert_eq!(is_a_candidate(&Alice), false); - assert_eq!(candidate_reg_info(*Alice), None); - - assert_eq!(voters(), Vec::::new()); - assert_eq!(voter_last_active(*Alice), None); - assert_eq!(approvals_of(*Alice), vec![]); - }); - } - - #[test] - fn simple_candidate_submission_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - assert_eq!(candidates(), Vec::::new()); - assert_eq!(candidate_reg_info(*Alice), None); - assert_eq!(candidate_reg_info(*Bob), None); - assert_eq!(is_a_candidate(&Alice), false); - assert_eq!(is_a_candidate(&Bob), false); - - PublicPass::test(&Alice).submit_candidacy(0); - assert_eq!(candidates(), vec![Alice.to_raw_public()]); - assert_eq!(candidate_reg_info(*Alice), Some((0 as VoteIndex, 0u32))); - assert_eq!(candidate_reg_info(*Bob), None); - assert_eq!(is_a_candidate(&Alice), true); - assert_eq!(is_a_candidate(&Bob), false); - - PublicPass::test(&Bob).submit_candidacy(1); - assert_eq!(candidates(), vec![Alice.to_raw_public(), Bob.into()]); - assert_eq!(candidate_reg_info(*Alice), Some((0 as VoteIndex, 0u32))); - assert_eq!(candidate_reg_info(*Bob), Some((0 as VoteIndex, 1u32))); - assert_eq!(is_a_candidate(&Alice), true); - assert_eq!(is_a_candidate(&Bob), true); - }); - } - - fn new_test_ext_with_candidate_holes() -> TestExternalities { - let mut t = new_test_ext(); - t.insert(twox_128(Candidates::key()).to_vec(), vec![].and(&vec![AccountId::default(), AccountId::default(), Alice.to_raw_public()])); - t.insert(twox_128(CandidateCount::key()).to_vec(), vec![].and(&1u32)); - t.insert(twox_128(&RegisterInfoOf::key_for(*Alice)).to_vec(), vec![].and(&(0 as VoteIndex, 2u32))); - t - } - - #[test] - fn candidate_submission_using_free_slot_should_work() { - let mut t = new_test_ext_with_candidate_holes(); - - with_externalities(&mut t, || { - system::testing::set_block_number(1); - assert_eq!(candidates(), vec![AccountId::default(), AccountId::default(), Alice.to_raw_public()]); - - PublicPass::test(&Bob).submit_candidacy(1); - assert_eq!(candidates(), vec![AccountId::default(), Bob.into(), Alice.to_raw_public()]); - - PublicPass::test(&Charlie).submit_candidacy(0); - assert_eq!(candidates(), vec![Charlie.into(), Bob.into(), Alice.to_raw_public()]); - }); - } - - #[test] - fn candidate_submission_using_alternative_free_slot_should_work() { - let mut t = new_test_ext_with_candidate_holes(); - - with_externalities(&mut t, || { - system::testing::set_block_number(1); - assert_eq!(candidates(), vec![AccountId::default(), AccountId::default(), Alice.into()]); - - PublicPass::test(&Bob).submit_candidacy(0); - assert_eq!(candidates(), vec![Bob.into(), AccountId::default(), Alice.into()]); - - PublicPass::test(&Charlie).submit_candidacy(1); - assert_eq!(candidates(), vec![Bob.to_raw_public(), Charlie.into(), Alice.into()]); - }); - } - - #[test] - #[should_panic(expected = "invalid candidate slot")] - fn candidate_submission_not_using_free_slot_should_panic() { - let mut t = new_test_ext_with_candidate_holes(); - - with_externalities(&mut t, || { - system::testing::set_block_number(1); - PublicPass::test(&Dave).submit_candidacy(3); - }); - } - - #[test] - #[should_panic(expected = "invalid candidate slot")] - fn bad_candidate_slot_submission_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - assert_eq!(candidates(), Vec::::new()); - PublicPass::test(&Alice).submit_candidacy(1); - }); - } - - #[test] - #[should_panic(expected = "invalid candidate slot")] - fn non_free_candidate_slot_submission_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - assert_eq!(candidates(), Vec::::new()); - PublicPass::test(&Alice).submit_candidacy(0); - PublicPass::test(&Bob).submit_candidacy(0); - }); - } - - #[test] - #[should_panic(expected = "duplicate candidate submission")] - fn dupe_candidate_submission_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - assert_eq!(candidates(), Vec::::new()); - PublicPass::test(&Alice).submit_candidacy(0); - PublicPass::test(&Alice).submit_candidacy(1); - }); - } - - #[test] - #[should_panic(expected = "candidate has not enough funds")] - fn poor_candidate_submission_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - assert_eq!(candidates(), Vec::::new()); - PublicPass::test(&One).submit_candidacy(0); - }); - } - - #[test] - fn voting_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - - PublicPass::test(&Eve).submit_candidacy(0); - - PublicPass::test(&Alice).set_approvals(vec![true], 0); - PublicPass::test(&Dave).set_approvals(vec![true], 0); - - assert_eq!(approvals_of(*Alice), vec![true]); - assert_eq!(approvals_of(*Dave), vec![true]); - assert_eq!(voters(), vec![Alice.to_raw_public(), Dave.into()]); - - PublicPass::test(&Bob).submit_candidacy(1); - PublicPass::test(&Charlie).submit_candidacy(2); - - PublicPass::test(&Bob).set_approvals(vec![false, true, true], 0); - PublicPass::test(&Charlie).set_approvals(vec![false, true, true], 0); - - assert_eq!(approvals_of(*Alice), vec![true]); - assert_eq!(approvals_of(*Dave), vec![true]); - assert_eq!(approvals_of(*Bob), vec![false, true, true]); - assert_eq!(approvals_of(*Charlie), vec![false, true, true]); - - assert_eq!(voters(), vec![Alice.to_raw_public(), Dave.into(), Bob.into(), Charlie.into()]); - }); - } - - #[test] - fn resubmitting_voting_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - - PublicPass::test(&Eve).submit_candidacy(0); - PublicPass::test(&Dave).set_approvals(vec![true], 0); - - assert_eq!(approvals_of(*Dave), vec![true]); - - PublicPass::test(&Bob).submit_candidacy(1); - PublicPass::test(&Charlie).submit_candidacy(2); - PublicPass::test(&Dave).set_approvals(vec![true, false, true], 0); - - assert_eq!(approvals_of(*Dave), vec![true, false, true]); - }); - } - - #[test] - fn retracting_voter_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - - PublicPass::test(&Eve).submit_candidacy(0); - PublicPass::test(&Bob).submit_candidacy(1); - PublicPass::test(&Charlie).submit_candidacy(2); - - PublicPass::test(&Alice).set_approvals(vec![true], 0); - PublicPass::test(&Bob).set_approvals(vec![false, true, true], 0); - PublicPass::test(&Charlie).set_approvals(vec![false, true, true], 0); - PublicPass::test(&Dave).set_approvals(vec![true, false, true], 0); - - assert_eq!(voters(), vec![Alice.to_raw_public(), Bob.into(), Charlie.into(), Dave.into()]); - assert_eq!(approvals_of(*Alice), vec![true]); - assert_eq!(approvals_of(*Bob), vec![false, true, true]); - assert_eq!(approvals_of(*Charlie), vec![false, true, true]); - assert_eq!(approvals_of(*Dave), vec![true, false, true]); - - PublicPass::test(&Alice).retract_voter(0); - - assert_eq!(voters(), vec![Dave.to_raw_public(), Bob.into(), Charlie.into()]); - assert_eq!(approvals_of(*Alice), Vec::::new()); - assert_eq!(approvals_of(*Bob), vec![false, true, true]); - assert_eq!(approvals_of(*Charlie), vec![false, true, true]); - assert_eq!(approvals_of(*Dave), vec![true, false, true]); - - PublicPass::test(&Bob).retract_voter(1); - - assert_eq!(voters(), vec![Dave.to_raw_public(), Charlie.into()]); - assert_eq!(approvals_of(*Alice), Vec::::new()); - assert_eq!(approvals_of(*Bob), Vec::::new()); - assert_eq!(approvals_of(*Charlie), vec![false, true, true]); - assert_eq!(approvals_of(*Dave), vec![true, false, true]); - - PublicPass::test(&Charlie).retract_voter(1); - - assert_eq!(voters(), vec![Dave.to_raw_public()]); - assert_eq!(approvals_of(*Alice), Vec::::new()); - assert_eq!(approvals_of(*Bob), Vec::::new()); - assert_eq!(approvals_of(*Charlie), Vec::::new()); - assert_eq!(approvals_of(*Dave), vec![true, false, true]); - }); - } - - #[test] - #[should_panic(expected = "retraction index mismatch")] - fn invalid_retraction_index_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - PublicPass::test(&Charlie).submit_candidacy(0); - PublicPass::test(&Alice).set_approvals(vec![true], 0); - PublicPass::test(&Bob).set_approvals(vec![true], 0); - PublicPass::test(&Alice).retract_voter(1); - }); - } - - #[test] - #[should_panic(expected = "retraction index invalid")] - fn overflow_retraction_index_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - PublicPass::test(&Charlie).submit_candidacy(0); - PublicPass::test(&Alice).set_approvals(vec![true], 0); - PublicPass::test(&Alice).retract_voter(1); - }); - } - - #[test] - #[should_panic(expected = "cannot retract non-voter")] - fn non_voter_retraction_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - PublicPass::test(&Charlie).submit_candidacy(0); - PublicPass::test(&Alice).set_approvals(vec![true], 0); - PublicPass::test(&Bob).retract_voter(0); - }); - } - - #[test] - fn simple_tally_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(4); - assert!(!presentation_active()); - - PublicPass::test(&Bob).submit_candidacy(0); - PublicPass::test(&Eve).submit_candidacy(1); - PublicPass::test(&Bob).set_approvals(vec![true, false], 0); - PublicPass::test(&Eve).set_approvals(vec![false, true], 0); - internal::end_block(); - - system::testing::set_block_number(6); - assert!(presentation_active()); - PublicPass::test(&Dave).present_winner(Bob.into(), 11, 0); - PublicPass::test(&Dave).present_winner(Eve.into(), 41, 0); - assert_eq!(leaderboard(), Some(vec![(0, AccountId::default()), (0, AccountId::default()), (11, Bob.into()), (41, Eve.into())])); - - internal::end_block(); - - assert!(!presentation_active()); - assert_eq!(active_council(), vec![(Eve.to_raw_public(), 11), (Bob.into(), 11)]); - - assert!(!is_a_candidate(&Bob)); - assert!(!is_a_candidate(&Eve)); - assert_eq!(vote_index(), 1); - assert_eq!(voter_last_active(*Bob), Some(0)); - assert_eq!(voter_last_active(*Eve), Some(0)); - }); - } - - #[test] - fn double_presentations_should_be_punished() { - with_externalities(&mut new_test_ext(), || { - assert!(staking::can_slash(&Dave, 10)); - - system::testing::set_block_number(4); - PublicPass::test(&Bob).submit_candidacy(0); - PublicPass::test(&Eve).submit_candidacy(1); - PublicPass::test(&Bob).set_approvals(vec![true, false], 0); - PublicPass::test(&Eve).set_approvals(vec![false, true], 0); - internal::end_block(); - - system::testing::set_block_number(6); - PublicPass::test(&Dave).present_winner(Bob.into(), 11, 0); - PublicPass::test(&Dave).present_winner(Eve.into(), 41, 0); - PublicPass::test(&Dave).present_winner(Eve.into(), 41, 0); - internal::end_block(); - - assert_eq!(active_council(), vec![(Eve.to_raw_public(), 11), (Bob.into(), 11)]); - assert_eq!(staking::balance(&Dave), 38); - }); - } - - #[test] - fn retracting_inactive_voter_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(4); - PublicPass::test(&Bob).submit_candidacy(0); - PublicPass::test(&Bob).set_approvals(vec![true], 0); - internal::end_block(); - - system::testing::set_block_number(6); - PublicPass::test(&Dave).present_winner(Bob.into(), 11, 0); - internal::end_block(); - - system::testing::set_block_number(8); - PublicPass::test(&Eve).submit_candidacy(0); - PublicPass::test(&Eve).set_approvals(vec![true], 1); - internal::end_block(); - - system::testing::set_block_number(10); - PublicPass::test(&Dave).present_winner(Eve.into(), 41, 1); - internal::end_block(); - - PublicPass::test(&Eve).reap_inactive_voter( - voters().iter().position(|&i| i == *Eve).unwrap() as u32, - Bob.into(), voters().iter().position(|&i| i == *Bob).unwrap() as u32, - 2 - ); - - assert_eq!(voters(), vec![Eve.to_raw_public()]); - assert_eq!(approvals_of(*Bob).len(), 0); - assert_eq!(staking::balance(&Bob), 17); - assert_eq!(staking::balance(&Eve), 53); - }); - } - - #[test] - #[should_panic(expected = "candidate must not form a duplicated member if elected")] - fn presenting_for_double_election_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(4); - PublicPass::test(&Bob).submit_candidacy(0); - PublicPass::test(&Bob).set_approvals(vec![true], 0); - internal::end_block(); - - system::testing::set_block_number(6); - PublicPass::test(&Dave).present_winner(Bob.into(), 11, 0); - internal::end_block(); - - system::testing::set_block_number(8); - PublicPass::test(&Bob).submit_candidacy(0); - PublicPass::test(&Bob).set_approvals(vec![true], 1); - internal::end_block(); - - system::testing::set_block_number(10); - PublicPass::test(&Dave).present_winner(Bob.into(), 11, 1); - }); - } - - #[test] - fn retracting_inactive_voter_with_other_candidates_in_slots_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(4); - PublicPass::test(&Bob).submit_candidacy(0); - PublicPass::test(&Bob).set_approvals(vec![true], 0); - internal::end_block(); - - system::testing::set_block_number(6); - PublicPass::test(&Dave).present_winner(Bob.into(), 11, 0); - internal::end_block(); - - system::testing::set_block_number(8); - PublicPass::test(&Eve).submit_candidacy(0); - PublicPass::test(&Eve).set_approvals(vec![true], 1); - internal::end_block(); - - system::testing::set_block_number(10); - PublicPass::test(&Dave).present_winner(Eve.into(), 41, 1); - internal::end_block(); - - system::testing::set_block_number(11); - PublicPass::test(&Alice).submit_candidacy(0); - - PublicPass::test(&Eve).reap_inactive_voter( - voters().iter().position(|&i| i == *Eve).unwrap() as u32, - Bob.into(), voters().iter().position(|&i| i == *Bob).unwrap() as u32, - 2 - ); - - assert_eq!(voters(), vec![Eve.to_raw_public()]); - assert_eq!(approvals_of(*Bob).len(), 0); - assert_eq!(staking::balance(&Bob), 17); - assert_eq!(staking::balance(&Eve), 53); - }); - } - - #[test] - #[should_panic(expected = "bad reporter index")] - fn retracting_inactive_voter_with_bad_reporter_index_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(4); - PublicPass::test(&Bob).submit_candidacy(0); - PublicPass::test(&Bob).set_approvals(vec![true], 0); - internal::end_block(); - - system::testing::set_block_number(6); - PublicPass::test(&Dave).present_winner(Bob.into(), 8, 0); - internal::end_block(); - - system::testing::set_block_number(8); - PublicPass::test(&Eve).submit_candidacy(0); - PublicPass::test(&Eve).set_approvals(vec![true], 1); - internal::end_block(); - - system::testing::set_block_number(10); - PublicPass::test(&Dave).present_winner(Eve.into(), 38, 1); - internal::end_block(); - - PublicPass::test(&Bob).reap_inactive_voter( - 42, - Bob.into(), voters().iter().position(|&i| i == *Bob).unwrap() as u32, - 2 - ); - }); - } - - #[test] - #[should_panic(expected = "bad target index")] - fn retracting_inactive_voter_with_bad_target_index_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(4); - PublicPass::test(&Bob).submit_candidacy(0); - PublicPass::test(&Bob).set_approvals(vec![true], 0); - internal::end_block(); - - system::testing::set_block_number(6); - PublicPass::test(&Dave).present_winner(Bob.into(), 8, 0); - internal::end_block(); - - system::testing::set_block_number(8); - PublicPass::test(&Eve).submit_candidacy(0); - PublicPass::test(&Eve).set_approvals(vec![true], 1); - internal::end_block(); - - system::testing::set_block_number(10); - PublicPass::test(&Dave).present_winner(Eve.into(), 38, 1); - internal::end_block(); - - PublicPass::test(&Bob).reap_inactive_voter( - voters().iter().position(|&i| i == *Bob).unwrap() as u32, - Bob.into(), 42, - 2 - ); - }); - } - - #[test] - fn attempting_to_retract_active_voter_should_slash_reporter() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(4); - PublicPass::test(&Bob).submit_candidacy(0); - PublicPass::test(&Charlie).submit_candidacy(1); - PublicPass::test(&Dave).submit_candidacy(2); - PublicPass::test(&Eve).submit_candidacy(3); - PublicPass::test(&Bob).set_approvals(vec![true, false, false, false], 0); - PublicPass::test(&Charlie).set_approvals(vec![false, true, false, false], 0); - PublicPass::test(&Dave).set_approvals(vec![false, false, true, false], 0); - PublicPass::test(&Eve).set_approvals(vec![false, false, false, true], 0); - internal::end_block(); - - system::testing::set_block_number(6); - PublicPass::test(&Dave).present_winner(Bob.into(), 11, 0); - PublicPass::test(&Dave).present_winner(Charlie.into(), 21, 0); - PublicPass::test(&Dave).present_winner(Dave.into(), 31, 0); - PublicPass::test(&Dave).present_winner(Eve.into(), 41, 0); - internal::end_block(); - - system::testing::set_block_number(8); - PrivPass::test().set_desired_seats(3); - internal::end_block(); - - system::testing::set_block_number(10); - PublicPass::test(&Dave).present_winner(Bob.into(), 11, 1); - PublicPass::test(&Dave).present_winner(Charlie.into(), 21, 1); - internal::end_block(); - - PublicPass::test(&Dave).reap_inactive_voter( - voters().iter().position(|&i| i == *Dave).unwrap() as u32, - Bob.into(), voters().iter().position(|&i| i == *Bob).unwrap() as u32, - 2 - ); - - assert_eq!(voters(), vec![Bob.to_raw_public(), Charlie.into(), Eve.into()]); - assert_eq!(approvals_of(*Dave).len(), 0); - assert_eq!(staking::balance(&Dave), 37); - }); - } - - #[test] - #[should_panic(expected = "reaper must be a voter")] - fn attempting_to_retract_inactive_voter_by_nonvoter_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(4); - PublicPass::test(&Bob).submit_candidacy(0); - PublicPass::test(&Bob).set_approvals(vec![true], 0); - internal::end_block(); - - system::testing::set_block_number(6); - PublicPass::test(&Dave).present_winner(Bob.into(), 11, 0); - internal::end_block(); - - system::testing::set_block_number(8); - PublicPass::test(&Eve).submit_candidacy(0); - PublicPass::test(&Eve).set_approvals(vec![true], 1); - internal::end_block(); - - system::testing::set_block_number(10); - PublicPass::test(&Dave).present_winner(Eve.into(), 41, 1); - internal::end_block(); - - PublicPass::test(&Dave).reap_inactive_voter( - 0, - Bob.into(), voters().iter().position(|&i| i == *Bob).unwrap() as u32, - 2 - ); - }); - } - - #[test] - #[should_panic(expected = "candidate not worthy of leaderboard")] - fn presenting_loser_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(4); - PublicPass::test(&Alice).submit_candidacy(0); - PublicPass::test(&Ferdie).set_approvals(vec![true], 0); - PublicPass::test(&Bob).submit_candidacy(1); - PublicPass::test(&Bob).set_approvals(vec![false, true], 0); - PublicPass::test(&Charlie).submit_candidacy(2); - PublicPass::test(&Charlie).set_approvals(vec![false, false, true], 0); - PublicPass::test(&Dave).submit_candidacy(3); - PublicPass::test(&Dave).set_approvals(vec![false, false, false, true], 0); - PublicPass::test(&Eve).submit_candidacy(4); - PublicPass::test(&Eve).set_approvals(vec![false, false, false, false, true], 0); - internal::end_block(); - - system::testing::set_block_number(6); - PublicPass::test(&Dave).present_winner(Alice.into(), 60, 0); - PublicPass::test(&Dave).present_winner(Charlie.into(), 21, 0); - PublicPass::test(&Dave).present_winner(Dave.into(), 31, 0); - PublicPass::test(&Dave).present_winner(Eve.into(), 41, 0); - PublicPass::test(&Dave).present_winner(Bob.into(), 11, 0); - }); - } - - #[test] - fn presenting_loser_first_should_not_matter() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(4); - PublicPass::test(&Alice).submit_candidacy(0); - PublicPass::test(&Ferdie).set_approvals(vec![true], 0); - PublicPass::test(&Bob).submit_candidacy(1); - PublicPass::test(&Bob).set_approvals(vec![false, true], 0); - PublicPass::test(&Charlie).submit_candidacy(2); - PublicPass::test(&Charlie).set_approvals(vec![false, false, true], 0); - PublicPass::test(&Dave).submit_candidacy(3); - PublicPass::test(&Dave).set_approvals(vec![false, false, false, true], 0); - PublicPass::test(&Eve).submit_candidacy(4); - PublicPass::test(&Eve).set_approvals(vec![false, false, false, false, true], 0); - internal::end_block(); - - system::testing::set_block_number(6); - PublicPass::test(&Dave).present_winner(Bob.into(), 11, 0); - PublicPass::test(&Dave).present_winner(Alice.into(), 60, 0); - PublicPass::test(&Dave).present_winner(Charlie.into(), 21, 0); - PublicPass::test(&Dave).present_winner(Dave.into(), 31, 0); - PublicPass::test(&Dave).present_winner(Eve.into(), 41, 0); - - assert_eq!(leaderboard(), Some(vec![ - (21, Charlie.into()), - (31, Dave.into()), - (41, Eve.into()), - (60, Alice.to_raw_public()) - ])); - }); - } - - #[test] - #[should_panic(expected = "cannot present outside of presentation period")] - fn present_panics_outside_of_presentation_period() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(4); - assert!(!presentation_active()); - PublicPass::test(&Eve).present_winner(Eve.into(), 1, 0); - }); - } - - #[test] - #[should_panic(expected = "index not current")] - fn present_panics_with_invalid_vote_index() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(4); - PublicPass::test(&Bob).submit_candidacy(0); - PublicPass::test(&Eve).submit_candidacy(1); - PublicPass::test(&Bob).set_approvals(vec![true, false], 0); - PublicPass::test(&Eve).set_approvals(vec![false, true], 0); - internal::end_block(); - - system::testing::set_block_number(6); - PublicPass::test(&Dave).present_winner(Bob.into(), 11, 1); - }); - } - - #[test] - #[should_panic(expected = "presenter must have sufficient slashable funds")] - fn present_panics_when_presenter_is_poor() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(4); - assert!(!presentation_active()); - - PublicPass::test(&Alice).submit_candidacy(0); - PublicPass::test(&Eve).submit_candidacy(1); - PublicPass::test(&Bob).set_approvals(vec![true, false], 0); - PublicPass::test(&Eve).set_approvals(vec![false, true], 0); - internal::end_block(); - - system::testing::set_block_number(6); - assert_eq!(staking::balance(&Alice), 1); - PublicPass::test(&Alice).present_winner(Alice.into(), 30, 0); - }); - } - - #[test] - fn invalid_present_tally_should_slash() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(4); - assert!(!presentation_active()); - assert_eq!(staking::balance(&Dave), 40); - - PublicPass::test(&Bob).submit_candidacy(0); - PublicPass::test(&Eve).submit_candidacy(1); - PublicPass::test(&Bob).set_approvals(vec![true, false], 0); - PublicPass::test(&Eve).set_approvals(vec![false, true], 0); - internal::end_block(); - - system::testing::set_block_number(6); - PublicPass::test(&Dave).present_winner(Bob.into(), 80, 0); - - assert_eq!(staking::balance(&Dave), 38); - }); - } - - #[test] - fn runners_up_should_be_kept() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(4); - assert!(!presentation_active()); - - PublicPass::test(&Alice).submit_candidacy(0); - PublicPass::test(&Ferdie).set_approvals(vec![true], 0); - PublicPass::test(&Bob).submit_candidacy(1); - PublicPass::test(&Bob).set_approvals(vec![false, true], 0); - PublicPass::test(&Charlie).submit_candidacy(2); - PublicPass::test(&Charlie).set_approvals(vec![false, false, true], 0); - PublicPass::test(&Dave).submit_candidacy(3); - PublicPass::test(&Dave).set_approvals(vec![false, false, false, true], 0); - PublicPass::test(&Eve).submit_candidacy(4); - PublicPass::test(&Eve).set_approvals(vec![false, false, false, false, true], 0); - - internal::end_block(); - - system::testing::set_block_number(6); - assert!(presentation_active()); - PublicPass::test(&Dave).present_winner(Alice.into(), 60, 0); - assert_eq!(leaderboard(), Some(vec![ - (0, AccountId::default()), - (0, AccountId::default()), - (0, AccountId::default()), - (60, Alice.to_raw_public()) - ])); - PublicPass::test(&Dave).present_winner(Charlie.into(), 21, 0); - PublicPass::test(&Dave).present_winner(Dave.into(), 31, 0); - PublicPass::test(&Dave).present_winner(Eve.into(), 41, 0); - assert_eq!(leaderboard(), Some(vec![ - (21, Charlie.into()), - (31, Dave.into()), - (41, Eve.into()), - (60, Alice.to_raw_public()) - ])); - - internal::end_block(); - - assert!(!presentation_active()); - assert_eq!(active_council(), vec![(Alice.to_raw_public(), 11), (Eve.into(), 11)]); - - assert!(!is_a_candidate(&Alice)); - assert!(!is_a_candidate(&Eve)); - assert!(!is_a_candidate(&Bob)); - assert!(is_a_candidate(&Charlie)); - assert!(is_a_candidate(&Dave)); - assert_eq!(vote_index(), 1); - assert_eq!(voter_last_active(*Bob), Some(0)); - assert_eq!(voter_last_active(*Charlie), Some(0)); - assert_eq!(voter_last_active(*Dave), Some(0)); - assert_eq!(voter_last_active(*Eve), Some(0)); - assert_eq!(voter_last_active(*Ferdie), Some(0)); - assert_eq!(candidate_reg_info(*Charlie), Some((0, 2))); - assert_eq!(candidate_reg_info(*Dave), Some((0, 3))); - }); - } - - #[test] - fn second_tally_should_use_runners_up() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(4); - PublicPass::test(&Alice).submit_candidacy(0); - PublicPass::test(&Ferdie).set_approvals(vec![true], 0); - PublicPass::test(&Bob).submit_candidacy(1); - PublicPass::test(&Bob).set_approvals(vec![false, true], 0); - PublicPass::test(&Charlie).submit_candidacy(2); - PublicPass::test(&Charlie).set_approvals(vec![false, false, true], 0); - PublicPass::test(&Dave).submit_candidacy(3); - PublicPass::test(&Dave).set_approvals(vec![false, false, false, true], 0); - PublicPass::test(&Eve).submit_candidacy(4); - PublicPass::test(&Eve).set_approvals(vec![false, false, false, false, true], 0); - internal::end_block(); - - system::testing::set_block_number(6); - PublicPass::test(&Dave).present_winner(Alice.into(), 60, 0); - PublicPass::test(&Dave).present_winner(Charlie.into(), 21, 0); - PublicPass::test(&Dave).present_winner(Dave.into(), 31, 0); - PublicPass::test(&Dave).present_winner(Eve.into(), 41, 0); - internal::end_block(); - - system::testing::set_block_number(8); - PublicPass::test(&Ferdie).set_approvals(vec![false, false, true, false], 1); - PrivPass::test().set_desired_seats(3); - internal::end_block(); - - system::testing::set_block_number(10); - PublicPass::test(&Dave).present_winner(Charlie.into(), 81, 1); - PublicPass::test(&Dave).present_winner(Dave.into(), 31, 1); - internal::end_block(); - - assert!(!presentation_active()); - assert_eq!(active_council(), vec![(Alice.to_raw_public(), 11), (Eve.into(), 11), (Charlie.into(), 15)]); - - assert!(!is_a_candidate(&Alice)); - assert!(!is_a_candidate(&Bob)); - assert!(!is_a_candidate(&Charlie)); - assert!(!is_a_candidate(&Eve)); - assert!(is_a_candidate(&Dave)); - assert_eq!(vote_index(), 2); - assert_eq!(voter_last_active(*Bob), Some(0)); - assert_eq!(voter_last_active(*Charlie), Some(0)); - assert_eq!(voter_last_active(*Dave), Some(0)); - assert_eq!(voter_last_active(*Eve), Some(0)); - assert_eq!(voter_last_active(*Ferdie), Some(1)); - - assert_eq!(candidate_reg_info(*Dave), Some((0, 3))); - }); - } -} diff --git a/demo/runtime/src/runtime/council_vote.rs b/demo/runtime/src/runtime/council_vote.rs deleted file mode 100644 index 876bb906e7854..0000000000000 --- a/demo/runtime/src/runtime/council_vote.rs +++ /dev/null @@ -1,493 +0,0 @@ -// 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 . - -//! Council voting system. - -use rstd::prelude::*; -use rstd::borrow::Borrow; -use codec::{KeyedVec, Slicable, Input, NonTrivialSlicable}; -use runtime_support::Hashable; -use runtime_support::{StorageValue, StorageMap}; -use demo_primitives::{AccountId, Hash, BlockNumber}; -use runtime::{system, democracy, council}; -use runtime::staking::{PublicPass, Balance}; -use runtime::democracy::PrivPass; -use dispatch::PrivCall as Proposal; - -type ProposalHash = [u8; 32]; - -storage_items! { - pub CooloffPeriod get(cooloff_period): b"cov:cooloff" => required BlockNumber; - pub VotingPeriod get(voting_period): b"cov:period" => required BlockNumber; - pub Proposals get(proposals): b"cov:prs" => default Vec<(BlockNumber, ProposalHash)>; // ordered by expiry. - pub ProposalOf get(proposal_of): b"cov:pro" => map [ ProposalHash => Proposal ]; - pub ProposalVoters get(proposal_voters): b"cov:voters:" => default map [ ProposalHash => Vec ]; - pub CouncilVoteOf get(vote_of): b"cov:vote:" => map [ (ProposalHash, AccountId) => bool ]; - pub VetoedProposal get(veto_of): b"cov:veto:" => map [ ProposalHash => (BlockNumber, Vec) ]; -} - -pub fn is_vetoed>(proposal: B) -> bool { - VetoedProposal::get(proposal.borrow()) - .map(|(expiry, _): (BlockNumber, Vec)| system::block_number() < expiry) - .unwrap_or(false) -} - -fn set_veto_of(proposal: &ProposalHash, expiry: BlockNumber, vetoers: Vec) { - VetoedProposal::insert(proposal, (expiry, vetoers)); -} - -fn kill_veto_of(proposal: &ProposalHash) { - VetoedProposal::remove(proposal); -} - -pub fn will_still_be_councillor_at(who: &AccountId, n: BlockNumber) -> bool { - council::active_council().iter() - .find(|&&(ref a, _)| a == who) - .map(|&(_, expires)| expires > n) - .unwrap_or(false) -} - -pub fn is_councillor(who: &AccountId) -> bool { - council::active_council().iter() - .any(|&(ref a, _)| a == who) -} - -pub fn tally(proposal_hash: &ProposalHash) -> (u32, u32, u32) { - generic_tally(proposal_hash, |w: &AccountId, p: &ProposalHash| CouncilVoteOf::get((*p, *w))) -} - -fn take_tally(proposal_hash: &ProposalHash) -> (u32, u32, u32) { - generic_tally(proposal_hash, |w: &AccountId, p: &ProposalHash| CouncilVoteOf::take((*p, *w))) -} - -fn generic_tally Option>(proposal_hash: &ProposalHash, vote_of: F) -> (u32, u32, u32) { - let c = council::active_council(); - let (approve, reject) = c.iter() - .filter_map(|&(ref a, _)| vote_of(a, proposal_hash)) - .map(|approve| if approve { (1, 0) } else { (0, 1) }) - .fold((0, 0), |(a, b), (c, d)| (a + c, b + d)); - (approve, reject, c.len() as u32 - approve - reject) -} - -fn set_proposals(p: &Vec<(BlockNumber, ProposalHash)>) { - Proposals::put(p); -} - -fn take_proposal_if_expiring_at(n: BlockNumber) -> Option<(Proposal, ProposalHash)> { - let mut proposals = proposals(); - match proposals.first() { - Some(&(expiry, hash)) if expiry == n => { - // yes this is horrible, but fixing it will need substantial work in storage. - set_proposals(&proposals[1..].to_vec()); - let proposal = ProposalOf::take(hash).expect("all queued proposal hashes must have associated proposals"); - Some((proposal, hash)) - } - _ => None, - } -} - -impl_dispatch! { - pub mod public; - fn propose(proposal: Box) = 0; - fn vote(proposal: ProposalHash, approve: bool) = 1; - fn veto(proposal_hash: ProposalHash) = 2; -} - -impl<'a> public::Dispatch for PublicPass<'a> { - fn propose(self, proposal: Box) { - let expiry = system::block_number() + voting_period(); - assert!(will_still_be_councillor_at(&self, expiry)); - - let proposal_hash = proposal.blake2_256(); - - assert!(!is_vetoed(&proposal_hash)); - - let mut proposals = proposals(); - proposals.push((expiry, proposal_hash)); - proposals.sort_by_key(|&(expiry, _)| expiry); - set_proposals(&proposals); - - ProposalOf::insert(proposal_hash, *proposal); - ProposalVoters::insert(proposal_hash, vec![*self]); - CouncilVoteOf::insert((proposal_hash, *self), true); - } - - fn vote(self, proposal: ProposalHash, approve: bool) { - if vote_of((*self, proposal)).is_none() { - let mut voters = proposal_voters(&proposal); - voters.push(*self); - ProposalVoters::insert(proposal, voters); - } - CouncilVoteOf::insert((proposal, *self), approve); - } - - fn veto(self, proposal_hash: ProposalHash) { - assert!(is_councillor(&self), "only councillors may veto council proposals"); - assert!(ProposalVoters::exists(&proposal_hash), "proposal must exist to be vetoed"); - - let mut existing_vetoers = veto_of(&proposal_hash) - .map(|pair| pair.1) - .unwrap_or_else(Vec::new); - let insert_position = existing_vetoers.binary_search(&self) - .expect_err("a councillor may not veto a proposal twice"); - existing_vetoers.insert(insert_position, *self); - set_veto_of(&proposal_hash, system::block_number() + cooloff_period(), existing_vetoers); - - set_proposals(&proposals().into_iter().filter(|&(_, h)| h != proposal_hash).collect::>()); - ProposalVoters::remove(proposal_hash); - ProposalOf::remove(proposal_hash); - for (c, _) in council::active_council() { - CouncilVoteOf::remove((proposal_hash, c)); - } - } -} - -impl_dispatch! { - pub mod privileged; - fn set_cooloff_period(blocks: BlockNumber) = 0; - fn set_voting_period(blocks: BlockNumber) = 1; -} - -impl privileged::Dispatch for PrivPass { - fn set_cooloff_period(self, blocks: BlockNumber) { - CooloffPeriod::put(blocks); - } - - fn set_voting_period(self, blocks: BlockNumber) { - VotingPeriod::put(blocks); - } -} - -pub mod internal { - use super::*; - use runtime::democracy::privileged::Dispatch; - use runtime::democracy::VoteThreshold; - use runtime::democracy::internal::start_referendum; - - pub fn end_block(now: BlockNumber) { - while let Some((proposal, proposal_hash)) = take_proposal_if_expiring_at(now) { - let tally = take_tally(&proposal_hash); - if let &Proposal::Democracy(democracy::privileged::Call::cancel_referendum(ref_index)) = &proposal { - if let (_, 0, 0) = tally { - democracy::internal::cancel_referendum(ref_index); - } - } else { - if tally.0 > tally.1 + tally.2 { - kill_veto_of(&proposal_hash); - match tally { - (_, 0, 0) => start_referendum(proposal, VoteThreshold::SuperMajorityAgainst), - _ => start_referendum(proposal, VoteThreshold::SimpleMajority), - }; - } - } - } - } -} - -#[cfg(test)] -pub mod testing { - use super::*; - use runtime_io::{twox_128, TestExternalities}; - use keyring::Keyring::{Alice, Bob, Charlie}; - use codec::Joiner; - use runtime::{council, democracy}; - - pub fn externalities() -> TestExternalities { - let expiry: BlockNumber = 10; - let extras: TestExternalities = map![ - twox_128(council::ActiveCouncil::key()).to_vec() => vec![].and(&vec![ - (Alice.to_raw_public(), expiry), - (Bob.into(), expiry), - (Charlie.into(), expiry) - ]), - twox_128(CooloffPeriod::key()).to_vec() => vec![].and(&2u64), - twox_128(VotingPeriod::key()).to_vec() => vec![].and(&1u64), - twox_128(democracy::VotingPeriod::key()).to_vec() => vec![].and(&3u64) - ]; - council::testing::externalities() - .into_iter().chain(extras.into_iter()).collect() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use runtime_io::{with_externalities, twox_128, TestExternalities}; - use codec::{KeyedVec, Joiner}; - use keyring::Keyring::{Alice, Bob, Charlie, Dave}; - use demo_primitives::AccountId; - use runtime::democracy::VoteThreshold; - use runtime::{staking, council, democracy}; - use super::public::Dispatch; - use super::privileged::Dispatch as PrivDispatch; - - fn new_test_ext() -> TestExternalities { - testing::externalities() - } - - #[test] - fn basic_environment_works() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - assert_eq!(staking::bonding_duration(), 0); - assert_eq!(cooloff_period(), 2); - assert_eq!(voting_period(), 1); - assert_eq!(will_still_be_councillor_at(&Alice, 1), true); - assert_eq!(will_still_be_councillor_at(&Alice, 10), false); - assert_eq!(will_still_be_councillor_at(&Dave, 10), false); - assert_eq!(is_councillor(&Alice), true); - assert_eq!(is_councillor(&Dave), false); - assert_eq!(proposals(), Vec::<(BlockNumber, ProposalHash)>::new()); - assert_eq!(proposal_voters(ProposalHash::default()), Vec::::new()); - assert_eq!(is_vetoed(&ProposalHash::default()), false); - assert_eq!(vote_of((*Alice, ProposalHash::default())), None); - assert_eq!(tally(&ProposalHash::default()), (0, 0, 3)); - }); - } - - fn sessions_per_era_proposal(value: u64) -> Proposal { - Proposal::Staking(staking::privileged::Call::set_sessions_per_era(value)) - } - - fn bonding_duration_proposal(value: u64) -> Proposal { - Proposal::Staking(staking::privileged::Call::set_bonding_duration(value)) - } - - fn cancel_referendum_proposal(id: u32) -> Proposal { - Proposal::Democracy(democracy::privileged::Call::cancel_referendum(id)) - } - - #[test] - fn referendum_cancellation_should_work_when_unanimous() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let proposal = bonding_duration_proposal(42); - democracy::internal::start_referendum(proposal.clone(), VoteThreshold::SuperMajorityApprove); - assert_eq!(democracy::active_referendums(), vec![(0, 4, proposal, VoteThreshold::SuperMajorityApprove)]); - - let cancellation = cancel_referendum_proposal(0); - let hash = cancellation.blake2_256(); - PublicPass::new(&Alice).propose(Box::new(cancellation)); - PublicPass::new(&Bob).vote(hash, true); - PublicPass::new(&Charlie).vote(hash, true); - assert_eq!(proposals(), vec![(2, hash)]); - internal::end_block(1); - - system::testing::set_block_number(2); - internal::end_block(2); - assert_eq!(democracy::active_referendums(), vec![]); - assert_eq!(staking::bonding_duration(), 0); - }); - } - - #[test] - fn referendum_cancellation_should_fail_when_not_unanimous() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let proposal = bonding_duration_proposal(42); - democracy::internal::start_referendum(proposal.clone(), VoteThreshold::SuperMajorityApprove); - - let cancellation = cancel_referendum_proposal(0); - let hash = cancellation.blake2_256(); - PublicPass::new(&Alice).propose(Box::new(cancellation)); - PublicPass::new(&Bob).vote(hash, true); - PublicPass::new(&Charlie).vote(hash, false); - internal::end_block(1); - - system::testing::set_block_number(2); - internal::end_block(2); - assert_eq!(democracy::active_referendums(), vec![(0, 4, proposal, VoteThreshold::SuperMajorityApprove)]); - }); - } - - #[test] - fn referendum_cancellation_should_fail_when_abstentions() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let proposal = bonding_duration_proposal(42); - democracy::internal::start_referendum(proposal.clone(), VoteThreshold::SuperMajorityApprove); - - let cancellation = cancel_referendum_proposal(0); - let hash = cancellation.blake2_256(); - PublicPass::new(&Alice).propose(Box::new(cancellation)); - PublicPass::new(&Bob).vote(hash, true); - internal::end_block(1); - - system::testing::set_block_number(2); - internal::end_block(2); - assert_eq!(democracy::active_referendums(), vec![(0, 4, proposal, VoteThreshold::SuperMajorityApprove)]); - }); - } - - #[test] - fn veto_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let proposal = bonding_duration_proposal(42); - let hash = proposal.blake2_256(); - PublicPass::new(&Alice).propose(Box::new(proposal.clone())); - PublicPass::new(&Bob).veto(hash); - assert_eq!(proposals().len(), 0); - assert_eq!(democracy::active_referendums().len(), 0); - }); - } - - #[test] - #[should_panic] - fn double_veto_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let proposal = bonding_duration_proposal(42); - let hash = proposal.blake2_256(); - PublicPass::new(&Alice).propose(Box::new(proposal.clone())); - PublicPass::new(&Bob).veto(hash); - - system::testing::set_block_number(3); - PublicPass::new(&Alice).propose(Box::new(proposal.clone())); - PublicPass::new(&Bob).veto(hash); - }); - } - - #[test] - #[should_panic] - fn retry_in_cooloff_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let proposal = bonding_duration_proposal(42); - let hash = proposal.blake2_256(); - PublicPass::new(&Alice).propose(Box::new(proposal.clone())); - PublicPass::new(&Bob).veto(hash); - - system::testing::set_block_number(2); - PublicPass::new(&Alice).propose(Box::new(proposal.clone())); - }); - } - - #[test] - fn retry_after_cooloff_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let proposal = bonding_duration_proposal(42); - let hash = proposal.blake2_256(); - PublicPass::new(&Alice).propose(Box::new(proposal.clone())); - PublicPass::new(&Bob).veto(hash); - - system::testing::set_block_number(3); - PublicPass::new(&Alice).propose(Box::new(proposal.clone())); - PublicPass::new(&Bob).vote(hash, false); - PublicPass::new(&Charlie).vote(hash, true); - internal::end_block(3); - - system::testing::set_block_number(4); - internal::end_block(4); - assert_eq!(proposals().len(), 0); - assert_eq!(democracy::active_referendums(), vec![(0, 7, bonding_duration_proposal(42), VoteThreshold::SimpleMajority)]); - }); - } - - #[test] - fn alternative_double_veto_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let proposal = bonding_duration_proposal(42); - let hash = proposal.blake2_256(); - PublicPass::new(&Alice).propose(Box::new(proposal.clone())); - PublicPass::new(&Bob).veto(hash); - - system::testing::set_block_number(3); - PublicPass::new(&Alice).propose(Box::new(proposal.clone())); - PublicPass::new(&Charlie).veto(hash); - assert_eq!(proposals().len(), 0); - assert_eq!(democracy::active_referendums().len(), 0); - }); - } - - #[test] - fn simple_propose_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let proposal = bonding_duration_proposal(42); - let hash = proposal.blake2_256(); - PublicPass::new(&Alice).propose(Box::new(proposal.clone())); - assert_eq!(proposals().len(), 1); - assert_eq!(proposal_voters(&hash), vec![Alice.to_raw_public()]); - assert_eq!(vote_of((hash, *Alice)), Some(true)); - assert_eq!(tally(&hash), (1, 0, 2)); - }); - } - - #[test] - fn unvoted_proposal_should_expire_without_action() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let proposal = bonding_duration_proposal(42); - PublicPass::new(&Alice).propose(Box::new(proposal.clone())); - assert_eq!(tally(&proposal.blake2_256()), (1, 0, 2)); - internal::end_block(1); - - system::testing::set_block_number(2); - internal::end_block(2); - assert_eq!(proposals().len(), 0); - assert_eq!(democracy::active_referendums().len(), 0); - }); - } - - #[test] - fn unanimous_proposal_should_expire_with_biased_referendum() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let proposal = bonding_duration_proposal(42); - PublicPass::new(&Alice).propose(Box::new(proposal.clone())); - PublicPass::new(&Bob).vote(proposal.blake2_256(), true); - PublicPass::new(&Charlie).vote(proposal.blake2_256(), true); - assert_eq!(tally(&proposal.blake2_256()), (3, 0, 0)); - internal::end_block(1); - - system::testing::set_block_number(2); - internal::end_block(2); - assert_eq!(proposals().len(), 0); - assert_eq!(democracy::active_referendums(), vec![(0, 5, proposal, VoteThreshold::SuperMajorityAgainst)]); - }); - } - - #[test] - fn majority_proposal_should_expire_with_unbiased_referendum() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let proposal = bonding_duration_proposal(42); - PublicPass::new(&Alice).propose(Box::new(proposal.clone())); - PublicPass::new(&Bob).vote(proposal.blake2_256(), true); - PublicPass::new(&Charlie).vote(proposal.blake2_256(), false); - assert_eq!(tally(&proposal.blake2_256()), (2, 1, 0)); - internal::end_block(1); - - system::testing::set_block_number(2); - internal::end_block(2); - assert_eq!(proposals().len(), 0); - assert_eq!(democracy::active_referendums(), vec![(0, 5, proposal, VoteThreshold::SimpleMajority)]); - }); - } - - #[test] - #[should_panic] - fn propose_by_public_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let proposal = bonding_duration_proposal(42); - PublicPass::new(&Dave).propose(Box::new(proposal)); - }); - } -} diff --git a/demo/runtime/src/runtime/democracy.rs b/demo/runtime/src/runtime/democracy.rs deleted file mode 100644 index d3d4a098be647..0000000000000 --- a/demo/runtime/src/runtime/democracy.rs +++ /dev/null @@ -1,623 +0,0 @@ -// 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 . - -//! Democratic system: Handles administration of general stakeholder voting. - -use rstd::prelude::*; -use integer_sqrt::IntegerSquareRoot; -use codec::{KeyedVec, Slicable, Input, NonTrivialSlicable}; -use runtime_support::{StorageValue, StorageMap}; -use demo_primitives::{AccountId, Hash, BlockNumber}; -use dispatch::PrivCall as Proposal; -use runtime::{staking, system, session}; -use runtime::staking::{PublicPass, Balance}; - -/// A token for privileged dispatch. Can only be created in this module. -pub struct PrivPass((),); - -impl PrivPass { - fn new() -> PrivPass { PrivPass((),) } - - #[cfg(test)] - pub fn test() -> PrivPass { PrivPass((),) } -} - -/// A proposal index. -pub type PropIndex = u32; -/// A referendum index. -pub type ReferendumIndex = u32; - -/// A means of determining if a vote is past pass threshold. -#[derive(Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] -pub enum VoteThreshold { - /// A supermajority of approvals is needed to pass this vote. - SuperMajorityApprove, - /// A supermajority of rejects is needed to fail this vote. - SuperMajorityAgainst, - /// A simple majority of approvals is needed to pass this vote. - SimpleMajority, -} - -impl Slicable for VoteThreshold { - fn decode(input: &mut I) -> Option { - u8::decode(input).and_then(|v| match v { - 0 => Some(VoteThreshold::SuperMajorityApprove), - 1 => Some(VoteThreshold::SuperMajorityAgainst), - 2 => Some(VoteThreshold::SimpleMajority), - _ => None, - }) - } - - fn using_encoded R>(&self, f: F) -> R { - match *self { - VoteThreshold::SuperMajorityApprove => 0u8, - VoteThreshold::SuperMajorityAgainst => 1u8, - VoteThreshold::SimpleMajority => 2u8, - }.using_encoded(f) - } -} -impl NonTrivialSlicable for VoteThreshold {} - -trait Approved { - /// Given `approve` votes for and `against` votes against from a total electorate size of - /// `electorate` (`electorate - (approve + against)` are abstainers), then returns true if the - /// overall outcome is in favour of approval. - fn approved(&self, approve: Balance, against: Balance, electorate: Balance) -> bool; -} - -impl Approved for VoteThreshold { - /// Given `approve` votes for and `against` votes against from a total electorate size of - /// `electorate` (`electorate - (approve + against)` are abstainers), then returns true if the - /// overall outcome is in favour of approval. - fn approved(&self, approve: Balance, against: Balance, electorate: Balance) -> bool { - let voters = approve + against; - match *self { - VoteThreshold::SuperMajorityApprove => - voters.integer_sqrt() * approve / electorate.integer_sqrt() > against, - VoteThreshold::SuperMajorityAgainst => - approve > voters.integer_sqrt() * against / electorate.integer_sqrt(), - VoteThreshold::SimpleMajority => approve > against, - } - } -} - -storage_items! { - // The number of (public) proposals that have been made so far. - pub PublicPropCount get(public_prop_count): b"dem:ppc" => default PropIndex; - // The public proposals. Unsorted. - pub PublicProps get(public_props): b"dem:pub" => default Vec<(PropIndex, Proposal, AccountId)>; - // Those who have locked a deposit. - pub DepositOf get(deposit_lockers): b"dem:dep:" => map [ PropIndex => (Balance, Vec) ]; - // How often (in blocks) new public referenda are launched. - pub LaunchPeriod get(launch_period): b"dem:lau" => required BlockNumber; - // The minimum amount to be used as a deposit for a public referendum proposal. - pub MinimumDeposit get(minimum_deposit): b"dem:min" => required Balance; - - // How often (in blocks) to check for new votes. - pub VotingPeriod get(voting_period): b"dem:per" => required BlockNumber; - - // The next free referendum index, aka the number of referendums started so far. - pub ReferendumCount get(next_free_ref_index): b"dem:rco" => default ReferendumIndex; - // The next referendum index that should be tallied. - pub NextTally get(next_tally): b"dem:nxt" => default ReferendumIndex; - // Information concerning any given referendum. - pub ReferendumInfoOf get(referendum_info): b"dem:pro:" => map [ ReferendumIndex => (BlockNumber, Proposal, VoteThreshold) ]; - - // Get the voters for the current proposal. - pub VotersFor get(voters_for): b"dem:vtr:" => default map [ ReferendumIndex => Vec ]; - - // Get the vote, if Some, of `who`. - pub VoteOf get(vote_of): b"dem:vot:" => map [ (ReferendumIndex, AccountId) => bool ]; -} - -// public proposals - -/// Get the amount locked in support of `proposal`; false if proposal isn't a valid proposal -/// index. -pub fn locked_for(proposal: PropIndex) -> Option { - deposit_lockers(proposal).map(|(d, l)| d * (l.len() as Balance)) -} - -/// Return true if `ref_index` is an on-going referendum. -pub fn is_active_referendum(ref_index: ReferendumIndex) -> bool { - ReferendumInfoOf::exists(ref_index) -} - -/// Get all referendums currently active. -pub fn active_referendums() -> Vec<(ReferendumIndex, BlockNumber, Proposal, VoteThreshold)> { - let next = NextTally::get(); - let last = ReferendumCount::get(); - (next..last).into_iter() - .filter_map(|i| referendum_info(i).map(|(n, p, t)| (i, n, p, t))) - .collect() -} - -/// Get all referendums ready for tally at block `n`. -pub fn maturing_referendums_at(n: BlockNumber) -> Vec<(ReferendumIndex, BlockNumber, Proposal, VoteThreshold)> { - let next = NextTally::get(); - let last = ReferendumCount::get(); - (next..last).into_iter() - .filter_map(|i| referendum_info(i).map(|(n, p, t)| (i, n, p, t))) - .take_while(|&(_, block_number, _, _)| block_number == n) - .collect() -} - -/// Get the voters for the current proposal. -pub fn tally(ref_index: ReferendumIndex) -> (staking::Balance, staking::Balance) { - voters_for(ref_index).iter() - .map(|a| (staking::balance(a), vote_of((ref_index, *a)).expect("all items come from `voters`; for an item to be in `voters` there must be a vote registered; qed"))) - .map(|(bal, vote)| if vote { (bal, 0) } else { (0, bal) }) - .fold((0, 0), |(a, b), (c, d)| (a + c, b + d)) -} - -impl_dispatch! { - pub mod public; - fn propose(proposal: Box, value: Balance) = 0; - fn second(proposal: PropIndex) = 1; - fn vote(ref_index: ReferendumIndex, approve_proposal: bool) = 2; -} - -impl<'a> public::Dispatch for PublicPass<'a> { - /// Propose a sensitive action to be taken. - fn propose(self, proposal: Box, value: Balance) { - assert!(value >= minimum_deposit()); - assert!(staking::internal::deduct_unbonded(&self, value)); - - let index = PublicPropCount::get(); - PublicPropCount::put(index + 1); - DepositOf::insert(index, (value, vec![*self])); - - let mut props = public_props(); - props.push((index, (*proposal).clone(), *self)); - PublicProps::put(props); - } - - /// Propose a sensitive action to be taken. - fn second(self, proposal: PropIndex) { - let mut deposit = DepositOf::get(proposal).expect("can only second an existing proposal"); - assert!(staking::internal::deduct_unbonded(&self, deposit.0)); - - deposit.1.push(*self); - DepositOf::insert(proposal, deposit); - } - - /// Vote in a referendum. If `approve_proposal` is true, the vote is to enact the proposal; - /// false would be a vote to keep the status quo.. - fn vote(self, ref_index: ReferendumIndex, approve_proposal: bool) { - if !is_active_referendum(ref_index) { - panic!("vote given for invalid referendum.") - } - if staking::balance(&self) == 0 { - panic!("transactor must have balance to signal approval."); - } - if !VoteOf::exists(&(ref_index, *self)) { - let mut voters = voters_for(ref_index); - voters.push(self.clone()); - VotersFor::insert(ref_index, voters); - } - VoteOf::insert(&(ref_index, *self), approve_proposal); - } -} - -impl_dispatch! { - pub mod privileged; - fn start_referendum(proposal: Box, vote_threshold: VoteThreshold) = 0; - fn cancel_referendum(ref_index: ReferendumIndex) = 1; -} - -impl privileged::Dispatch for PrivPass { - /// Start a referendum. - fn start_referendum(self, proposal: Box, vote_threshold: VoteThreshold) { - inject_referendum(system::block_number() + voting_period(), *proposal, vote_threshold); - } - - /// Remove a referendum. - fn cancel_referendum(self, ref_index: ReferendumIndex) { - clear_referendum(ref_index); - } -} - -pub mod internal { - use super::*; - use dispatch; - - /// Can be called directly by the council. - pub fn start_referendum(proposal: Proposal, vote_threshold: VoteThreshold) { - inject_referendum(system::block_number() + voting_period(), proposal, vote_threshold); - } - - /// Remove a referendum. - pub fn cancel_referendum(ref_index: ReferendumIndex) { - clear_referendum(ref_index); - } - - /// Current era is ending; we should finish up any proposals. - pub fn end_block(now: BlockNumber) { - // pick out another public referendum if it's time. - if now % launch_period() == 0 { - let mut public_props = public_props(); - if let Some((winner_index, _)) = public_props.iter() - .enumerate() - .max_by_key(|x| locked_for((x.1).0).expect("All current public proposals have an amount locked")) - { - let (prop_index, proposal, _) = public_props.swap_remove(winner_index); - let (deposit, depositors): (Balance, Vec) = - DepositOf::take(prop_index).expect("depositors always exist for current proposals"); - // refund depositors - for d in &depositors { - staking::internal::refund(d, deposit); - } - PublicProps::put(public_props); - inject_referendum(now + voting_period(), proposal, VoteThreshold::SuperMajorityApprove); - } - } - - // tally up votes for any expiring referenda. - for (index, _, proposal, vote_threshold) in maturing_referendums_at(now) { - let (approve, against) = tally(index); - let total_stake = staking::total_stake(); - clear_referendum(index); - if vote_threshold.approved(approve, against, total_stake) { - proposal.dispatch(PrivPass::new()); - } - NextTally::put(index + 1); - } - } -} - -/// Start a referendum -fn inject_referendum( - end: BlockNumber, - proposal: Proposal, - vote_threshold: VoteThreshold -) -> ReferendumIndex { - let ref_index = next_free_ref_index(); - if ref_index > 0 && referendum_info(ref_index - 1).map(|i| i.0 > end).unwrap_or(false) { - panic!("Cannot inject a referendum that ends earlier than preceeding referendum"); - } - - ReferendumCount::put(ref_index + 1); - ReferendumInfoOf::insert(ref_index, (end, proposal, vote_threshold)); - ref_index -} - -/// Remove all info on a referendum. -fn clear_referendum(ref_index: ReferendumIndex) { - ReferendumInfoOf::remove(ref_index); - VotersFor::remove(ref_index); - for v in voters_for(ref_index) { - VoteOf::remove((ref_index, v)); - } -} - -#[cfg(test)] -pub mod testing { - use super::*; - use runtime_io::{twox_128, TestExternalities}; - use runtime_support::{StorageList, StorageValue, StorageMap}; - use codec::Joiner; - use keyring::Keyring::*; - use runtime::{session, staking}; - - pub fn externalities() -> TestExternalities { - map![ - twox_128(session::SessionLength::key()).to_vec() => vec![].and(&1u64), - twox_128(session::Validators::key()).to_vec() => vec![].and(&vec![Alice.to_raw_public(), Bob.into(), Charlie.into()]), - twox_128(&staking::Intention::len_key()).to_vec() => vec![].and(&3u32), - twox_128(&staking::Intention::key_for(0)).to_vec() => Alice.to_raw_public_vec(), - twox_128(&staking::Intention::key_for(1)).to_vec() => Bob.to_raw_public_vec(), - twox_128(&staking::Intention::key_for(2)).to_vec() => Charlie.to_raw_public_vec(), - twox_128(&staking::FreeBalanceOf::key_for(*Alice)).to_vec() => vec![].and(&10u64), - twox_128(&staking::FreeBalanceOf::key_for(*Bob)).to_vec() => vec![].and(&20u64), - twox_128(&staking::FreeBalanceOf::key_for(*Charlie)).to_vec() => vec![].and(&30u64), - twox_128(&staking::FreeBalanceOf::key_for(*Dave)).to_vec() => vec![].and(&40u64), - twox_128(&staking::FreeBalanceOf::key_for(*Eve)).to_vec() => vec![].and(&50u64), - twox_128(&staking::FreeBalanceOf::key_for(*Ferdie)).to_vec() => vec![].and(&60u64), - twox_128(&staking::FreeBalanceOf::key_for(*One)).to_vec() => vec![].and(&1u64), - twox_128(staking::TotalStake::key()).to_vec() => vec![].and(&210u64), - twox_128(staking::SessionsPerEra::key()).to_vec() => vec![].and(&1u64), - twox_128(staking::ValidatorCount::key()).to_vec() => vec![].and(&3u64), - twox_128(staking::CurrentEra::key()).to_vec() => vec![].and(&1u64), - twox_128(staking::TransactionFee::key()).to_vec() => vec![].and(&1u64), - twox_128(staking::BondingDuration::key()).to_vec() => vec![].and(&0u64), - - twox_128(LaunchPeriod::key()).to_vec() => vec![].and(&1u64), - twox_128(VotingPeriod::key()).to_vec() => vec![].and(&1u64), - twox_128(MinimumDeposit::key()).to_vec() => vec![].and(&1u64) - ] - } -} - -#[cfg(test)] -mod tests { - use super::*; - use runtime_io::{with_externalities, twox_128, TestExternalities}; - use codec::{KeyedVec, Joiner}; - use keyring::Keyring::*; - use demo_primitives::AccountId; - use dispatch::PrivCall as Proposal; - use runtime::staking::PublicPass; - use super::public::Dispatch; - use super::privileged::Dispatch as PrivDispatch; - use runtime::{staking, session, democracy}; - - fn new_test_ext() -> TestExternalities { - testing::externalities() - } - - #[test] - fn params_should_work() { - with_externalities(&mut new_test_ext(), || { - assert_eq!(launch_period(), 1u64); - assert_eq!(voting_period(), 1u64); - assert_eq!(minimum_deposit(), 1u64); - assert_eq!(next_free_ref_index(), 0u32); - assert_eq!(staking::sessions_per_era(), 1u64); - assert_eq!(staking::total_stake(), 210u64); - }); - } - - // TODO: test VoteThreshold - - fn propose_sessions_per_era(who: &AccountId, value: u64, locked: Balance) { - PublicPass::test(who). - propose(Box::new(Proposal::Staking(staking::privileged::Call::set_sessions_per_era(value))), locked); - } - - #[test] - fn locked_for_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - propose_sessions_per_era(&Alice, 2, 2u64); - propose_sessions_per_era(&Alice, 4, 4u64); - propose_sessions_per_era(&Alice, 3, 3u64); - assert_eq!(locked_for(0), Some(2)); - assert_eq!(locked_for(1), Some(4)); - assert_eq!(locked_for(2), Some(3)); - }); - } - - #[test] - fn single_proposal_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - propose_sessions_per_era(&Alice, 2, 1u64); - democracy::internal::end_block(system::block_number()); - - system::testing::set_block_number(2); - let r = 0; - PublicPass::test(&Alice).vote(r, true); - - assert_eq!(next_free_ref_index(), 1); - assert_eq!(voters_for(r), vec![Alice.to_raw_public()]); - assert_eq!(vote_of((r, *Alice)), Some(true)); - assert_eq!(tally(r), (10, 0)); - - democracy::internal::end_block(system::block_number()); - staking::internal::check_new_era(); - - assert_eq!(staking::era_length(), 2u64); - }); - } - - #[test] - fn deposit_for_proposals_should_be_taken() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - propose_sessions_per_era(&Alice, 2, 5u64); - PublicPass::test(&Bob).second(0); - PublicPass::test(&Eve).second(0); - PublicPass::test(&Eve).second(0); - PublicPass::test(&Eve).second(0); - assert_eq!(staking::balance(&Alice), 5u64); - assert_eq!(staking::balance(&Bob), 15u64); - assert_eq!(staking::balance(&Eve), 35u64); - }); - } - - #[test] - fn deposit_for_proposals_should_be_returned() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - propose_sessions_per_era(&Alice, 2, 5u64); - PublicPass::test(&Bob).second(0); - PublicPass::test(&Eve).second(0); - PublicPass::test(&Eve).second(0); - PublicPass::test(&Eve).second(0); - democracy::internal::end_block(system::block_number()); - assert_eq!(staking::balance(&Alice), 10u64); - assert_eq!(staking::balance(&Bob), 20u64); - assert_eq!(staking::balance(&Eve), 50u64); - }); - } - - #[test] - #[should_panic] - fn proposal_with_deposit_below_minimum_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - propose_sessions_per_era(&Alice, 2, 0u64); - }); - } - - #[test] - #[should_panic] - fn poor_proposer_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - propose_sessions_per_era(&Alice, 2, 11u64); - }); - } - - #[test] - #[should_panic] - fn poor_seconder_should_panic() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - propose_sessions_per_era(&Bob, 2, 11u64); - PublicPass::test(&Alice).second(0); - }); - } - - fn propose_bonding_duration(who: &AccountId, value: u64, locked: Balance) { - PublicPass::test(who). - propose(Box::new(Proposal::Staking(staking::privileged::Call::set_bonding_duration(value))), locked); - } - - #[test] - fn runners_up_should_come_after() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(0); - propose_bonding_duration(&Alice, 2, 2u64); - propose_bonding_duration(&Alice, 4, 4u64); - propose_bonding_duration(&Alice, 3, 3u64); - democracy::internal::end_block(system::block_number()); - - system::testing::set_block_number(1); - PublicPass::test(&Alice).vote(0, true); - democracy::internal::end_block(system::block_number()); - staking::internal::check_new_era(); - assert_eq!(staking::bonding_duration(), 4u64); - - system::testing::set_block_number(2); - PublicPass::test(&Alice).vote(1, true); - democracy::internal::end_block(system::block_number()); - staking::internal::check_new_era(); - assert_eq!(staking::bonding_duration(), 3u64); - - system::testing::set_block_number(3); - PublicPass::test(&Alice).vote(2, true); - democracy::internal::end_block(system::block_number()); - staking::internal::check_new_era(); - assert_eq!(staking::bonding_duration(), 2u64); - }); - } - - fn sessions_per_era_propsal(value: u64) -> Proposal { - Proposal::Staking(staking::privileged::Call::set_sessions_per_era(value)) - } - - #[test] - fn simple_passing_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let r = inject_referendum(1, sessions_per_era_propsal(2), VoteThreshold::SuperMajorityApprove); - PublicPass::test(&Alice).vote(r, true); - - assert_eq!(voters_for(r), vec![Alice.to_raw_public()]); - assert_eq!(vote_of((r, *Alice)), Some(true)); - assert_eq!(tally(r), (10, 0)); - - democracy::internal::end_block(system::block_number()); - staking::internal::check_new_era(); - - assert_eq!(staking::era_length(), 2u64); - }); - } - - #[test] - fn cancel_referendum_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let r = inject_referendum(1, sessions_per_era_propsal(2), VoteThreshold::SuperMajorityApprove); - PublicPass::test(&Alice).vote(r, true); - PrivPass::test().cancel_referendum(r); - - democracy::internal::end_block(system::block_number()); - staking::internal::check_new_era(); - - assert_eq!(staking::era_length(), 1u64); - }); - } - - #[test] - fn simple_failing_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let r = inject_referendum(1, sessions_per_era_propsal(2), VoteThreshold::SuperMajorityApprove); - PublicPass::test(&Alice).vote(r, false); - - assert_eq!(voters_for(r), vec![Alice.to_raw_public()]); - assert_eq!(vote_of((r, *Alice)), Some(false)); - assert_eq!(tally(r), (0, 10)); - - democracy::internal::end_block(system::block_number()); - staking::internal::check_new_era(); - - assert_eq!(staking::era_length(), 1u64); - }); - } - - #[test] - fn controversial_voting_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let r = inject_referendum(1, sessions_per_era_propsal(2), VoteThreshold::SuperMajorityApprove); - PublicPass::test(&Alice).vote(r, true); - PublicPass::test(&Bob).vote(r, false); - PublicPass::test(&Charlie).vote(r, false); - PublicPass::test(&Dave).vote(r, true); - PublicPass::test(&Eve).vote(r, false); - PublicPass::test(&Ferdie).vote(r, true); - - assert_eq!(tally(r), (110, 100)); - - democracy::internal::end_block(system::block_number()); - staking::internal::check_new_era(); - - assert_eq!(staking::era_length(), 2u64); - }); - } - - #[test] - fn controversial_low_turnout_voting_should_work() { - with_externalities(&mut new_test_ext(), || { - system::testing::set_block_number(1); - let r = inject_referendum(1, sessions_per_era_propsal(2), VoteThreshold::SuperMajorityApprove); - PublicPass::test(&Eve).vote(r, false); - PublicPass::test(&Ferdie).vote(r, true); - - assert_eq!(tally(r), (60, 50)); - - democracy::internal::end_block(system::block_number()); - staking::internal::check_new_era(); - - assert_eq!(staking::era_length(), 1u64); - }); - } - - #[test] - fn passing_low_turnout_voting_should_work() { - with_externalities(&mut new_test_ext(), || { - assert_eq!(staking::era_length(), 1u64); - assert_eq!(staking::total_stake(), 210u64); - - system::testing::set_block_number(1); - let r = inject_referendum(1, sessions_per_era_propsal(2), VoteThreshold::SuperMajorityApprove); - PublicPass::test(&Dave).vote(r, true); - PublicPass::test(&Eve).vote(r, false); - PublicPass::test(&Ferdie).vote(r, true); - - assert_eq!(tally(r), (100, 50)); - - democracy::internal::end_block(system::block_number()); - staking::internal::check_new_era(); - - assert_eq!(staking::era_length(), 2u64); - }); - } -} diff --git a/demo/runtime/src/runtime/mod.rs b/demo/runtime/src/runtime/mod.rs deleted file mode 100644 index bba91af86d7a8..0000000000000 --- a/demo/runtime/src/runtime/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -// 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 . - -//! The Substrate Demo runtime. - -#[allow(unused)] -pub mod system; -#[allow(unused)] -pub mod consensus; -#[allow(unused)] -pub mod staking; -#[allow(unused)] -pub mod timestamp; -#[allow(unused)] -pub mod session; -#[allow(unused)] -pub mod democracy; -#[allow(unused)] -pub mod council; -#[allow(unused)] -pub mod council_vote; diff --git a/demo/runtime/src/runtime/session.rs b/demo/runtime/src/runtime/session.rs deleted file mode 100644 index 430a095a20f07..0000000000000 --- a/demo/runtime/src/runtime/session.rs +++ /dev/null @@ -1,259 +0,0 @@ -// 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 . - -//! Session manager: is told the validators and allows them to manage their session keys for the -//! consensus module. - -use rstd::prelude::*; -use codec::KeyedVec; -use runtime_support::{storage, StorageValue, StorageMap}; -use demo_primitives::{AccountId, SessionKey, BlockNumber}; -use runtime::{system, staking, consensus}; -use runtime::democracy::PrivPass; -use runtime::staking::PublicPass; - -storage_items!{ - // The current set of validators. - pub Validators get(validators): b"ses:val" => required Vec; - // Current length of the session. - pub SessionLength get(length): b"ses:len" => required BlockNumber; - // Current index of the session. - pub CurrentIndex get(current_index): b"ses:ind" => required BlockNumber; - - // Block at which the session length last changed. - LastLengthChange: b"ses:llc" => default BlockNumber; - // The next key for a given validator. - NextKeyFor: b"ses:nxt:" => map [ AccountId => SessionKey ]; - // The next session length. - NextSessionLength: b"ses:nln" => BlockNumber; -} - -/// The number of validators currently. -pub fn validator_count() -> u32 { - Validators::get().len() as u32 // TODO: can probably optimised -} - -impl_dispatch! { - pub mod public; - fn set_key(key: SessionKey) = 0; -} - -impl<'a> public::Dispatch for PublicPass<'a> { - /// Sets the session key of `_validator` to `_key`. This doesn't take effect until the next - /// session. - fn set_key(self, key: SessionKey) { - // set new value for next session - NextKeyFor::insert(*self, key) - } -} - -impl_dispatch! { - pub mod privileged; - fn set_length(new: BlockNumber) = 0; - fn force_new_session() = 1; -} - -impl privileged::Dispatch for PrivPass { - /// Set a new era length. Won't kick in until the next era change (at current length). - fn set_length(self, new: BlockNumber) { - NextSessionLength::put(new); - } - - /// Forces a new session. - fn force_new_session(self) { - internal::rotate_session(); - } -} - -// INTERNAL API (available to other runtime modules) - -pub mod internal { - use super::*; - - /// Set the current set of validators. - /// - /// Called by `staking::next_era()` only. `next_session` should be called after this in order to - /// update the session keys to the next validator set. - pub fn set_validators(new: &[AccountId]) { - Validators::put(&new.to_vec()); // TODO: optimise. - consensus::internal::set_authorities(new); - } - - /// Hook to be called after transaction processing. - pub fn check_rotate_session() { - // do this last, after the staking system has had chance to switch out the authorities for the - // new set. - // check block number and call next_session if necessary. - if (system::block_number() - LastLengthChange::get()) % length() == 0 { - rotate_session(); - } - } - - /// Move onto next session: register the new authority set. - pub fn rotate_session() { - // Increment current session index. - CurrentIndex::put(CurrentIndex::get() + 1); - - // Enact era length change. - if let Some(next_len) = NextSessionLength::take() { - SessionLength::put(next_len); - LastLengthChange::put(system::block_number()); - } - - // Update any changes in session keys. - validators().iter().enumerate().for_each(|(i, v)| { - if let Some(n) = NextKeyFor::take(v) { - consensus::internal::set_authority(i as u32, &n); - } - }); - } -} - -#[cfg(any(feature = "std", test))] -pub mod testing { - use super::*; - use runtime_io::{twox_128, TestExternalities}; - use codec::{Joiner, KeyedVec}; - use keyring::Keyring::*; - use runtime::system; - - pub fn externalities(session_length: u64) -> TestExternalities { - let three = [3u8; 32]; - - let extras: TestExternalities = map![ - twox_128(SessionLength::key()).to_vec() => vec![].and(&session_length), - twox_128(CurrentIndex::key()).to_vec() => vec![].and(&0u64), - twox_128(Validators::key()).to_vec() => vec![].and(&vec![One.into(), Two.into(), three]) - ]; - system::testing::externalities().into_iter().chain(extras.into_iter()).collect() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use super::public::*; - use super::privileged::Dispatch as PrivDispatch; - use super::internal::*; - use runtime_io::{with_externalities, twox_128, TestExternalities}; - use codec::{KeyedVec, Joiner}; - use keyring::Keyring; - use demo_primitives::AccountId; - use runtime::{consensus, session}; - - fn simple_setup() -> TestExternalities { - map![ - twox_128(SessionLength::key()).to_vec() => vec![].and(&2u64), - twox_128(CurrentIndex::key()).to_vec() => vec![].and(&0u64), - // the validators (10, 20, ...) - twox_128(Validators::key()).to_vec() => vec![].and(&vec![[10u8; 32], [20; 32]]), - // initial session keys (11, 21, ...) - b":auth:len".to_vec() => vec![].and(&2u32), - 0u32.to_keyed_vec(b":auth:") => vec![11; 32], - 1u32.to_keyed_vec(b":auth:") => vec![21; 32] - ] - } - - #[test] - fn simple_setup_should_work() { - let mut t = simple_setup(); - with_externalities(&mut t, || { - assert_eq!(consensus::authorities(), vec![[11u8; 32], [21u8; 32]]); - assert_eq!(length(), 2u64); - assert_eq!(validators(), vec![[10u8; 32], [20u8; 32]]); - }); - } - - #[test] - fn session_length_change_should_work() { - let mut t = simple_setup(); - with_externalities(&mut t, || { - // Block 1: Change to length 3; no visible change. - system::testing::set_block_number(1); - PrivPass::test().set_length(3); - check_rotate_session(); - assert_eq!(length(), 2); - assert_eq!(current_index(), 0); - - // Block 2: Length now changed to 3. Index incremented. - system::testing::set_block_number(2); - PrivPass::test().set_length(3); - check_rotate_session(); - assert_eq!(length(), 3); - assert_eq!(current_index(), 1); - - // Block 3: Length now changed to 3. Index incremented. - system::testing::set_block_number(3); - check_rotate_session(); - assert_eq!(length(), 3); - assert_eq!(current_index(), 1); - - // Block 4: Change to length 2; no visible change. - system::testing::set_block_number(4); - PrivPass::test().set_length(2); - check_rotate_session(); - assert_eq!(length(), 3); - assert_eq!(current_index(), 1); - - // Block 5: Length now changed to 2. Index incremented. - system::testing::set_block_number(5); - check_rotate_session(); - assert_eq!(length(), 2); - assert_eq!(current_index(), 2); - - // Block 6: No change. - system::testing::set_block_number(6); - check_rotate_session(); - assert_eq!(length(), 2); - assert_eq!(current_index(), 2); - - // Block 7: Next index. - system::testing::set_block_number(7); - check_rotate_session(); - assert_eq!(length(), 2); - assert_eq!(current_index(), 3); - }); - } - - #[test] - fn session_change_should_work() { - let mut t = simple_setup(); - with_externalities(&mut t, || { - // Block 1: No change - system::testing::set_block_number(1); - check_rotate_session(); - assert_eq!(consensus::authorities(), vec![[11u8; 32], [21u8; 32]]); - - // Block 2: Session rollover, but no change. - system::testing::set_block_number(2); - check_rotate_session(); - assert_eq!(consensus::authorities(), vec![[11u8; 32], [21u8; 32]]); - - // Block 3: Set new key for validator 2; no visible change. - system::testing::set_block_number(3); - PublicPass::test(&[20; 32]).set_key([22; 32]); - assert_eq!(consensus::authorities(), vec![[11u8; 32], [21u8; 32]]); - - check_rotate_session(); - assert_eq!(consensus::authorities(), vec![[11u8; 32], [21u8; 32]]); - - // Block 4: Session rollover, authority 2 changes. - system::testing::set_block_number(4); - check_rotate_session(); - assert_eq!(consensus::authorities(), vec![[11u8; 32], [22u8; 32]]); - }); - } -} diff --git a/demo/runtime/src/runtime/staking.rs b/demo/runtime/src/runtime/staking.rs deleted file mode 100644 index d772aad898c68..0000000000000 --- a/demo/runtime/src/runtime/staking.rs +++ /dev/null @@ -1,885 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// This file is part of Substrate Demo. - -// Substrate Demo is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate Demo is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate Demo. If not, see . - -//! Staking manager: Handles balances and periodically determines the best set of validators. - -use rstd::prelude::*; -use rstd::{ops, cmp}; -use rstd::cell::RefCell; -use rstd::collections::btree_map::{BTreeMap, Entry}; -use runtime_io::{print, blake2_256}; -use codec::{Slicable, Input, KeyedVec}; -use runtime_support::{storage, StorageValue, StorageList, StorageMap}; -use demo_primitives::{BlockNumber, AccountId}; -use runtime::{system, session, democracy}; - -/// The balance of an account. -pub type Balance = u64; - -/// The amount of bonding period left in an account. Measured in eras. -pub type Bondage = u64; - -storage_items! { - // The length of the bonding duration in eras. - pub BondingDuration get(bonding_duration): b"sta:loc" => required BlockNumber; - // The length of a staking era in sessions. - pub ValidatorCount get(validator_count): b"sta:vac" => required u32; - // The length of a staking era in sessions. - pub SessionsPerEra get(sessions_per_era): b"sta:spe" => required BlockNumber; - // The total amount of stake on the system. - pub TotalStake get(total_stake): b"sta:tot" => required Balance; - // The fee to be paid for making a transaction. - pub TransactionFee get(transaction_fee): b"sta:fee" => required Balance; - - // The current era index. - pub CurrentEra get(current_era): b"sta:era" => required BlockNumber; - // All the accounts with a desire to stake. - pub Intention: b"sta:wil:" => list [ AccountId ]; - // The next value of sessions per era. - pub NextSessionsPerEra get(next_sessions_per_era): b"sta:nse" => BlockNumber; - // The block number at which the era length last changed. - pub LastEraLengthChange get(last_era_length_change): b"sta:lec" => default BlockNumber; - - // The balance of a given account. - pub FreeBalanceOf get(free_balance_of): b"sta:bal:" => default map [ AccountId => Balance ]; - - // The amount of the balance of a given account that is exterally reserved; this can still get - // slashed, but gets slashed last of all. - pub ReservedBalanceOf get(reserved_balance_of): b"sta:lbo:" => default map [ AccountId => Balance ]; - - // The block at which the `who`'s funds become entirely liquid. - pub BondageOf get(bondage_of): b"sta:bon:" => default map [ AccountId => Bondage ]; - - // The code associated with an account. - pub CodeOf: b"sta:cod:" => default map [ AccountId => Vec ]; // TODO Vec values should be optimised to not do a length prefix. - - // The storage items associated with an account/key. - pub StorageOf: b"sta:sto:" => map [ (AccountId, Vec) => Vec ]; // TODO: keys should also be able to take AsRef to ensure Vecs can be passed as &[u8] -} - -/// The length of a staking era in blocks. -pub fn era_length() -> BlockNumber { - SessionsPerEra::get() * session::length() -} - -/// The combined balance of `who`. -pub fn balance(who: &AccountId) -> Balance { - FreeBalanceOf::get(who) + ReservedBalanceOf::get(who) -} - -/// Some result as `slash(who, value)` (but without the side-effects) asuming there are no -/// balance changes in the meantime. -pub fn can_slash(who: &AccountId, value: Balance) -> bool { - balance(who) >= value -} - -#[derive(PartialEq, Copy, Clone)] -#[cfg_attr(test, derive(Debug))] -pub enum LockStatus { - Liquid, - LockedUntil(BlockNumber), - Staked, -} - -/// The block at which the `who`'s funds become entirely liquid. -pub fn unlock_block(who: &AccountId) -> LockStatus { - match BondageOf::get(who) { - i if i == Bondage::max_value() => LockStatus::Staked, - i if i <= system::block_number() => LockStatus::Liquid, - i => LockStatus::LockedUntil(i), - } -} - -pub struct PublicPass<'a> (&'a AccountId); - -const NOBODY: AccountId = [0u8; 32]; - -impl<'a> PublicPass<'a> { - pub fn new(transactor: &AccountId) -> PublicPass { - let b = FreeBalanceOf::get(transactor); - let transaction_fee = TransactionFee::get(); - assert!(b >= transaction_fee, "attempt to transact without enough funds to pay fee"); - FreeBalanceOf::insert(transactor, b - transaction_fee); - PublicPass(transactor) - } - - #[cfg(test)] - pub fn test(signed: &AccountId) -> PublicPass { - PublicPass(signed) - } - - #[cfg(test)] - pub fn nobody() -> PublicPass<'static> { - PublicPass(&NOBODY) - } - - /// Create a smart-contract account. - pub fn create(self, code: &[u8], value: Balance) { - // commit anything that made it this far to storage - if let Some(commit) = private::effect_create(self.0, code, value, private::DirectExt) { - private::commit_state(commit); - } - } -} - -impl<'a> ops::Deref for PublicPass<'a> { - type Target = AccountId; - fn deref(&self) -> &AccountId { - self.0 - } -} - -impl_dispatch! { - pub mod public; - fn transfer(dest: AccountId, value: Balance) = 0; - fn stake() = 1; - fn unstake() = 2; -} - -impl<'a> public::Dispatch for PublicPass<'a> { - /// Transfer some unlocked staking balance to another staker. - /// TODO: probably want to state gas-limit and gas-price. - fn transfer(self, dest: AccountId, value: Balance) { - // commit anything that made it this far to storage - if let Some(commit) = private::effect_transfer(&self, &dest, value, private::DirectExt) { - private::commit_state(commit); - } - } - - /// Declare the desire to stake for the transactor. - /// - /// Effects will be felt at the beginning of the next era. - fn stake(self) { - let mut intentions = Intention::items(); - // can't be in the list twice. - assert!(intentions.iter().find(|&t| *t == *self).is_none(), "Cannot stake if already staked."); - intentions.push(self.clone()); - Intention::set_items(&intentions); - BondageOf::insert(*self, u64::max_value()); - } - - /// Retract the desire to stake for the transactor. - /// - /// Effects will be felt at the beginning of the next era. - fn unstake(self) { - let mut intentions = Intention::items(); - if let Some(position) = intentions.iter().position(|&t| t == *self) { - intentions.swap_remove(position); - } else { - panic!("Cannot unstake if not already staked."); - } - Intention::set_items(&intentions); - BondageOf::insert(*self, CurrentEra::get() + BondingDuration::get()); - } -} - -impl_dispatch! { - pub mod privileged; - fn set_sessions_per_era(new: BlockNumber) = 0; - fn set_bonding_duration(new: BlockNumber) = 1; - fn set_validator_count(new: u32) = 2; - fn force_new_era() = 3; -} - -impl privileged::Dispatch for democracy::PrivPass { - /// Set the number of sessions in an era. - fn set_sessions_per_era(self, new: BlockNumber) { - NextSessionsPerEra::put(&new); - } - - /// The length of the bonding duration in eras. - fn set_bonding_duration(self, new: BlockNumber) { - BondingDuration::put(&new); - } - - /// The length of a staking era in sessions. - fn set_validator_count(self, new: u32) { - ValidatorCount::put(&new); - } - - /// Force there to be a new era. This also forces a new session immediately after. - fn force_new_era(self) { - new_era(); - session::internal::rotate_session(); - } -} - -// Each identity's stake may be in one of three bondage states, given by an integer: -// - n | n <= CurrentEra::get(): inactive: free to be transferred. -// - ~0: active: currently representing a validator. -// - n | n > CurrentEra::get(): deactivating: recently representing a validator and not yet -// ready for transfer. - -mod private { - use super::*; - - #[derive(Default)] - pub struct ChangeEntry { - balance: Option, - code: Option>, - storage: BTreeMap, Option>>, - } - - impl ChangeEntry { - pub fn balance_changed(b: Balance) -> Self { - ChangeEntry { balance: Some(b), code: None, storage: Default::default() } - } - } - - type State = BTreeMap; - - pub trait Externalities { - fn get_storage(&self, account: &AccountId, location: &[u8]) -> Option>; - fn get_code(&self, account: &AccountId) -> Vec; - fn get_balance(&self, account: &AccountId) -> Balance; - } - - struct Ext where - F1 : Fn(&AccountId, &[u8]) -> Option>, - F3 : Fn(&AccountId) -> Vec, - F5 : Fn(&AccountId) -> Balance - { - do_get_storage: F1, - do_get_code: F3, - do_get_balance: F5, - } - - pub struct DirectExt; - impl Externalities for DirectExt { - fn get_storage(&self, account: &AccountId, location: &[u8]) -> Option> { - StorageOf::get(&(*account, location.to_vec())) - } - fn get_code(&self, account: &AccountId) -> Vec { - CodeOf::get(account) - } - fn get_balance(&self, account: &AccountId) -> Balance { - FreeBalanceOf::get(account) - } - } - - impl Externalities for Ext where - F1 : Fn(&AccountId, &[u8]) -> Option>, - F3 : Fn(&AccountId) -> Vec, - F5 : Fn(&AccountId) -> Balance - { - fn get_storage(&self, account: &AccountId, location: &[u8]) -> Option> { - (self.do_get_storage)(account, location) - } - fn get_code(&self, account: &AccountId) -> Vec { - (self.do_get_code)(account) - } - fn get_balance(&self, account: &AccountId) -> Balance { - (self.do_get_balance)(account) - } - } - - pub fn commit_state(s: State) { - for (address, changed) in s.into_iter() { - if let Some(balance) = changed.balance { - FreeBalanceOf::insert(address, balance); - } - if let Some(code) = changed.code { - CodeOf::insert(&address, &code); - } - for (k, v) in changed.storage.into_iter() { - if let Some(value) = v { - StorageOf::insert(&(address, k), &value); - } else { - StorageOf::remove(&(address, k)); - } - } - } - } - - fn merge_state(commit_state: State, local: &mut State) { - for (address, changed) in commit_state.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 fn effect_create( - transactor: &AccountId, - code: &[u8], - value: Balance, - ext: E - ) -> Option { - let from_balance = ext.get_balance(transactor); - // TODO: a fee. - assert!(from_balance >= value); - - let mut dest_pre = blake2_256(code).to_vec(); - dest_pre.extend(&transactor[..]); - let dest = blake2_256(&dest_pre); - - // early-out if degenerate. - if &dest == transactor { - return None; - } - - let mut local = BTreeMap::new(); - - // two inserts are safe - assert!(&dest != transactor); - local.insert(dest, ChangeEntry { balance: Some(value), code: Some(code.to_vec()), storage: Default::default() }); - local.insert(transactor.clone(), ChangeEntry::balance_changed(from_balance - value)); - - Some(local) - } - - pub fn effect_transfer( - transactor: &AccountId, - dest: &AccountId, - value: Balance, - ext: E - ) -> Option { - let from_balance = ext.get_balance(transactor); - assert!(from_balance >= value); - - let to_balance = ext.get_balance(dest); - assert!(BondageOf::get(transactor) <= BondageOf::get(dest)); - assert!(to_balance + value > to_balance); // no overflow - - // TODO: a fee, based upon gaslimit/gasprice. - // TODO: consider storing upper-bound for contract's gas limit in fixed-length runtime - // code in contract itself and use that. - - let local: RefCell = RefCell::new(BTreeMap::new()); - - if transactor != dest { - let mut local = local.borrow_mut(); - local.insert(transactor.clone(), ChangeEntry::balance_changed(from_balance - value)); - local.insert(dest.clone(), ChangeEntry::balance_changed(to_balance + value)); - } - - let should_commit = { - // Our local ext: Should be used for any transfers and creates that happen internally. - let ext = || Ext { - do_get_storage: |account: &AccountId, location: &[u8]| - local.borrow().get(account) - .and_then(|a| a.storage.get(location)) - .cloned() - .unwrap_or_else(|| ext.get_storage(account, location)), - do_get_code: |account: &AccountId| - local.borrow().get(account) - .and_then(|a| a.code.clone()) - .unwrap_or_else(|| ext.get_code(account)), - do_get_balance: |account: &AccountId| - local.borrow().get(account) - .and_then(|a| a.balance) - .unwrap_or_else(|| ext.get_balance(account)), - }; - let mut transfer = |inner_dest: &AccountId, value: Balance| { - if let Some(commit_state) = effect_transfer(dest, inner_dest, value, ext()) { - merge_state(commit_state, &mut *local.borrow_mut()); - } - }; - let mut create = |code: &[u8], value: Balance| { - if let Some(commit_state) = effect_create(dest, code, value, ext()) { - merge_state(commit_state, &mut *local.borrow_mut()); - } - }; - let mut put_storage = |location: Vec, value: Option>| { - local.borrow_mut() - .entry(dest.clone()) - .or_insert(Default::default()) - .storage.insert(location, value); - }; - - // TODO: logging (logs are just appended into a notable storage-based vector and cleared every - // block). - // TODO: execute code with ext(), put_storage, create and transfer as externalities. - true - }; - - if should_commit { - Some(local.into_inner()) - } else { - None - } - } -} - -pub mod internal { - use super::*; - - /// Hook to be called after to transaction processing. - pub fn check_new_era() { - // check block number and call new_era if necessary. - if (system::block_number() - LastEraLengthChange::get()) % era_length() == 0 { - new_era(); - } - } - - /// Deduct from an unbonded balance. true if it happened. - pub fn deduct_unbonded(who: &AccountId, value: Balance) -> bool { - if let LockStatus::Liquid = unlock_block(who) { - let b = FreeBalanceOf::get(who); - if b >= value { - FreeBalanceOf::insert(who, &(b - value)); - return true; - } - } - false - } - - /// Refund some balance. - pub fn refund(who: &AccountId, value: Balance) { - FreeBalanceOf::insert(who, &(FreeBalanceOf::get(who) + value)) - } - - /// Will slash any balance, but prefer free over reserved. - pub fn slash(who: &AccountId, value: Balance) -> bool { - let free_balance = FreeBalanceOf::get(who); - let free_slash = cmp::min(free_balance, value); - FreeBalanceOf::insert(who, &(free_balance - free_slash)); - if free_slash < value { - slash_reserved(who, value - free_slash) - } else { - true - } - } - - /// Moves `value` from balance to reserved balance. - pub fn reserve_balance(who: &AccountId, value: Balance) { - let b = FreeBalanceOf::get(who); - assert!(b >= value); - FreeBalanceOf::insert(who, &(b - value)); - ReservedBalanceOf::insert(who, &(ReservedBalanceOf::get(who) + value)); - } - - /// Moves `value` from reserved balance to balance. - pub fn unreserve_balance(who: &AccountId, value: Balance) { - let b = ReservedBalanceOf::get(who); - let value = cmp::min(b, value); - ReservedBalanceOf::insert(who, &(b - value)); - FreeBalanceOf::insert(who, &(FreeBalanceOf::get(who) + value)); - } - - /// Moves `value` from reserved balance to balance. - pub fn slash_reserved(who: &AccountId, value: Balance) -> bool { - let b = ReservedBalanceOf::get(who); - let slash = cmp::min(b, value); - ReservedBalanceOf::insert(who, &(b - slash)); - value == slash - } - - /// Moves `value` from reserved balance to balance. - pub fn transfer_reserved_balance(slashed: &AccountId, beneficiary: &AccountId, value: Balance) -> bool { - let b = ReservedBalanceOf::get(slashed); - let slash = cmp::min(b, value); - ReservedBalanceOf::insert(slashed, &(b - slash)); - FreeBalanceOf::insert(beneficiary, &(FreeBalanceOf::get(beneficiary) + slash)); - slash == value - } -} - -/// The era has changed - enact new staking set. -/// -/// NOTE: This always happens immediately before a session change to ensure that new validators -/// get a chance to set their session keys. -fn new_era() { - // Increment current era. - CurrentEra::put(&(CurrentEra::get() + 1)); - - // Enact era length change. - if let Some(next_spe) = NextSessionsPerEra::get() { - if next_spe != SessionsPerEra::get() { - SessionsPerEra::put(&next_spe); - LastEraLengthChange::put(&system::block_number()); - } - } - - // evaluate desired staking amounts and nominations and optimise to find the best - // combination of validators, then use session::internal::set_validators(). - // for now, this just orders would-be stakers by their balances and chooses the top-most - // ValidatorCount::get() of them. - let mut intentions = Intention::items() - .into_iter() - .map(|v| (balance(&v), v)) - .collect::>(); - intentions.sort_unstable_by(|&(b1, _), &(b2, _)| b2.cmp(&b1)); - session::internal::set_validators( - &intentions.into_iter() - .map(|(_, v)| v) - .take(ValidatorCount::get() as usize) - .collect::>() - ); -} - -#[cfg(any(feature = "std", test))] -pub mod testing { - use super::*; - use runtime_io::{twox_128, TestExternalities}; - use codec::{Joiner, KeyedVec}; - use keyring::Keyring::*; - use runtime::session; - use super::public::{Call, Dispatch}; - use super::privileged::{Dispatch as PrivDispatch, Call as PrivCall}; - - pub fn externalities(session_length: u64, sessions_per_era: u64, current_era: u64) -> TestExternalities { - let extras: TestExternalities = map![ - twox_128(&Intention::len_key()).to_vec() => vec![].and(&3u32), - twox_128(&Intention::key_for(0)).to_vec() => Alice.to_raw_public_vec(), - twox_128(&Intention::key_for(1)).to_vec() => Bob.to_raw_public_vec(), - twox_128(&Intention::key_for(2)).to_vec() => Charlie.to_raw_public_vec(), - twox_128(SessionsPerEra::key()).to_vec() => vec![].and(&sessions_per_era), - twox_128(ValidatorCount::key()).to_vec() => vec![].and(&3u64), - twox_128(BondingDuration::key()).to_vec() => vec![].and(&0u64), - twox_128(TransactionFee::key()).to_vec() => vec![].and(&1u64), - twox_128(CurrentEra::key()).to_vec() => vec![].and(¤t_era), - twox_128(&FreeBalanceOf::key_for(*Alice)).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0] - ]; - session::testing::externalities(session_length).into_iter().chain(extras.into_iter()).collect() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use super::internal::*; - use super::privileged::*; - - use runtime_io::{with_externalities, twox_128, TestExternalities}; - use codec::{KeyedVec, Joiner}; - use keyring::Keyring::*; - use demo_primitives::AccountId; - use runtime::{staking, session}; - use runtime::democracy::PrivPass; - use runtime::staking::public::{Call, Dispatch}; - use runtime::staking::privileged::{Call as PCall, Dispatch as PDispatch}; - - #[test] - fn staking_should_work() { - let mut t: TestExternalities = map![ - twox_128(session::SessionLength::key()).to_vec() => vec![].and(&1u64), - twox_128(session::Validators::key()).to_vec() => vec![].and(&vec![[10u8; 32], [20; 32]]), - twox_128(CurrentEra::key()).to_vec() => vec![].and(&0u64), - twox_128(SessionsPerEra::key()).to_vec() => vec![].and(&2u64), - twox_128(ValidatorCount::key()).to_vec() => vec![].and(&2u32), - twox_128(BondingDuration::key()).to_vec() => vec![].and(&3u64), - twox_128(TotalStake::key()).to_vec() => vec![].and(&100u64), - twox_128(TransactionFee::key()).to_vec() => vec![].and(&0u64), - twox_128(&FreeBalanceOf::key_for(*Alice)).to_vec() => vec![].and(&10u64), - twox_128(&FreeBalanceOf::key_for(*Bob)).to_vec() => vec![].and(&20u64), - twox_128(&FreeBalanceOf::key_for(*Charlie)).to_vec() => vec![].and(&30u64), - twox_128(&FreeBalanceOf::key_for(*Dave)).to_vec() => vec![].and(&40u64) - ]; - - with_externalities(&mut t, || { - assert_eq!(era_length(), 2u64); - assert_eq!(ValidatorCount::get(), 2); - assert_eq!(BondingDuration::get(), 3); - assert_eq!(session::validators(), vec![[10u8; 32], [20u8; 32]]); - - // Block 1: Add three validators. No obvious change. - system::testing::set_block_number(1); - public::Call::stake().dispatch(PublicPass::new(&Alice)); - PublicPass::new(&Bob).stake(); - PublicPass::new(&Dave).stake(); - check_new_era(); - assert_eq!(session::validators(), vec![[10u8; 32], [20u8; 32]]); - - // Block 2: New validator set now. - system::testing::set_block_number(2); - check_new_era(); - assert_eq!(session::validators(), vec![Dave.to_raw_public(), Bob.into()]); - - // Block 3: Unstake highest, introduce another staker. No change yet. - system::testing::set_block_number(3); - PublicPass::new(&Charlie).stake(); - PublicPass::new(&Dave).unstake(); - check_new_era(); - - // Block 4: New era - validators change. - system::testing::set_block_number(4); - check_new_era(); - assert_eq!(session::validators(), vec![Charlie.to_raw_public(), Bob.into()]); - - // Block 5: Transfer stake from highest to lowest. No change yet. - system::testing::set_block_number(5); - PublicPass::new(&Dave).transfer(Alice.to_raw_public(), 40); - check_new_era(); - - // Block 6: Lowest now validator. - system::testing::set_block_number(6); - check_new_era(); - assert_eq!(session::validators(), vec![Alice.to_raw_public(), Charlie.into()]); - - // Block 7: Unstake three. No change yet. - system::testing::set_block_number(7); - PublicPass::new(&Charlie).unstake(); - check_new_era(); - assert_eq!(session::validators(), vec![Alice.to_raw_public(), Charlie.into()]); - - // Block 8: Back to one and two. - system::testing::set_block_number(8); - check_new_era(); - assert_eq!(session::validators(), vec![Alice.to_raw_public(), Bob.into()]); - }); - } - - #[test] - fn staking_eras_work() { - let mut t: TestExternalities = map![ - twox_128(session::SessionLength::key()).to_vec() => vec![].and(&1u64), - twox_128(SessionsPerEra::key()).to_vec() => vec![].and(&2u64), - twox_128(ValidatorCount::key()).to_vec() => vec![].and(&2u32), - twox_128(CurrentEra::key()).to_vec() => vec![].and(&0u64) - ]; - with_externalities(&mut t, || { - assert_eq!(era_length(), 2u64); - assert_eq!(SessionsPerEra::get(), 2u64); - assert_eq!(LastEraLengthChange::get(), 0u64); - assert_eq!(CurrentEra::get(), 0u64); - - // Block 1: No change. - system::testing::set_block_number(1); - check_new_era(); - assert_eq!(SessionsPerEra::get(), 2u64); - assert_eq!(LastEraLengthChange::get(), 0u64); - assert_eq!(CurrentEra::get(), 0u64); - - // Block 2: Simple era change. - system::testing::set_block_number(2); - check_new_era(); - assert_eq!(SessionsPerEra::get(), 2u64); - assert_eq!(LastEraLengthChange::get(), 0u64); - assert_eq!(CurrentEra::get(), 1u64); - - // Block 3: Schedule an era length change; no visible changes. - system::testing::set_block_number(3); - PrivPass::test().set_sessions_per_era(3); - check_new_era(); - assert_eq!(SessionsPerEra::get(), 2u64); - assert_eq!(LastEraLengthChange::get(), 0u64); - assert_eq!(CurrentEra::get(), 1u64); - - // Block 4: Era change kicks in. - system::testing::set_block_number(4); - check_new_era(); - assert_eq!(SessionsPerEra::get(), 3u64); - assert_eq!(LastEraLengthChange::get(), 4u64); - assert_eq!(CurrentEra::get(), 2u64); - - // Block 5: No change. - system::testing::set_block_number(5); - check_new_era(); - assert_eq!(SessionsPerEra::get(), 3u64); - assert_eq!(LastEraLengthChange::get(), 4u64); - assert_eq!(CurrentEra::get(), 2u64); - - // Block 6: No change. - system::testing::set_block_number(6); - check_new_era(); - assert_eq!(SessionsPerEra::get(), 3u64); - assert_eq!(LastEraLengthChange::get(), 4u64); - assert_eq!(CurrentEra::get(), 2u64); - - // Block 7: Era increment. - system::testing::set_block_number(7); - check_new_era(); - assert_eq!(SessionsPerEra::get(), 3u64); - assert_eq!(LastEraLengthChange::get(), 4u64); - assert_eq!(CurrentEra::get(), 3u64); - }); - } - - #[test] - fn staking_balance_works() { - with_externalities(&mut testing::externalities(1, 3, 1), || { - FreeBalanceOf::insert(*Alice, 42); - assert_eq!(FreeBalanceOf::get(*Alice), 42); - assert_eq!(ReservedBalanceOf::get(*Alice), 0); - assert_eq!(balance(&Alice), 42); - assert_eq!(FreeBalanceOf::get(*Bob), 0); - assert_eq!(ReservedBalanceOf::get(*Bob), 0); - assert_eq!(balance(&Bob), 0); - }); - } - - #[test] - fn staking_balance_transfer_works() { - with_externalities(&mut testing::externalities(1, 3, 1), || { - FreeBalanceOf::insert(*Alice, 112); - PublicPass::new(&Alice).transfer(Bob.to_raw_public(), 69); - assert_eq!(balance(&Alice), 42); - assert_eq!(balance(&Bob), 69); - }); - } - - #[test] - #[should_panic] - fn staking_balance_transfer_when_bonded_panics() { - with_externalities(&mut testing::externalities(1, 3, 1), || { - FreeBalanceOf::insert(*Alice, 111); - PublicPass::new(&Alice).stake(); - PublicPass::new(&Alice).transfer(Bob.to_raw_public(), 69); - }); - } - - #[test] - fn reserving_balance_should_work() { - with_externalities(&mut testing::externalities(1, 3, 1), || { - FreeBalanceOf::insert(*Alice, 111); - - assert_eq!(balance(&Alice), 111); - assert_eq!(FreeBalanceOf::get(*Alice), 111); - assert_eq!(ReservedBalanceOf::get(*Alice), 0); - - reserve_balance(&Alice, 69); - - assert_eq!(balance(&Alice), 111); - assert_eq!(FreeBalanceOf::get(*Alice), 42); - assert_eq!(ReservedBalanceOf::get(*Alice), 69); - }); - } - - #[test] - #[should_panic] - fn staking_balance_transfer_when_reserved_panics() { - with_externalities(&mut testing::externalities(1, 3, 1), || { - FreeBalanceOf::insert(*Alice, 111); - reserve_balance(&Alice, 69); - PublicPass::new(&Alice).transfer(Bob.to_raw_public(), 69); - }); - } - - #[test] - fn deducting_balance_should_work() { - with_externalities(&mut testing::externalities(1, 3, 1), || { - FreeBalanceOf::insert(*Alice, 111); - assert!(deduct_unbonded(&Alice, 69)); - assert_eq!(FreeBalanceOf::get(*Alice), 42); - }); - } - - #[test] - fn deducting_balance_should_fail_when_bonded() { - let mut t: TestExternalities = map![ - twox_128(&FreeBalanceOf::key_for(*Alice)).to_vec() => vec![].and(&111u64), - twox_128(&BondageOf::key_for(*Alice)).to_vec() => vec![].and(&2u64) - ]; - with_externalities(&mut t, || { - system::testing::set_block_number(1); - assert_eq!(unlock_block(&Alice), LockStatus::LockedUntil(2)); - assert!(!deduct_unbonded(&Alice, 69)); - }); - } - - #[test] - fn refunding_balance_should_work() { - with_externalities(&mut testing::externalities(1, 3, 1), || { - FreeBalanceOf::insert(*Alice, 42); - refund(&Alice, 69); - assert_eq!(FreeBalanceOf::get(*Alice), 111); - }); - } - - #[test] - fn slashing_balance_should_work() { - with_externalities(&mut testing::externalities(1, 3, 1), || { - FreeBalanceOf::insert(*Alice, 111); - reserve_balance(&Alice, 69); - assert!(slash(&Alice, 69)); - assert_eq!(FreeBalanceOf::get(*Alice), 0); - assert_eq!(ReservedBalanceOf::get(*Alice), 42); - }); - } - - #[test] - fn slashing_incomplete_balance_should_work() { - with_externalities(&mut testing::externalities(1, 3, 1), || { - FreeBalanceOf::insert(*Alice, 42); - reserve_balance(&Alice, 21); - assert!(!slash(&Alice, 69)); - assert_eq!(FreeBalanceOf::get(*Alice), 0); - assert_eq!(ReservedBalanceOf::get(*Alice), 0); - }); - } - - #[test] - fn unreserving_balance_should_work() { - with_externalities(&mut testing::externalities(1, 3, 1), || { - FreeBalanceOf::insert(*Alice, 111); - reserve_balance(&Alice, 111); - unreserve_balance(&Alice, 42); - assert_eq!(ReservedBalanceOf::get(*Alice), 69); - assert_eq!(FreeBalanceOf::get(*Alice), 42); - }); - } - - #[test] - fn slashing_reserved_balance_should_work() { - with_externalities(&mut testing::externalities(1, 3, 1), || { - FreeBalanceOf::insert(*Alice, 111); - reserve_balance(&Alice, 111); - assert!(slash_reserved(&Alice, 42)); - assert_eq!(ReservedBalanceOf::get(*Alice), 69); - assert_eq!(FreeBalanceOf::get(*Alice), 0); - }); - } - - #[test] - fn slashing_incomplete_reserved_balance_should_work() { - with_externalities(&mut testing::externalities(1, 3, 1), || { - FreeBalanceOf::insert(*Alice, 111); - reserve_balance(&Alice, 42); - assert!(!slash_reserved(&Alice, 69)); - assert_eq!(FreeBalanceOf::get(*Alice), 69); - assert_eq!(ReservedBalanceOf::get(*Alice), 0); - }); - } - - #[test] - fn transferring_reserved_balance_should_work() { - with_externalities(&mut testing::externalities(1, 3, 1), || { - FreeBalanceOf::insert(*Alice, 111); - reserve_balance(&Alice, 111); - assert!(transfer_reserved_balance(&Alice, &Bob, 42)); - assert_eq!(ReservedBalanceOf::get(*Alice), 69); - assert_eq!(FreeBalanceOf::get(*Alice), 0); - assert_eq!(ReservedBalanceOf::get(*Bob), 0); - assert_eq!(FreeBalanceOf::get(*Bob), 42); - }); - } - - #[test] - fn transferring_incomplete_reserved_balance_should_work() { - with_externalities(&mut testing::externalities(1, 3, 1), || { - FreeBalanceOf::insert(*Alice, 111); - reserve_balance(&Alice, 42); - assert!(!transfer_reserved_balance(&Alice, &Bob, 69)); - assert_eq!(ReservedBalanceOf::get(*Alice), 0); - assert_eq!(FreeBalanceOf::get(*Alice), 69); - assert_eq!(ReservedBalanceOf::get(*Bob), 0); - assert_eq!(FreeBalanceOf::get(*Bob), 42); - }); - } -} diff --git a/demo/runtime/src/runtime/system.rs b/demo/runtime/src/runtime/system.rs deleted file mode 100644 index 8cd75b49ed258..0000000000000 --- a/demo/runtime/src/runtime/system.rs +++ /dev/null @@ -1,352 +0,0 @@ -// 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 . - -//! System manager: Handles all of the top-level stuff; executing block/transaction, setting code -//! and depositing logs. - -use rstd::prelude::*; -use rstd::mem; -use runtime_io::{print, storage_root, enumerated_trie_root}; -use codec::{KeyedVec, Slicable}; -use runtime_support::{Hashable, storage, StorageValue, StorageMap}; -use demo_primitives::{AccountId, Hash, TxOrder, BlockNumber, Header, Log}; -use block::{self, Block}; -use transaction::UncheckedTransaction; -use runtime::{staking, session}; -use runtime::democracy::PrivPass; -use dispatch; -use safe_mix::TripletMix; - -storage_items! { - pub Nonce get(nonce): b"sys:non" => default map [ AccountId => TxOrder ]; - pub BlockHashAt get(block_hash): b"sys:old" => required map [ BlockNumber => Hash ]; - RandomSeed get(random_seed): b"sys:rnd" => required Hash; - // The current block number being processed. Set by `execute_block`. - Number get(block_number): b"sys:num" => required BlockNumber; - ParentHash get(parent_hash): b"sys:pha" => required Hash; - TransactionsRoot get(transactions_root): b"sys:txr" => required Hash; - Digest: b"sys:dig" => default block::Digest; -} - -pub const CODE: &'static[u8] = b":code"; - -impl_dispatch! { - pub mod privileged; - fn set_code(new: Vec) = 0; -} - -impl privileged::Dispatch for PrivPass { - /// Set the new code. - fn set_code(self, new: Vec) { - storage::unhashed::put_raw(CODE, &new); - } -} - -pub mod internal { - use super::*; - - struct CheckedTransaction(UncheckedTransaction); - - /// Deposits a log and ensures it matches the blocks log data. - pub fn deposit_log(log: Log) { - let mut l = Digest::get(); - l.logs.push(log); - Digest::put(l); - } - - /// Actually execute all transitioning for `block`. - pub fn execute_block(mut block: Block) { - initialise_block(&block.header); - - // any initial checks - initial_checks(&block); - - // execute transactions - block.transactions.iter().cloned().for_each(super::execute_transaction); - - // post-transactional book-keeping. - staking::internal::check_new_era(); - session::internal::check_rotate_session(); - - // any final checks - final_checks(&block); - - // any stuff that we do after taking the storage root. - post_finalise(&block.header); - } - - /// Start the execution of a particular block. - pub fn initialise_block(mut header: &Header) { - // populate environment from header. - Number::put(header.number); - ParentHash::put(header.parent_hash); - TransactionsRoot::put(header.transaction_root); - RandomSeed::put(calculate_random()); - } - - /// Execute a transaction outside of the block execution function. - /// This doesn't attempt to validate anything regarding the block. - pub fn execute_transaction(utx: UncheckedTransaction) { - super::execute_transaction(utx); - } - - /// Finalise the block - it is up the caller to ensure that all header fields are valid - /// except state-root. - pub fn finalise_block() -> Header { - staking::internal::check_new_era(); - session::internal::check_rotate_session(); - - RandomSeed::kill(); - let header = Header { - number: Number::take(), - digest: Digest::take(), - parent_hash: ParentHash::take(), - transaction_root: TransactionsRoot::take(), - state_root: storage_root().into(), - }; - - post_finalise(&header); - - header - } -} - -fn execute_transaction(utx: UncheckedTransaction) { - use ::transaction; - - // Verify the signature is good. - let tx = match transaction::check(utx) { - Ok(tx) => tx, - Err(_) => panic!("All transactions should be properly signed"), - }; - - { - // check nonce - let expected_nonce: TxOrder = Nonce::get(&tx.signed); - assert!(tx.nonce == expected_nonce, "All transactions should have the correct nonce"); - - // increment nonce in storage - Nonce::insert(&tx.signed, &(expected_nonce + 1)); - } - - // decode parameters and dispatch - let tx = tx.drain().transaction; - tx.function.dispatch(staking::PublicPass::new(&tx.signed)); -} - -fn initial_checks(block: &Block) { - let ref header = block.header; - - // check parent_hash is correct. - assert!( - header.number > 0 && BlockHashAt::get(&(header.number - 1)) == header.parent_hash, - "Parent hash should be valid." - ); - - // check transaction trie root represents the transactions. - let txs = block.transactions.iter().map(Slicable::encode).collect::>(); - let txs = txs.iter().map(Vec::as_slice).collect::>(); - let txs_root = enumerated_trie_root(&txs).into(); - info_expect_equal_hash(&header.transaction_root, &txs_root); - assert!(header.transaction_root == txs_root, "Transaction trie root must be valid."); -} - -fn final_checks(block: &Block) { - let ref header = block.header; - - // check digest - assert!(header.digest == Digest::get()); - - // remove temporaries. - kill_temps(); - - // check storage root. - let storage_root = storage_root().into(); - info_expect_equal_hash(&header.state_root, &storage_root); - assert!(header.state_root == storage_root, "Storage root must match that calculated."); -} - -fn kill_temps() { - Number::kill(); - ParentHash::kill(); - RandomSeed::kill(); - Digest::kill(); - TransactionsRoot::kill(); -} - -fn post_finalise(header: &Header) { - // store the header hash in storage; we can't do it before otherwise there would be a - // cyclic dependency. - BlockHashAt::insert(&header.number, &header.blake2_256().into()); -} - -fn calculate_random() -> Hash { - let c = block_number() - 1; - (0..81) - .map(|i| if c >= i { block_hash(c - i) } else { Default::default() }) - .triplet_mix() -} - -#[cfg(feature = "std")] -fn info_expect_equal_hash(given: &Hash, expected: &Hash) { - use primitives::hexdisplay::HexDisplay; - if given != expected { - println!("Hash: given={}, expected={}", HexDisplay::from(&given.0), HexDisplay::from(&expected.0)); - } -} - -#[cfg(not(feature = "std"))] -fn info_expect_equal_hash(given: &Hash, expected: &Hash) { - if given != expected { - print("Hash not equal"); - print(&given.0[..]); - print(&expected.0[..]); - } -} - -#[cfg(any(feature = "std", test))] -pub mod testing { - use super::*; - use runtime_io::{twox_128, TestExternalities}; - use codec::Joiner; - - pub fn externalities() -> TestExternalities { - map![ - twox_128(&BlockHashAt::key_for(&0)).to_vec() => [69u8; 32].encode(), - twox_128(Number::key()).to_vec() => 1u64.encode(), - twox_128(ParentHash::key()).to_vec() => [69u8; 32].encode(), - twox_128(RandomSeed::key()).to_vec() => [0u8; 32].encode() - ] - } - - pub fn set_block_number(n: BlockNumber) { - Number::put(n); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use super::internal::*; - - use runtime_io::{with_externalities, twox_128, TestExternalities}; - use runtime_support::StorageValue; - use codec::{Joiner, KeyedVec, Slicable}; - use keyring::Keyring::*; - use primitives::hexdisplay::HexDisplay; - use demo_primitives::{Header, Digest}; - use transaction::{UncheckedTransaction, Transaction}; - use runtime::staking; - use dispatch::public::Call as PubCall; - use runtime::staking::public::Call as StakingCall; - - #[test] - fn staking_balance_transfer_dispatch_works() { - let mut t: TestExternalities = map![ - twox_128(&staking::FreeBalanceOf::key_for(*One)).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0], - twox_128(staking::TransactionFee::key()).to_vec() => vec![10u8, 0, 0, 0, 0, 0, 0, 0], - twox_128(&BlockHashAt::key_for(&0)).to_vec() => [69u8; 32].encode() - ]; - - let tx = UncheckedTransaction { - transaction: Transaction { - signed: One.into(), - nonce: 0, - function: PubCall::Staking(StakingCall::transfer(Two.into(), 69)), - }, - signature: hex!("3a682213cb10e8e375fe0817fe4d220a4622d910088809ed7fc8b4ea3871531dbadb22acfedd28a100a0b7bd2d274e0ff873655b13c88f4640b5569db3222706").into(), - }; - - with_externalities(&mut t, || { - internal::initialise_block(&Header::from_block_number(1)); - internal::execute_transaction(tx); - assert_eq!(staking::balance(&One), 32); - assert_eq!(staking::balance(&Two), 69); - }); - } - - fn new_test_ext() -> TestExternalities { - staking::testing::externalities(2, 2, 0) - } - - #[test] - fn block_import_works() { - let mut t = new_test_ext(); - - let h = Header { - parent_hash: [69u8; 32].into(), - number: 1, - state_root: hex!("cc3f1f5db826013193e502c76992b5e933b12367e37a269a9822b89218323e9f").into(), - transaction_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), - digest: Digest { logs: vec![], }, - }; - - let b = Block { - header: h, - transactions: vec![], - }; - - with_externalities(&mut t, || { - execute_block(b); - }); - } - - #[test] - #[should_panic] - fn block_import_of_bad_state_root_fails() { - let mut t = new_test_ext(); - - let h = Header { - parent_hash: [69u8; 32].into(), - number: 1, - state_root: [0u8; 32].into(), - transaction_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), - digest: Digest { logs: vec![], }, - }; - - let b = Block { - header: h, - transactions: vec![], - }; - - with_externalities(&mut t, || { - execute_block(b); - }); - } - - #[test] - #[should_panic] - fn block_import_of_bad_transaction_root_fails() { - let mut t = new_test_ext(); - - let h = Header { - parent_hash: [69u8; 32].into(), - number: 1, - state_root: hex!("1ab2dbb7d4868a670b181327b0b6a58dc64b10cfb9876f737a5aa014b8da31e0").into(), - transaction_root: [0u8; 32].into(), - digest: Digest { logs: vec![], }, - }; - - let b = Block { - header: h, - transactions: vec![], - }; - - with_externalities(&mut t, || { - execute_block(b); - }); - } -} diff --git a/demo/runtime/src/runtime/timestamp.rs b/demo/runtime/src/runtime/timestamp.rs deleted file mode 100644 index ed51ebabc7279..0000000000000 --- a/demo/runtime/src/runtime/timestamp.rs +++ /dev/null @@ -1,64 +0,0 @@ -// 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 . - -//! Timestamp manager: just handles the current timestamp. - -use runtime_support::storage::StorageValue; -use runtime::staking::PublicPass; - -pub type Timestamp = u64; - -storage_items! { - pub Now: b"tim:val" => required Timestamp; -} - -impl_dispatch! { - pub mod public; - fn set(now: Timestamp) = 0; -} - -impl<'a> public::Dispatch for PublicPass<'a> { - /// Set the current time. - fn set(self, now: Timestamp) { - Now::put(&now); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use super::public::*; - - use runtime_io::{with_externalities, twox_128, TestExternalities}; - use runtime_support::storage::StorageValue; - use runtime::timestamp; - use codec::{Joiner, KeyedVec}; - use demo_primitives::AccountId; - use runtime::staking::PublicPass; - - #[test] - fn timestamp_works() { - let mut t: TestExternalities = map![ - twox_128(Now::key()).to_vec() => vec![].and(&42u64) - ]; - - with_externalities(&mut t, || { - assert_eq!(Now::get(), 42); - PublicPass::nobody().set(69); - assert_eq!(Now::get(), 69); - }); - } -} diff --git a/demo/runtime/src/transaction.rs b/demo/runtime/src/transaction.rs deleted file mode 100644 index de769b5a16cef..0000000000000 --- a/demo/runtime/src/transaction.rs +++ /dev/null @@ -1,188 +0,0 @@ -// 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 . - -//! Transaction type. - -use rstd::prelude::*; -use rstd::ops; -use codec::{Input, Slicable}; -use demo_primitives::{AccountId, TxOrder, Signature}; -use dispatch::PubCall; - -#[cfg(feature = "std")] -use std::fmt; - -/// A vetted and verified transaction from the external world. -#[derive(PartialEq, Eq, Clone)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] -pub struct Transaction { - /// Who signed it (note this is not a signature). - pub signed: AccountId, - /// The number of transactions have come before from the same signer. - pub nonce: TxOrder, - /// The function that should be called. - pub function: PubCall, -} - -impl Slicable for Transaction { - fn decode(input: &mut I) -> Option { - Some(Transaction { - signed: Slicable::decode(input)?, - nonce: Slicable::decode(input)?, - function: Slicable::decode(input)?, - }) - } - - fn encode(&self) -> Vec { - let mut v = Vec::new(); - - self.signed.using_encoded(|s| v.extend(s)); - self.nonce.using_encoded(|s| v.extend(s)); - self.function.using_encoded(|s| v.extend(s)); - - v - } -} - -impl ::codec::NonTrivialSlicable for Transaction {} - -/// A transactions right from the external world. Unchecked. -#[derive(Eq, Clone)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct UncheckedTransaction { - /// The actual transaction information. - pub transaction: Transaction, - /// The signature; should be an Ed25519 signature applied to the serialised `transaction` field. - pub signature: Signature, -} - -impl Slicable for UncheckedTransaction { - fn decode(input: &mut I) -> Option { - // This is a little more complicated than usual since the binary format must be compatible - // with substrate's generic `Vec` type. Basically this just means accepting that there - // will be a prefix of u32, which has the total number of bytes following (we don't need - // to use this). - let _length_do_not_remove_me_see_above: u32 = Slicable::decode(input)?; - - Some(UncheckedTransaction { - transaction: Slicable::decode(input)?, - signature: Slicable::decode(input)?, - }) - } - - fn encode(&self) -> Vec { - let mut v = Vec::new(); - - // need to prefix with the total length as u32 to ensure it's binary comptible with - // Vec. we'll make room for it here, then overwrite once we know the length. - v.extend(&[0u8; 4]); - - self.transaction.signed.using_encoded(|s| v.extend(s)); - self.transaction.nonce.using_encoded(|s| v.extend(s)); - self.transaction.function.using_encoded(|s| v.extend(s)); - self.signature.using_encoded(|s| v.extend(s)); - - let length = (v.len() - 4) as u32; - length.using_encoded(|s| v[0..4].copy_from_slice(s)); - - v - } -} - -impl ::codec::NonTrivialSlicable for UncheckedTransaction {} - -impl PartialEq for UncheckedTransaction { - fn eq(&self, other: &Self) -> bool { - self.signature.iter().eq(other.signature.iter()) && self.transaction == other.transaction - } -} - -#[cfg(feature = "std")] -impl fmt::Debug for UncheckedTransaction { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "UncheckedTransaction({:?})", self.transaction) - } -} - -/// A type-safe indicator that a transaction has been checked. -#[derive(PartialEq, Eq, Clone)] -#[cfg_attr(feature = "std", derive(Debug))] -pub struct CheckedTransaction(UncheckedTransaction); - -impl CheckedTransaction { - /// Get a reference to the checked signature. - pub fn signature(&self) -> &Signature { - &self.0.signature - } - - /// Get the inner object. - pub fn drain(self) -> UncheckedTransaction { - self.0 - } -} - -impl ops::Deref for CheckedTransaction { - type Target = Transaction; - - fn deref(&self) -> &Transaction { - &self.0.transaction - } -} - -/// Check the signature on a transaction. -/// -/// On failure, return the transaction back. -pub fn check(tx: UncheckedTransaction) -> Result { - let msg = ::codec::Slicable::encode(&tx.transaction); - if ::runtime_io::ed25519_verify(&tx.signature.0, &msg, &tx.transaction.signed) { - Ok(CheckedTransaction(tx)) - } else { - Err(tx) - } -} - - -#[cfg(test)] -mod tests { - use super::*; - use primitives; - use codec::Slicable; - use primitives::hexdisplay::HexDisplay; - use dispatch::public::Call; - use runtime::timestamp::public::Call as TimestampCall; - - #[test] - fn serialize_unchecked() { - let tx = UncheckedTransaction { - transaction: Transaction { - signed: [1; 32], - nonce: 999u64, - function: Call::Timestamp(TimestampCall::set(135135)), - }, - signature: primitives::hash::H512([0; 64]), - }; - // 71000000 - // 0101010101010101010101010101010101010101010101010101010101010101 - // e703000000000000 - // 00 - // df0f0200 - // 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - - let v = Slicable::encode(&tx); - println!("{}", HexDisplay::from(&v)); - assert_eq!(UncheckedTransaction::decode(&mut &v[..]).unwrap(), tx); - } -} diff --git a/demo/runtime/wasm/Cargo.lock b/demo/runtime/wasm/Cargo.lock index a415dd58eb92f..bc627b63e09c6 100644 --- a/demo/runtime/wasm/Cargo.lock +++ b/demo/runtime/wasm/Cargo.lock @@ -96,6 +96,7 @@ dependencies = [ "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-primitives 0.1.0", + "substrate-runtime-primitives 0.1.0", "substrate-runtime-std 0.1.0", ] @@ -105,11 +106,21 @@ version = "0.1.0" dependencies = [ "demo-primitives 0.1.0", "integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)", + "safe-mix 0.1.0", "substrate-codec 0.1.0", "substrate-primitives 0.1.0", + "substrate-runtime-consensus 0.1.0", + "substrate-runtime-council 0.1.0", + "substrate-runtime-democracy 0.1.0", + "substrate-runtime-executive 0.1.0", "substrate-runtime-io 0.1.0", + "substrate-runtime-primitives 0.1.0", + "substrate-runtime-session 0.1.0", + "substrate-runtime-staking 0.1.0", "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]] @@ -249,6 +260,11 @@ name = "integer-sqrt" version = "0.1.0" source = "git+https://github.com/paritytech/integer-sqrt-rs.git#f4cf61482096dc98c1273f46a10849d182b4c23c" +[[package]] +name = "integer-sqrt" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "isatty" version = "0.1.6" @@ -337,6 +353,11 @@ name = "nodrop" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "num-traits" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "num_cpus" version = "1.8.0" @@ -546,6 +567,13 @@ dependencies = [ "semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "safe-mix" +version = "0.1.0" +dependencies = [ + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "scopeguard" version = "0.3.3" @@ -605,6 +633,15 @@ dependencies = [ "substrate-runtime-std 0.1.0", ] +[[package]] +name = "substrate-keyring" +version = "0.1.0" +dependencies = [ + "ed25519 0.1.0", + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "substrate-primitives" version = "0.1.0" @@ -622,6 +659,74 @@ dependencies = [ "uint 0.1.2 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)", ] +[[package]] +name = "substrate-runtime-consensus" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-primitives 0.1.0", + "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", +] + +[[package]] +name = "substrate-runtime-council" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "integer-sqrt 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "safe-mix 0.1.0", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-keyring 0.1.0", + "substrate-primitives 0.1.0", + "substrate-runtime-consensus 0.1.0", + "substrate-runtime-democracy 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-primitives 0.1.0", + "substrate-runtime-session 0.1.0", + "substrate-runtime-staking 0.1.0", + "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", + "substrate-runtime-system 0.1.0", +] + +[[package]] +name = "substrate-runtime-democracy" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "safe-mix 0.1.0", + "serde 1.0.27 (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-session 0.1.0", + "substrate-runtime-staking 0.1.0", + "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", + "substrate-runtime-system 0.1.0", +] + +[[package]] +name = "substrate-runtime-executive" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-primitives 0.1.0", + "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", + "substrate-runtime-system 0.1.0", +] + [[package]] name = "substrate-runtime-io" version = "0.1.0" @@ -636,6 +741,58 @@ dependencies = [ "triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "substrate-runtime-primitives" +version = "0.1.0" +dependencies = [ + "integer-sqrt 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-primitives 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", +] + +[[package]] +name = "substrate-runtime-session" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "safe-mix 0.1.0", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-keyring 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", + "substrate-runtime-support 0.1.0", + "substrate-runtime-system 0.1.0", +] + +[[package]] +name = "substrate-runtime-staking" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "safe-mix 0.1.0", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-keyring 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-session 0.1.0", + "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", + "substrate-runtime-system 0.1.0", +] + [[package]] name = "substrate-runtime-std" version = "0.1.0" @@ -650,12 +807,40 @@ name = "substrate-runtime-support" version = "0.1.0" dependencies = [ "ed25519 0.1.0", - "environmental 0.1.0", "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-primitives 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-std 0.1.0", +] + +[[package]] +name = "substrate-runtime-system" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "safe-mix 0.1.0", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-io 0.1.0", + "substrate-runtime-primitives 0.1.0", + "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", +] + +[[package]] +name = "substrate-runtime-timestamp" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-runtime-primitives 0.1.0", "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", ] [[package]] @@ -837,6 +1022,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd546ef520ab3745f1aae5f2cdc6de9e6498e94d1ab138b9eb3ddfbf335847fb" "checksum hex-literal-impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2ea76da4c7f1a54d01d54985566d3fdd960b2bbd7b970da024821c883c2d9631" "checksum integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)" = "" +"checksum integer-sqrt 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7536fe7f78abedb82f609d87f46f0e0ca0ad31e84597deb8dabd8ed9ad047257" "checksum isatty 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f2a233726c7bb76995cec749d59582e5664823b7245d4970354408f1d79a7a2" "checksum keccak-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f300c1f149cd9ca5214eed24f6e713a597517420fb8b15499824aa916259ec1" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" @@ -848,6 +1034,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" "checksum memorydb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "013b7e4c5e10c764936ebc6bd3662d8e3c92292d267bf6a42ef3f5cad9c793ee" "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" +"checksum num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dee092fcdf725aee04dd7da1d21debff559237d49ef1cb3e69bcb8ece44c7364" "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" "checksum odds 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)" = "4eae0151b9dacf24fcc170d9995e511669a082856a91f958a2fe380bfab3fb22" "checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" diff --git a/demo/runtime/wasm/Cargo.toml b/demo/runtime/wasm/Cargo.toml index 266b5e2d8175a..32af7412f4b2c 100644 --- a/demo/runtime/wasm/Cargo.toml +++ b/demo/runtime/wasm/Cargo.toml @@ -7,22 +7,42 @@ authors = ["Parity Technologies "] crate-type = ["cdylib"] [dependencies] +integer-sqrt = { git = "https://github.com/paritytech/integer-sqrt-rs.git", branch = "master" } +safe-mix = { path = "../../../safe-mix", default-features = false } substrate-codec = { path = "../../../substrate/codec", default-features = false } +substrate-primitives = { path = "../../../substrate/primitives", default-features = false } substrate-runtime-std = { path = "../../../substrate/runtime-std", default-features = false } substrate-runtime-io = { path = "../../../substrate/runtime-io", default-features = false } substrate-runtime-support = { path = "../../../substrate/runtime-support", default-features = false } -substrate-primitives = { path = "../../../substrate/primitives", default-features = false } +substrate-runtime-consensus = { path = "../../../substrate/runtime/consensus", default-features = false } +substrate-runtime-council = { path = "../../../substrate/runtime/council", default-features = false } +substrate-runtime-democracy = { path = "../../../substrate/runtime/democracy", default-features = false } +substrate-runtime-executive = { path = "../../../substrate/runtime/executive", default-features = false } +substrate-runtime-primitives = { path = "../../../substrate/runtime/primitives", default-features = false } +substrate-runtime-session = { path = "../../../substrate/runtime/session", default-features = false } +substrate-runtime-staking = { path = "../../../substrate/runtime/staking", default-features = false } +substrate-runtime-system = { path = "../../../substrate/runtime/system", default-features = false } +substrate-runtime-timestamp = { path = "../../../substrate/runtime/timestamp", default-features = false } demo-primitives = { path = "../../primitives", default-features = false } -integer-sqrt = { git = "https://github.com/paritytech/integer-sqrt-rs.git", branch = "master" } [features] default = [] std = [ + "safe-mix/std", "substrate-codec/std", - "substrate-runtime-io/std", + "substrate-primitives/std", "substrate-runtime-std/std", + "substrate-runtime-io/std", "substrate-runtime-support/std", - "substrate-primitives/std", + "substrate-runtime-consensus/std", + "substrate-runtime-council/std", + "substrate-runtime-democracy/std", + "substrate-runtime-executive/std", + "substrate-runtime-primitives/std", + "substrate-runtime-session/std", + "substrate-runtime-staking/std", + "substrate-runtime-system/std", + "substrate-runtime-timestamp/std", "demo-primitives/std", ] diff --git a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm index 5cae06b23b4b1..e380c8c8f1656 100644 Binary files a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm and b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm differ diff --git a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm old mode 100755 new mode 100644 index d1c274e15e414..528b6f61ec2d1 Binary files a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm and b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm differ diff --git a/polkadot/cli/Cargo.toml b/polkadot/cli/Cargo.toml index 8fa34c3a02eea..c7c10f9e5d970 100644 --- a/polkadot/cli/Cargo.toml +++ b/polkadot/cli/Cargo.toml @@ -12,7 +12,7 @@ log = "0.3" hex-literal = "0.1" triehash = "0.1" ed25519 = { path = "../../substrate/ed25519" } -app_dirs = "1.2" +app_dirs = "1.2.1" substrate-client = { path = "../../substrate/client" } substrate-codec = { path = "../../substrate/codec" } substrate-runtime-io = { path = "../../substrate/runtime-io" } diff --git a/polkadot/consensus/Cargo.toml b/polkadot/consensus/Cargo.toml index df49b2a1cfdb8..103ae1a9132e7 100644 --- a/polkadot/consensus/Cargo.toml +++ b/polkadot/consensus/Cargo.toml @@ -18,8 +18,8 @@ polkadot-transaction-pool = { path = "../transaction-pool" } substrate-bft = { path = "../../substrate/bft" } substrate-codec = { path = "../../substrate/codec" } substrate-primitives = { path = "../../substrate/primitives" } +substrate-runtime-support = { path = "../../substrate/runtime-support" } substrate-network = { path = "../../substrate/network" } - tokio-core = "0.1.12" substrate-keyring = { path = "../../substrate/keyring" } substrate-client = { path = "../../substrate/client" } diff --git a/polkadot/consensus/src/lib.rs b/polkadot/consensus/src/lib.rs index 8ee93bafa81ef..ba1e05b05e1ff 100644 --- a/polkadot/consensus/src/lib.rs +++ b/polkadot/consensus/src/lib.rs @@ -41,6 +41,7 @@ extern crate polkadot_transaction_pool as transaction_pool; extern crate substrate_bft as bft; extern crate substrate_codec as codec; extern crate substrate_primitives as primitives; +extern crate substrate_runtime_support as runtime_support; extern crate substrate_network; extern crate tokio_core; @@ -58,6 +59,7 @@ use std::sync::Arc; use codec::Slicable; use table::{Table, Context as TableContextTrait}; use table::generic::Statement as GenericStatement; +use runtime_support::Hashable; use polkadot_api::{PolkadotApi, BlockBuilder}; use polkadot_primitives::{Hash, Timestamp}; use polkadot_primitives::block::Block as PolkadotBlock; @@ -480,7 +482,7 @@ impl bft::ProposerFactory for ProposerFactory type Error = Error; fn init(&self, parent_header: &SubstrateHeader, authorities: &[AuthorityId], sign_with: Arc) -> Result { - let parent_hash = parent_header.hash(); + let parent_hash = parent_header.blake2_256().into(); let checked_id = self.client.check_id(BlockId::Hash(parent_hash))?; let duty_roster = self.client.duty_roster(&checked_id)?; diff --git a/polkadot/consensus/src/service.rs b/polkadot/consensus/src/service.rs index 6ef1aa8389217..db8c4ee7a5059 100644 --- a/polkadot/consensus/src/service.rs +++ b/polkadot/consensus/src/service.rs @@ -26,6 +26,7 @@ use parking_lot::Mutex; use substrate_network as net; use tokio_core::reactor; use client::BlockchainEvents; +use runtime_support::Hashable; use primitives::{Hash, AuthorityId}; use primitives::block::{Id as BlockId, HeaderHash, Header}; use polkadot_primitives::parachain::{BlockData, Extrinsic, CandidateReceipt}; @@ -154,7 +155,7 @@ impl Service { }; let bft_service = BftService::new(client.clone(), key, factory); let build_bft = |header: &Header| -> Result<_, Error> { - let hash = header.hash(); + let hash = header.blake2_256().into(); let authorities = client.authorities(&BlockId::Hash(hash))?; let input = network.bft_messages() .filter_map(move |message| { diff --git a/polkadot/primitives/src/block.rs b/polkadot/primitives/src/block.rs index b2d90a19c09d2..e74dd19f4c59b 100644 --- a/polkadot/primitives/src/block.rs +++ b/polkadot/primitives/src/block.rs @@ -49,7 +49,7 @@ impl Slicable for Log { } } -impl ::codec::NonTrivialSlicable for Log { } + /// The digest of a block, useful for light-clients. #[derive(Clone, Default, PartialEq, Eq)] diff --git a/polkadot/primitives/src/parachain.rs b/polkadot/primitives/src/parachain.rs index 34a8b34733251..e94ba03d28590 100644 --- a/polkadot/primitives/src/parachain.rs +++ b/polkadot/primitives/src/parachain.rs @@ -19,7 +19,7 @@ #[cfg(feature = "std")] use primitives::bytes; use primitives; -use codec::{Input, Slicable, NonTrivialSlicable}; +use codec::{Input, Slicable}; use rstd::cmp::{PartialOrd, Ord, Ordering}; use rstd::vec::Vec; use ::Hash; @@ -59,7 +59,7 @@ pub enum Chain { impl Slicable for Chain { fn decode(input: &mut I) -> Option { - let disc = try_opt!(u8::decode(input)); + let disc = input.read_byte()?; match disc { 0 => Some(Chain::Relay), @@ -71,9 +71,9 @@ impl Slicable for Chain { fn encode(&self) -> Vec { let mut v = Vec::new(); match *self { - Chain::Relay => { 0u8.using_encoded(|s| v.extend(s)); } + Chain::Relay => { v.push(0); } Chain::Parachain(id) => { - 1u8.using_encoded(|s| v.extend(s)); + v.push(1u8); id.using_encoded(|s| v.extend(s)); } } @@ -86,7 +86,7 @@ impl Slicable for Chain { } } -impl NonTrivialSlicable for Chain { } + /// The duty roster specifying what jobs each validator must do. #[derive(Clone, PartialEq)] @@ -317,7 +317,7 @@ impl Slicable for Statement { } fn decode(value: &mut I) -> Option { - match u8::decode(value) { + match value.read_byte() { Some(x) if x == StatementKind::Candidate as u8 => { Slicable::decode(value).map(Statement::Candidate) } diff --git a/polkadot/primitives/src/transaction.rs b/polkadot/primitives/src/transaction.rs index f13cf223a93e2..f3637034b403d 100644 --- a/polkadot/primitives/src/transaction.rs +++ b/polkadot/primitives/src/transaction.rs @@ -94,7 +94,7 @@ pub enum Proposal { impl Slicable for Proposal { fn decode(input: &mut I) -> Option { - let id = try_opt!(u8::decode(input).and_then(InternalFunctionId::from_u8)); + let id = InternalFunctionId::from_u8(input.read_byte()?)?; let function = match id { InternalFunctionId::SystemSetCode => Proposal::SystemSetCode(try_opt!(Slicable::decode(input))), @@ -119,33 +119,33 @@ impl Slicable for Proposal { let mut v = Vec::new(); match *self { Proposal::SystemSetCode(ref data) => { - (InternalFunctionId::SystemSetCode as u8).using_encoded(|s| v.extend(s)); + v.push(InternalFunctionId::SystemSetCode as u8); data.using_encoded(|s| v.extend(s)); } Proposal::SessionSetLength(ref data) => { - (InternalFunctionId::SessionSetLength as u8).using_encoded(|s| v.extend(s)); + v.push(InternalFunctionId::SessionSetLength as u8); data.using_encoded(|s| v.extend(s)); } Proposal::SessionForceNewSession => { - (InternalFunctionId::SessionForceNewSession as u8).using_encoded(|s| v.extend(s)); + v.push(InternalFunctionId::SessionForceNewSession as u8); } Proposal::StakingSetSessionsPerEra(ref data) => { - (InternalFunctionId::StakingSetSessionsPerEra as u8).using_encoded(|s| v.extend(s)); + v.push(InternalFunctionId::StakingSetSessionsPerEra as u8); data.using_encoded(|s| v.extend(s)); } Proposal::StakingSetBondingDuration(ref data) => { - (InternalFunctionId::StakingSetBondingDuration as u8).using_encoded(|s| v.extend(s)); + v.push(InternalFunctionId::StakingSetBondingDuration as u8); data.using_encoded(|s| v.extend(s)); } Proposal::StakingSetValidatorCount(ref data) => { - (InternalFunctionId::StakingSetValidatorCount as u8).using_encoded(|s| v.extend(s)); + v.push(InternalFunctionId::StakingSetValidatorCount as u8); data.using_encoded(|s| v.extend(s)); } Proposal::StakingForceNewEra => { - (InternalFunctionId::StakingForceNewEra as u8).using_encoded(|s| v.extend(s)); + v.push(InternalFunctionId::StakingForceNewEra as u8); } Proposal::GovernanceSetApprovalPpmRequired(ref data) => { - (InternalFunctionId::GovernanceSetApprovalPpmRequired as u8).using_encoded(|s| v.extend(s)); + v.push(InternalFunctionId::GovernanceSetApprovalPpmRequired as u8); data.using_encoded(|s| v.extend(s)); } } @@ -267,7 +267,7 @@ impl Function { impl Slicable for Function { fn decode(input: &mut I) -> Option { - let id = try_opt!(u8::decode(input).and_then(FunctionId::from_u8)); + let id = FunctionId::from_u8(input.read_byte()?)?; Some(match id { FunctionId::TimestampSet => Function::Inherent(InherentFunction::TimestampSet(try_opt!(Slicable::decode(input)))), @@ -293,34 +293,34 @@ impl Slicable for Function { let mut v = Vec::new(); match *self { Function::Inherent(InherentFunction::TimestampSet(ref data)) => { - (FunctionId::TimestampSet as u8).using_encoded(|s| v.extend(s)); + v.push(FunctionId::TimestampSet as u8); data.using_encoded(|s| v.extend(s)); } Function::SessionSetKey(ref data) => { - (FunctionId::SessionSetKey as u8).using_encoded(|s| v.extend(s)); + v.push(FunctionId::SessionSetKey as u8); data.using_encoded(|s| v.extend(s)); } Function::StakingStake => { - (FunctionId::StakingStake as u8).using_encoded(|s| v.extend(s)); + v.push(FunctionId::StakingStake as u8); } Function::StakingUnstake => { - (FunctionId::StakingUnstake as u8).using_encoded(|s| v.extend(s)); + v.push(FunctionId::StakingUnstake as u8); } Function::ReportMisbehavior(ref report) => { - (FunctionId::StakingReportMisbehavior as u8).using_encoded(|s| v.extend(s)); + v.push(FunctionId::StakingReportMisbehavior as u8); report.using_encoded(|s| v.extend(s)); } Function::StakingTransfer(ref to, ref amount) => { - (FunctionId::StakingTransfer as u8).using_encoded(|s| v.extend(s)); + v.push(FunctionId::StakingTransfer as u8); to.using_encoded(|s| v.extend(s)); amount.using_encoded(|s| v.extend(s)); } Function::GovernancePropose(ref data) => { - (FunctionId::GovernancePropose as u8).using_encoded(|s| v.extend(s)); + v.push(FunctionId::GovernancePropose as u8); data.using_encoded(|s| v.extend(s)); } Function::GovernanceApprove(ref data) => { - (FunctionId::GovernanceApprove as u8).using_encoded(|s| v.extend(s)); + v.push(FunctionId::GovernanceApprove as u8); data.using_encoded(|s| v.extend(s)); } } @@ -365,7 +365,7 @@ impl Slicable for Transaction { } } -impl ::codec::NonTrivialSlicable for Transaction {} + /// A transactions right from the external world. Unchecked. #[derive(Eq, Clone)] @@ -441,7 +441,7 @@ impl Slicable for UncheckedTransaction { } } -impl ::codec::NonTrivialSlicable for UncheckedTransaction {} + impl PartialEq for UncheckedTransaction { fn eq(&self, other: &Self) -> bool { diff --git a/polkadot/runtime/wasm/Cargo.lock b/polkadot/runtime/wasm/Cargo.lock index d2bf5b3e7875e..7c2e7ef05be4a 100644 --- a/polkadot/runtime/wasm/Cargo.lock +++ b/polkadot/runtime/wasm/Cargo.lock @@ -654,8 +654,9 @@ name = "substrate-runtime-support" version = "0.1.0" dependencies = [ "ed25519 0.1.0", - "environmental 0.1.0", "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-io 0.1.0", diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm index a752fb6e21971..14e9b61cdb68d 100644 Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm differ diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm old mode 100755 new mode 100644 index 2dd5cac8235c7..daf9b0468286b Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm differ diff --git a/publish-wasm.sh b/publish-wasm.sh index 8424817c66d72..7ac79a99bf548 100755 --- a/publish-wasm.sh +++ b/publish-wasm.sh @@ -4,7 +4,7 @@ set -e REPO="github.com/paritytech/polkadot-wasm-bin.git" REPO_AUTH="${GH_TOKEN}:@${REPO}" -SRCS=( "polkadot/runtime/wasm" "substrate/executor/wasm" "substrate/test-runtime/wasm" ) +SRCS=( "polkadot/runtime/wasm" "substrate/executor/wasm" "demo/runtime/wasm" "substrate/test-runtime/wasm" ) DST=".wasm-binaries" TARGET="wasm32-unknown-unknown" UTCDATE=`date -u "+%Y%m%d.%H%M%S.0"` diff --git a/safe-mix/Cargo.toml b/safe-mix/Cargo.toml new file mode 100644 index 0000000000000..23a40f81ce505 --- /dev/null +++ b/safe-mix/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "safe-mix" +version = "0.1.0" +authors = ["Parity Technologies "] +build = "build.rs" + +[build-dependencies] +rustc_version = "0.2" + +[features] +default = ["std"] +std = [] +nightly = [] +strict = [] diff --git a/safe-mix/build.rs b/safe-mix/build.rs new file mode 100644 index 0000000000000..55688bad9cc51 --- /dev/null +++ b/safe-mix/build.rs @@ -0,0 +1,14 @@ +//! Set a nightly feature + +extern crate rustc_version; +use rustc_version::{version, version_meta, Channel}; + +fn main() { + // Assert we haven't travelled back in time + assert!(version().unwrap().major >= 1); + + // Set cfg flags depending on release channel + if let Channel::Nightly = version_meta().unwrap().channel { + println!("cargo:rustc-cfg=feature=\"nightly\""); + } +} diff --git a/demo/runtime/src/safe_mix.rs b/safe-mix/src/lib.rs similarity index 95% rename from demo/runtime/src/safe_mix.rs rename to safe-mix/src/lib.rs index 7512d8db81a2d..05e8bad95a7aa 100644 --- a/demo/runtime/src/safe_mix.rs +++ b/safe-mix/src/lib.rs @@ -18,7 +18,14 @@ //! //! Described in http://www.cs.huji.ac.il/~nati/PAPERS/coll_coin_fl.pdf -use rstd::ops::{BitAnd, BitOr}; +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(feature = "std"), feature(core_intrinsics))] + +#[cfg(feature = "std")] +use std::ops::{BitAnd, BitOr}; + +#[cfg(not(feature = "std"))] +use core::ops::{BitAnd, BitOr}; pub const MAX_DEPTH: usize = 17; diff --git a/substrate/bft/Cargo.toml b/substrate/bft/Cargo.toml index 5d9fa23c63406..4c5978429f5cb 100644 --- a/substrate/bft/Cargo.toml +++ b/substrate/bft/Cargo.toml @@ -7,6 +7,7 @@ authors = ["Parity Technologies "] futures = "0.1.17" substrate-codec = { path = "../codec" } substrate-primitives = { path = "../primitives" } +substrate-runtime-support = { path = "../runtime-support" } ed25519 = { path = "../ed25519" } tokio-timer = "0.1.2" parking_lot = "0.4" diff --git a/substrate/bft/src/lib.rs b/substrate/bft/src/lib.rs index 110d14ee1b329..46f8f8fb3f5e6 100644 --- a/substrate/bft/src/lib.rs +++ b/substrate/bft/src/lib.rs @@ -21,6 +21,7 @@ pub mod generic; extern crate substrate_codec as codec; extern crate substrate_primitives as primitives; +extern crate substrate_runtime_support as runtime_support; extern crate ed25519; extern crate tokio_timer; extern crate parking_lot; @@ -37,6 +38,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use codec::Slicable; use ed25519::LocalizedSignature; +use runtime_support::Hashable; use primitives::bft::{Message as PrimitiveMessage, Action as PrimitiveAction, Justification as PrimitiveJustification}; use primitives::block::{Block, Id as BlockId, Header, HeaderHash}; use primitives::AuthorityId; @@ -176,7 +178,7 @@ impl generic::Context for BftInstance

{ } fn candidate_digest(&self, proposal: &Block) -> HeaderHash { - proposal.header.hash() + proposal.header.blake2_256().into() } fn sign_local(&self, message: Message) -> LocalizedMessage { @@ -334,7 +336,7 @@ impl BftService InStream: Stream::Proposer as Proposer>::Error>, OutSink: Sink::Proposer as Proposer>::Error>, { - let hash = header.hash(); + let hash = header.blake2_256().into(); let mut _preempted_consensus = None; // defers drop of live to the end. let authorities = self.client.authorities(&BlockId::Hash(hash))?; @@ -521,7 +523,7 @@ pub fn sign_message(message: Message, key: &ed25519::Pair, parent_hash: HeaderHa match message { ::generic::Message::Propose(r, proposal) => { - let header_hash = proposal.header.hash(); + let header_hash: HeaderHash = proposal.header.blake2_256().into(); let action_header = PrimitiveAction::ProposeHeader(r as u32, header_hash.clone()); let action_propose = PrimitiveAction::Propose(r as u32, proposal.clone()); @@ -664,11 +666,11 @@ mod tests { let service = make_service(client); let first = Header::from_block_number(2); - let first_hash = first.hash(); + let first_hash = first.blake2_256().into(); let mut second = Header::from_block_number(3); second.parent_hash = first_hash; - let second_hash = second.hash(); + let second_hash = second.blake2_256().into(); let bft = service.build_upon(&first, stream::empty(), Output(Default::default())).unwrap(); assert!(service.live_agreements.lock().contains_key(&first_hash)); diff --git a/substrate/client/src/client.rs b/substrate/client/src/client.rs index 4b803486c006d..a0c54097fa8a2 100644 --- a/substrate/client/src/client.rs +++ b/substrate/client/src/client.rs @@ -271,7 +271,7 @@ impl Client where ) -> error::Result { let authorities = self.authorities_at(&BlockId::Hash(header.parent_hash))?; let just = bft::check_justification(&authorities[..], header.parent_hash, justification) - .map_err(|_| error::ErrorKind::BadJustification(BlockId::Hash(header.hash())))?; + .map_err(|_| error::ErrorKind::BadJustification(BlockId::Hash(header.blake2_256().into())))?; Ok(JustifiedHeader { header, justification: just, @@ -453,7 +453,7 @@ mod tests { // TODO: remove this in favor of custom verification pipelines for the // client fn justify(header: &block::Header) -> bft::UncheckedJustification { - let hash = header.hash(); + let hash = header.blake2_256().into(); let authorities = vec![ Keyring::Alice.into(), Keyring::Bob.into(), diff --git a/substrate/codec/src/lib.rs b/substrate/codec/src/lib.rs index 15ff92b4f6694..7a9f17baeb510 100644 --- a/substrate/codec/src/lib.rs +++ b/substrate/codec/src/lib.rs @@ -27,6 +27,6 @@ mod slicable; mod joiner; mod keyedvec; -pub use self::slicable::{Input, Slicable, NonTrivialSlicable}; +pub use self::slicable::{Input, Slicable}; pub use self::joiner::Joiner; pub use self::keyedvec::KeyedVec; diff --git a/substrate/codec/src/slicable.rs b/substrate/codec/src/slicable.rs index 3a312df9ce4a3..592a348c194fd 100644 --- a/substrate/codec/src/slicable.rs +++ b/substrate/codec/src/slicable.rs @@ -24,6 +24,16 @@ use super::joiner::Joiner; pub trait Input { /// Read into the provided input slice. Returns the number of bytes read. fn read(&mut self, into: &mut [u8]) -> usize; + + /// Read a single byte from the input. + fn read_byte(&mut self) -> Option { + let mut buf = [0u8]; + match self.read(&mut buf[..]) { + 0 => None, + 1 => Some(buf[0]), + _ => unreachable!(), + } + } } impl<'a> Input for &'a [u8] { @@ -51,29 +61,25 @@ pub trait Slicable: Sized { } } -/// Trait to mark that a type is not trivially (essentially "in place") serialisable. -// TODO: under specialization, remove this and simply specialize in place serializable types. -pub trait NonTrivialSlicable: Slicable {} - impl Slicable for Option { fn decode(input: &mut I) -> Option { - u8::decode(input).and_then(|v| match v { + match input.read_byte()? { 0 => Some(Some(false)), 1 => Some(Some(true)), 2 => Some(None), _ => None, - }) + } } fn using_encoded R>(&self, f: F) -> R { - match *self { + f(&[match *self { Some(false) => 0u8, Some(true) => 1u8, None => 2u8, - }.using_encoded(f) + }]) } } -impl NonTrivialSlicable for Option {} + impl Slicable for Box { fn decode(input: &mut I) -> Option { @@ -108,89 +114,7 @@ impl Slicable for Vec { } } -// TODO: implement for all primitives. -impl Slicable for Vec { - fn decode(input: &mut I) -> Option { - u32::decode(input).and_then(move |len| { - let len = len as usize; - let mut vec = Vec::with_capacity(len); - for _ in 0..len { - vec.push(u64::decode(input)?); - } - Some(vec) - }) - } - - fn encode(&self) -> Vec { - let len = self.len(); - assert!(len <= u32::max_value() as usize, "Attempted to serialize vec with too many elements."); - - // TODO: optimise - no need to create a new vec and copy - can just reserve and encode in place - let mut r: Vec = Vec::new().and(&(len as u32)); - for i in self.iter() { - r.extend(&i.encode()); - } - r - } -} - -// TODO: use a BitVec-like representation. -impl Slicable for Vec { - fn decode(input: &mut I) -> Option { - >::decode(input).map(|a| a.into_iter().map(|b| b != 0).collect()) - } - - fn encode(&self) -> Vec { - >::encode(&self.iter().map(|&b| if b {1} else {0}).collect()) - } -} - -macro_rules! impl_vec_simple_array { - ($($size:expr),*) => { - $( - impl Slicable for Vec<[T; $size]> - where [T; $size]: Slicable - { - fn decode(input: &mut I) -> Option { - u32::decode(input).and_then(move |len| { - let mut r = Vec::with_capacity(len as usize); - for _ in 0..len { - r.push(match Slicable::decode(input) { - Some(x) => x, - None => return None, - }); - } - - Some(r) - }) - } - - fn using_encoded R>(&self, f: F) -> R { - f(&self.encode()) - } - - fn encode(&self) -> Vec { - use rstd::iter::Extend; - - let len = self.len(); - assert!(len <= u32::max_value() as usize, "Attempted to serialize vec with too many elements."); - - let mut r: Vec = Vec::new().and(&(len as u32)); - for item in self { - r.extend(item.encode()); - } - r - } - } - )* - } -} - -impl_vec_simple_array!(1, 2, 4, 8, 16, 32, 64); - -impl NonTrivialSlicable for Vec where Vec: Slicable {} - -impl Slicable for Vec { +impl Slicable for Vec { fn decode(input: &mut I) -> Option { u32::decode(input).and_then(move |len| { let mut r = Vec::with_capacity(len as usize); @@ -219,6 +143,8 @@ impl Slicable for Vec { } } + + impl Slicable for () { fn decode(_: &mut I) -> Option<()> { Some(()) @@ -248,7 +174,7 @@ macro_rules! tuple_impl { } } - impl<$one: NonTrivialSlicable> NonTrivialSlicable for ($one,) { } + }; ($first:ident, $($rest:ident,)+) => { impl<$first: Slicable, $($rest: Slicable),+> @@ -284,11 +210,6 @@ macro_rules! tuple_impl { } } - impl<$first: Slicable, $($rest: Slicable),+> - NonTrivialSlicable - for ($first, $($rest),+) - { } - tuple_impl!($($rest,)+); } } @@ -297,7 +218,7 @@ macro_rules! tuple_impl { mod inner_tuple_impl { use rstd::vec::Vec; - use super::{Input, Slicable, NonTrivialSlicable}; + use super::{Input, Slicable}; tuple_impl!(A, B, C, D, E, F, G, H, I, J, K,); } @@ -399,7 +320,7 @@ macro_rules! impl_non_endians { } impl_endians!(u16, u32, u64, usize, i16, i32, i64, isize); -impl_non_endians!(u8, i8, [u8; 1], [u8; 2], [u8; 3], [u8; 4], [u8; 5], [u8; 6], [u8; 7], [u8; 8], +impl_non_endians!(i8, [u8; 1], [u8; 2], [u8; 3], [u8; 4], [u8; 5], [u8; 6], [u8; 7], [u8; 8], [u8; 10], [u8; 12], [u8; 14], [u8; 16], [u8; 20], [u8; 24], [u8; 28], [u8; 32], [u8; 40], [u8; 48], [u8; 56], [u8; 64], [u8; 80], [u8; 96], [u8; 112], [u8; 128], bool); diff --git a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm index 5be7858aaa131..cbc81694c0999 100644 Binary files a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm and b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm differ diff --git a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm old mode 100755 new mode 100644 index 58ff23b9bd1fc..ab23e9b4f7a2a Binary files a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm and b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm differ diff --git a/substrate/network/src/test/mod.rs b/substrate/network/src/test/mod.rs index 721640f9882aa..52bb5d57ff226 100644 --- a/substrate/network/src/test/mod.rs +++ b/substrate/network/src/test/mod.rs @@ -162,7 +162,7 @@ impl Peer { } fn justify(header: &primitives::block::Header) -> bft::UncheckedJustification { - let hash = header.hash(); + let hash = header.blake2_256().into(); let authorities = vec![ Keyring::Alice.into() ]; bft::UncheckedJustification { diff --git a/substrate/primitives/src/bft.rs b/substrate/primitives/src/bft.rs index 717ff8a4242ad..43f8903aae3b3 100644 --- a/substrate/primitives/src/bft.rs +++ b/substrate/primitives/src/bft.rs @@ -23,7 +23,7 @@ use ::{AuthorityId, Signature}; #[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "std", derive(Debug))] -#[repr(u8)] +#[repr(i8)] enum ActionKind { Propose = 1, ProposeHeader = 2, @@ -83,27 +83,27 @@ impl Slicable for Action { } fn decode(value: &mut I) -> Option { - match u8::decode(value) { - Some(x) if x == ActionKind::Propose as u8 => { + match i8::decode(value) { + Some(x) if x == ActionKind::Propose as i8 => { let (round, block) = try_opt!(Slicable::decode(value)); Some(Action::Propose(round, block)) } - Some(x) if x == ActionKind::ProposeHeader as u8 => { + Some(x) if x == ActionKind::ProposeHeader as i8 => { let (round, hash) = try_opt!(Slicable::decode(value)); Some(Action::ProposeHeader(round, hash)) } - Some(x) if x == ActionKind::Prepare as u8 => { + Some(x) if x == ActionKind::Prepare as i8 => { let (round, hash) = try_opt!(Slicable::decode(value)); Some(Action::Prepare(round, hash)) } - Some(x) if x == ActionKind::Commit as u8 => { + Some(x) if x == ActionKind::Commit as i8 => { let (round, hash) = try_opt!(Slicable::decode(value)); Some(Action::Commit(round, hash)) } - Some(x) if x == ActionKind::AdvanceRound as u8 => { + Some(x) if x == ActionKind::AdvanceRound as i8 => { Slicable::decode(value).map(Action::AdvanceRound) } _ => None, @@ -169,7 +169,7 @@ impl Slicable for Justification { } // single-byte code to represent misbehavior kind. -#[repr(u8)] +#[repr(i8)] enum MisbehaviorCode { /// BFT: double prepare. BftDoublePrepare = 0x11, @@ -178,7 +178,7 @@ enum MisbehaviorCode { } impl MisbehaviorCode { - fn from_u8(x: u8) -> Option { + fn from_i8(x: i8) -> Option { match x { 0x11 => Some(MisbehaviorCode::BftDoublePrepare), 0x12 => Some(MisbehaviorCode::BftDoubleCommit), @@ -220,7 +220,7 @@ impl Slicable for MisbehaviorReport { match self.misbehavior { MisbehaviorKind::BftDoublePrepare(ref round, (ref h_a, ref s_a), (ref h_b, ref s_b)) => { - (MisbehaviorCode::BftDoublePrepare as u8).using_encoded(|s| v.extend(s)); + (MisbehaviorCode::BftDoublePrepare as i8).using_encoded(|s| v.extend(s)); round.using_encoded(|s| v.extend(s)); h_a.using_encoded(|s| v.extend(s)); s_a.using_encoded(|s| v.extend(s)); @@ -228,7 +228,7 @@ impl Slicable for MisbehaviorReport { s_b.using_encoded(|s| v.extend(s)); } MisbehaviorKind::BftDoubleCommit(ref round, (ref h_a, ref s_a), (ref h_b, ref s_b)) => { - (MisbehaviorCode::BftDoubleCommit as u8).using_encoded(|s| v.extend(s)); + (MisbehaviorCode::BftDoubleCommit as i8).using_encoded(|s| v.extend(s)); round.using_encoded(|s| v.extend(s)); h_a.using_encoded(|s| v.extend(s)); s_a.using_encoded(|s| v.extend(s)); @@ -245,7 +245,7 @@ impl Slicable for MisbehaviorReport { let parent_number = ::block::Number::decode(input)?; let target = AuthorityId::decode(input)?; - let misbehavior = match u8::decode(input).and_then(MisbehaviorCode::from_u8)? { + let misbehavior = match i8::decode(input).and_then(MisbehaviorCode::from_i8)? { MisbehaviorCode::BftDoublePrepare => { MisbehaviorKind::BftDoublePrepare( u32::decode(input)?, diff --git a/substrate/primitives/src/block.rs b/substrate/primitives/src/block.rs index 0fd55bca403fc..6f1ec91ab76a4 100644 --- a/substrate/primitives/src/block.rs +++ b/substrate/primitives/src/block.rs @@ -21,7 +21,7 @@ use rstd::vec::Vec; #[cfg(feature = "std")] use bytes; use Hash; -use codec::{Input, Slicable, NonTrivialSlicable}; +use codec::{Input, Slicable}; /// Used to refer to a block number. pub type Number = u64; @@ -47,10 +47,6 @@ impl Slicable for Transaction { } } -impl NonTrivialSlicable for Transaction { } - - - /// Execution log (event) #[derive(PartialEq, Eq, Clone)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] @@ -66,10 +62,6 @@ impl Slicable for Log { } } -impl NonTrivialSlicable for Log { } - - - /// The digest of a block, useful for light-clients. #[derive(Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] @@ -88,11 +80,9 @@ impl Slicable for Digest { } } -impl NonTrivialSlicable for Digest { } - /// Generic types to be specialised later. pub mod generic { - use super::{Header, Slicable, Input, NonTrivialSlicable, Vec}; + use super::{Header, Slicable, Input, Vec}; /// A Block - this is generic for later specialisation in particular runtimes. #[derive(PartialEq, Eq, Clone)] @@ -107,8 +97,8 @@ pub mod generic { impl Slicable for Block where Vec: Slicable { fn decode(input: &mut I) -> Option { Some(Block { - header: try_opt!(Slicable::decode(input)), - transactions: try_opt!(Slicable::decode(input)), + header: Slicable::decode(input)?, + transactions: Slicable::decode(input)?, }) } @@ -119,8 +109,6 @@ pub mod generic { v } } - - impl NonTrivialSlicable for Block where Vec: Slicable { } } /// The body of a block is just a bunch of transactions. @@ -162,22 +150,16 @@ impl Header { digest: Default::default(), } } - - /// Get the blake2-256 hash of this header. - #[cfg(feature = "std")] - pub fn hash(&self) -> HeaderHash { - ::hashing::blake2_256(Slicable::encode(self).as_slice()).into() - } } impl Slicable for Header { fn decode(input: &mut I) -> Option { Some(Header { - parent_hash: try_opt!(Slicable::decode(input)), - number: try_opt!(Slicable::decode(input)), - state_root: try_opt!(Slicable::decode(input)), - transaction_root: try_opt!(Slicable::decode(input)), - digest: try_opt!(Slicable::decode(input)), + parent_hash: Slicable::decode(input)?, + number: Slicable::decode(input)?, + state_root: Slicable::decode(input)?, + transaction_root: Slicable::decode(input)?, + digest: Slicable::decode(input)?, }) } diff --git a/substrate/primitives/src/lib.rs b/substrate/primitives/src/lib.rs index ecf187cf910f3..626efbf78727b 100644 --- a/substrate/primitives/src/lib.rs +++ b/substrate/primitives/src/lib.rs @@ -91,7 +91,7 @@ mod tests; pub use self::hash::{H160, H256}; pub use self::uint::{U256, U512}; -pub use block::{Block, Header}; +pub use block::{Block, Header, Digest}; /// General hash type. pub type Hash = H256; diff --git a/substrate/runtime-io/src/lib.rs b/substrate/runtime-io/src/lib.rs index 5a165c62e5848..a2eb13dafd552 100644 --- a/substrate/runtime-io/src/lib.rs +++ b/substrate/runtime-io/src/lib.rs @@ -29,3 +29,37 @@ include!("../with_std.rs"); #[cfg(not(feature = "std"))] include!("../without_std.rs"); + +/// Abstraction around hashing +pub trait Hashing { + /// The hash type produced. + type Output; + + /// Produce the hash of some byte-slice. + fn hash(s: &[u8]) -> Self::Output; + /// Produce the hash of some codec-encodable value. + fn hash_of(s: &S) -> Self::Output { + codec::Slicable::using_encoded(s, Self::hash) + } + /// Produce the patricia-trie root of a mapping from indices to byte slices. + fn enumerated_trie_root(items: &[&[u8]]) -> Self::Output; + + /// Acquire the global storage root. + fn storage_root() -> Self::Output; +} + +/// Blake2-256 Hashing implementation. +pub struct BlakeTwo256; + +impl Hashing for BlakeTwo256 { + type Output = primitives::H256; + fn hash(s: &[u8]) -> Self::Output { + blake2_256(s).into() + } + fn enumerated_trie_root(items: &[&[u8]]) -> Self::Output { + enumerated_trie_root(items).into() + } + fn storage_root() -> Self::Output { + storage_root().into() + } +} diff --git a/substrate/runtime-io/with_std.rs b/substrate/runtime-io/with_std.rs index 8c1bad8d7fc52..5fda5181b06ac 100644 --- a/substrate/runtime-io/with_std.rs +++ b/substrate/runtime-io/with_std.rs @@ -17,8 +17,10 @@ #[macro_use] extern crate environmental; -extern crate substrate_state_machine; +#[cfg_attr(test, macro_use)] extern crate substrate_primitives as primitives; + +extern crate substrate_state_machine; extern crate triehash; extern crate ed25519; @@ -156,12 +158,6 @@ macro_rules! impl_stubs { mod std_tests { use super::*; - macro_rules! map { - ($( $name:expr => $value:expr ),*) => ( - vec![ $( ( $name, $value ) ),* ].into_iter().collect() - ) - } - #[test] fn storage_works() { let mut t = TestExternalities::new(); diff --git a/substrate/runtime-std/build.rs b/substrate/runtime-std/build.rs index 35eb154f3a69a..55688bad9cc51 100644 --- a/substrate/runtime-std/build.rs +++ b/substrate/runtime-std/build.rs @@ -4,11 +4,11 @@ extern crate rustc_version; use rustc_version::{version, version_meta, Channel}; fn main() { - // Assert we haven't travelled back in time - assert!(version().unwrap().major >= 1); + // Assert we haven't travelled back in time + assert!(version().unwrap().major >= 1); - // Set cfg flags depending on release channel - if let Channel::Nightly = version_meta().unwrap().channel { - println!("cargo:rustc-cfg=feature=\"nightly\""); - } + // Set cfg flags depending on release channel + if let Channel::Nightly = version_meta().unwrap().channel { + println!("cargo:rustc-cfg=feature=\"nightly\""); + } } diff --git a/substrate/runtime-std/src/lib.rs b/substrate/runtime-std/src/lib.rs index 0128ebd31ae61..b98bfb43c3bf8 100644 --- a/substrate/runtime-std/src/lib.rs +++ b/substrate/runtime-std/src/lib.rs @@ -26,6 +26,13 @@ #![cfg_attr(feature = "std", doc = "Polkadot runtime standard library as compiled when linked with Rust's standard library.")] #![cfg_attr(not(feature = "std"), doc = "Polkadot's runtime standard library as compiled without Rust's standard library.")] +#[macro_export] +macro_rules! map { + ($( $name:expr => $value:expr ),*) => ( + vec![ $( ( $name, $value ) ),* ].into_iter().collect() + ) +} + #[cfg(feature = "std")] include!("../with_std.rs"); @@ -38,4 +45,6 @@ include!("../without_std.rs"); pub mod prelude { pub use ::vec::Vec; pub use ::boxed::Box; + pub use ::cmp::{Eq, PartialEq}; + pub use ::clone::Clone; } diff --git a/substrate/runtime-std/with_std.rs b/substrate/runtime-std/with_std.rs index 2cfd266f48271..92f6654bbb94e 100644 --- a/substrate/runtime-std/with_std.rs +++ b/substrate/runtime-std/with_std.rs @@ -17,15 +17,19 @@ pub use std::borrow; pub use std::boxed; pub use std::cell; +pub use std::clone; pub use std::cmp; pub use std::fmt; pub use std::iter; +pub use std::marker; pub use std::mem; +pub use std::num; pub use std::ops; pub use std::ptr; pub use std::rc; pub use std::slice; pub use std::vec; + pub mod collections { pub use std::collections::btree_map; } diff --git a/substrate/runtime-std/without_std.rs b/substrate/runtime-std/without_std.rs index 5fc2d7526b3ff..2d05e93887315 100644 --- a/substrate/runtime-std/without_std.rs +++ b/substrate/runtime-std/without_std.rs @@ -25,16 +25,20 @@ extern crate pwasm_alloc; pub use alloc::boxed; pub use alloc::rc; pub use alloc::vec; -pub mod collections { - pub use alloc::btree_map; -} pub use core::borrow; pub use core::cell; +pub use core::clone; pub use core::cmp; +pub use core::fmt; pub use core::intrinsics; pub use core::iter; +pub use core::marker; pub use core::mem; +pub use core::num; pub use core::ops; pub use core::ptr; pub use core::slice; -pub use core::fmt; + +pub mod collections { + pub use alloc::btree_map; +} diff --git a/substrate/runtime-support/Cargo.toml b/substrate/runtime-support/Cargo.toml index 61a4b064c4a50..bef5065ef0f4a 100644 --- a/substrate/runtime-support/Cargo.toml +++ b/substrate/runtime-support/Cargo.toml @@ -6,9 +6,10 @@ authors = ["Parity Technologies "] [dependencies] ed25519 = { path = "../ed25519", optional = true } hex-literal = { version = "0.1.0", optional = true } +serde = { version = "1.0", default_features = false } +serde_derive = { version = "1.0", optional = true } substrate-runtime-std = { path = "../runtime-std", default_features = false } substrate-runtime-io = { path = "../runtime-io", default_features = false } -environmental = { path = "../environmental", optional = true } substrate-primitives = { path = "../primitives", default_features = false } substrate-codec = { path = "../codec", default_features = false } @@ -17,7 +18,8 @@ default = ["std"] std = [ "ed25519", "hex-literal", - "environmental", + "serde/std", + "serde_derive", "substrate-primitives/std", "substrate-runtime-io/std", "substrate-codec/std", diff --git a/substrate/runtime-support/src/dispatch.rs b/substrate/runtime-support/src/dispatch.rs new file mode 100644 index 0000000000000..d5d9c97f3b9c1 --- /dev/null +++ b/substrate/runtime-support/src/dispatch.rs @@ -0,0 +1,510 @@ +// 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 . + +//! Dispatch system. Just dispatches calls. + +pub use rstd::prelude::{Vec, Clone, Eq, PartialEq}; +#[cfg(feature = "std")] +pub use std::fmt; +pub use rstd::marker::PhantomData; +#[cfg(feature = "std")] +use serde; +pub use codec::{Slicable, Input}; + +pub trait Dispatchable { + type Trait; + fn dispatch(self); +} + +pub trait AuxDispatchable { + type Aux; + type Trait; + fn dispatch(self, aux: &Self::Aux); +} + +#[cfg(feature = "std")] +pub trait AuxCallable { + type Call: AuxDispatchable + Slicable + ::serde::Serialize + Clone + PartialEq + Eq; +} +#[cfg(not(feature = "std"))] +pub trait AuxCallable { + type Call: AuxDispatchable + Slicable + Clone + PartialEq + Eq; +} + +#[cfg(feature = "std")] +pub trait Callable { + type Call: Dispatchable + Slicable + ::serde::Serialize + Clone + PartialEq + Eq; +} +#[cfg(not(feature = "std"))] +pub trait Callable { + type Call: Dispatchable + Slicable + Clone + PartialEq + Eq; +} + +#[cfg(feature = "std")] +pub trait Parameter: Slicable + serde::Serialize + Clone + Eq + fmt::Debug {} + +#[cfg(feature = "std")] +impl Parameter for T where T: Slicable + serde::Serialize + Clone + Eq + fmt::Debug {} + +#[cfg(not(feature = "std"))] +pub trait Parameter: Slicable + Clone + Eq {} + +#[cfg(not(feature = "std"))] +impl Parameter for T where T: Slicable + Clone + Eq {} + + +/// Declare a struct for this module, then implement dispatch logic to create a pairing of several +/// dispatch traits and enums. +#[macro_export] +macro_rules! decl_module { + ( + pub struct $mod_type:ident<$trait_instance:ident: $trait_name:ident>; + $($rest:tt)* + ) => { + pub struct $mod_type<$trait_instance: $trait_name>($crate::dispatch::PhantomData<$trait_instance>); + decl_dispatch! { + impl for $mod_type<$trait_instance: $trait_name>; + $($rest)* + } + }; + ( + struct $mod_type:ident<$trait_instance:ident: $trait_name:ident>; + $($rest:tt)* + ) => { + struct $mod_type<$trait_instance: $trait_name>($crate::dispatch::PhantomData<$trait_instance>); + decl_dispatch! { + impl for $mod_type<$trait_instance: $trait_name>; + $($rest)* + } + } +} + +/// Implement several dispatch modules to create a pairing of a dispatch trait and enum. +#[macro_export] +macro_rules! decl_dispatch { + // WITHOUT AUX + ( + impl for $mod_type:ident<$trait_instance:ident: $trait_name:ident>; + pub enum $call_type:ident { + $( + fn $fn_name:ident( + $( + $param_name:ident : $param:ty + ),* + ) + = $id:expr ; + )* + } + $($rest:tt)* + ) => { + __decl_dispatch_module_without_aux! { + impl for $mod_type<$trait_instance: $trait_name>; + pub enum $call_type; + $( + fn $fn_name( $( $param_name: $param ),* ) = $id; + )* + } + decl_dispatch! { + impl for $mod_type<$trait_instance: $trait_name>; + $($rest)* + } + }; + // WITH AUX + ( + impl for $mod_type:ident<$trait_instance:ident: $trait_name:ident>; + pub enum $call_type:ident where aux: $aux_type:ty { + $( + fn $fn_name:ident(aux + $( + , $param_name:ident : $param:ty + )* + ) + = $id:expr ; + )* + } + $($rest:tt)* + ) => { + __decl_dispatch_module_with_aux! { + impl for $mod_type<$trait_instance: $trait_name>; + pub enum $call_type where aux: $aux_type; + $( + fn $fn_name(aux $(, $param_name: $param )*)= $id; + )* + } + decl_dispatch! { + impl for $mod_type<$trait_instance: $trait_name>; + $($rest)* + } + }; + // BASE CASE + ( + impl for $mod_type:ident<$trait_instance:ident: $trait_name:ident>; + ) => { + impl<$trait_instance: $trait_name> $mod_type<$trait_instance> { + pub fn aux_dispatch>(d: D, aux: &D::Aux) { + d.dispatch(aux); + } + pub fn dispatch>(d: D) { + d.dispatch(); + } + } + } +} + +#[macro_export] +/// Implement a single dispatch modules to create a pairing of a dispatch trait and enum. +macro_rules! __decl_dispatch_module_without_aux { + ( + impl for $mod_type:ident<$trait_instance:ident: $trait_name:ident>; + pub enum $call_type:ident; + $( + fn $fn_name:ident( + $( + $param_name:ident : $param:ty + ),* + ) + = $id:expr ; + )* + ) => { + __decl_dispatch_module_common! { + impl for $mod_type<$trait_instance: $trait_name>; + pub enum $call_type; + $( fn $fn_name( $( $param_name : $param ),* ) = $id ; )* + } + impl<$trait_instance: $trait_name> $crate::dispatch::Dispatchable + for $call_type<$trait_instance> + { + type Trait = $trait_instance; + fn dispatch(self) { + match self { + $( + $call_type::$fn_name( $( $param_name ),* ) => + <$mod_type<$trait_instance>>::$fn_name( $( $param_name ),* ), + )* + $call_type::__PhantomItem(_) => { panic!("__PhantomItem should never be used.") }, + } + } + } + impl<$trait_instance: $trait_name> $crate::dispatch::Callable + for $mod_type<$trait_instance> + { + type Call = $call_type<$trait_instance>; + } + } +} + +#[macro_export] +/// Implement a single dispatch modules to create a pairing of a dispatch trait and enum. +macro_rules! __decl_dispatch_module_with_aux { + ( + impl for $mod_type:ident<$trait_instance:ident: $trait_name:ident>; + pub enum $call_type:ident where aux: $aux_type:ty; + $( + fn $fn_name:ident(aux + $( + , $param_name:ident : $param:ty + )* + ) + = $id:expr ; + )* + ) => { + __decl_dispatch_module_common! { + impl for $mod_type<$trait_instance: $trait_name>; + pub enum $call_type; + $( fn $fn_name( $( $param_name : $param ),* ) = $id ; )* + } + impl<$trait_instance: $trait_name> $crate::dispatch::AuxDispatchable + for $call_type<$trait_instance> + { + type Trait = $trait_instance; + type Aux = $aux_type; + fn dispatch(self, aux: &Self::Aux) { + match self { + $( + $call_type::$fn_name( $( $param_name ),* ) => + <$mod_type<$trait_instance>>::$fn_name( aux $(, $param_name )* ), + )* + $call_type::__PhantomItem(_) => { panic!("__PhantomItem should never be used.") }, + } + } + } + impl<$trait_instance: $trait_name> $crate::dispatch::AuxCallable + for $mod_type<$trait_instance> + { + type Call = $call_type<$trait_instance>; + } + }; +} + +#[macro_export] +/// Implement a single dispatch modules to create a pairing of a dispatch trait and enum. +macro_rules! __decl_dispatch_module_common { + ( + impl for $mod_type:ident<$trait_instance:ident: $trait_name:ident>; + pub enum $call_type:ident; + $( + fn $fn_name:ident( + $( + $param_name:ident : $param:ty + ),* + ) + = $id:expr ; + )* + ) => { + #[cfg_attr(feature = "std", derive(Serialize))] + #[allow(missing_docs)] + pub enum $call_type<$trait_instance: $trait_name> { + __PhantomItem($crate::dispatch::PhantomData<$trait_instance>), + $( + #[allow(non_camel_case_types)] + $fn_name ( $( $param ),* ), + )* + } + + // manual implementation of clone/eq/partialeq because using derive erroneously requires + // clone/eq/partialeq from T. + impl<$trait_instance: $trait_name> $crate::dispatch::Clone + for $call_type<$trait_instance> + { + fn clone(&self) -> Self { + match *self { + $( + $call_type::$fn_name( $( ref $param_name ),* ) => + $call_type::$fn_name( $( $param_name.clone() ),* ) + ,)* + $call_type::__PhantomItem(_) => unreachable!(), + } + } + } + impl<$trait_instance: $trait_name> $crate::dispatch::PartialEq + for $call_type<$trait_instance> + { + fn eq(&self, other: &Self) -> bool { + match *self { + $( + $call_type::$fn_name( $( ref $param_name ),* ) => { + let self_params = ( $( $param_name, )* ); + if let $call_type::$fn_name( $( ref $param_name ),* ) = *other { + self_params == ( $( $param_name, )* ) + } else { + if let $call_type::__PhantomItem(_) = *other { + unreachable!() + } else { + false + } + } + } + )* + $call_type::__PhantomItem(_) => unreachable!(), + } + } + } + impl<$trait_instance: $trait_name> $crate::dispatch::Eq + for $call_type<$trait_instance> + {} + + #[cfg(feature = "std")] + impl<$trait_instance: $trait_name> $crate::dispatch::fmt::Debug + for $call_type<$trait_instance> + { + fn fmt(&self, f: &mut $crate::dispatch::fmt::Formatter) -> Result<(), $crate::dispatch::fmt::Error> { + match *self { + $( + $call_type::$fn_name( $( ref $param_name ),* ) => + write!(f, "{}{:?}", + stringify!($fn_name), + ( $( $param_name.clone(), )* ) + ) + ,)* + $call_type::__PhantomItem(_) => unreachable!(), + } + } + } + + impl<$trait_instance: $trait_name> $crate::dispatch::Slicable for $call_type<$trait_instance> { + fn decode(input: &mut I) -> Option { + match input.read_byte()? { + $( + $id => { + $( + let $param_name = $crate::dispatch::Slicable::decode(input)?; + )* + Some($call_type:: $fn_name( $( $param_name ),* )) + } + )* + _ => None, + } + } + + fn encode(&self) -> $crate::dispatch::Vec { + let mut v = $crate::dispatch::Vec::new(); + match *self { + $( + $call_type::$fn_name( + $( + ref $param_name + ),* + ) => { + v.push($id as u8); + $( + $param_name.using_encoded(|s| v.extend(s)); + )* + } + )* + $call_type::__PhantomItem(_) => unreachable!(), + } + v + } + + fn using_encoded R>(&self, f: F) -> R { + f(self.encode().as_slice()) + } + } + + } +} + +pub trait IsSubType { + fn is_sub_type(&self) -> Option<&::Call>; +} +pub trait IsAuxSubType { + fn is_aux_sub_type(&self) -> Option<&::Call>; +} + +/// Implement a meta-dispatch module to dispatch to other dispatchers. +#[macro_export] +macro_rules! impl_outer_dispatch { + () => (); + ( + pub enum $call_type:ident where aux: $aux:ty { + $( + $camelcase:ident = $id:expr, + )* + } + $( $rest:tt )* + ) => { + #[derive(Clone, PartialEq, Eq)] + #[cfg_attr(feature = "std", derive(Serialize, Debug))] + #[allow(missing_docs)] + pub enum $call_type { + $( + $camelcase ( <$camelcase as $crate::dispatch::AuxCallable>::Call ) + ,)* + } + impl_outer_dispatch_common! { $call_type, $($camelcase = $id,)* } + impl $crate::dispatch::AuxDispatchable for $call_type { + type Aux = $aux; + type Trait = $call_type; + fn dispatch(self, aux: &$aux) { + match self { + $( + $call_type::$camelcase(call) => call.dispatch(&aux), + )* + } + } + } + $( + impl $crate::dispatch::IsAuxSubType<$camelcase> for $call_type { + fn is_aux_sub_type(&self) -> Option<&<$camelcase as $crate::dispatch::AuxCallable>::Call> { + if let $call_type::$camelcase ( ref r ) = *self { + Some(r) + } else { + None + } + } + } + )* + impl_outer_dispatch!{ $($rest)* } + }; + ( + pub enum $call_type:ident { + $( + $camelcase:ident = $id:expr, + )* + } + $( $rest:tt )* + ) => { + #[derive(Clone, PartialEq, Eq)] + #[cfg_attr(feature = "std", derive(Serialize, Debug))] + #[allow(missing_docs)] + pub enum $call_type { + $( + $camelcase ( <$camelcase as $crate::dispatch::Callable>::Call ) + ,)* + } + impl_outer_dispatch_common! { $call_type, $($camelcase = $id,)* } + impl $crate::dispatch::Dispatchable for $call_type { + type Trait = $call_type; + fn dispatch(self) { + match self { + $( + $call_type::$camelcase(call) => call.dispatch(), + )* + } + } + } + $( + impl $crate::dispatch::IsSubType<$camelcase> for $call_type { + fn is_sub_type(&self) -> Option<&<$camelcase as $crate::dispatch::Callable>::Call> { + if let $call_type::$camelcase ( ref r ) = *self { + Some(r) + } else { + None + } + } + } + )* + impl_outer_dispatch!{ $($rest)* } + } +} + +/// Implement a meta-dispatch module to dispatch to other dispatchers. +#[macro_export] +macro_rules! impl_outer_dispatch_common { + ( + $call_type:ident, $( $camelcase:ident = $id:expr, )* + ) => { + impl $crate::dispatch::Slicable for $call_type { + fn decode(input: &mut I) -> Option { + match input.read_byte()? { + $( + $id => + Some($call_type::$camelcase( $crate::dispatch::Slicable::decode(input)? )), + )* + _ => None, + } + } + + fn encode(&self) -> $crate::dispatch::Vec { + let mut v = $crate::dispatch::Vec::new(); + match *self { + $( + $call_type::$camelcase( ref sub ) => { + v.push($id as u8); + sub.using_encoded(|s| v.extend(s)); + } + )* + } + v + } + + fn using_encoded R>(&self, f: F) -> R { + f(self.encode().as_slice()) + } + } + + } +} diff --git a/substrate/runtime-support/src/lib.rs b/substrate/runtime-support/src/lib.rs index b3aea78cbc6e6..12894aa8088bc 100644 --- a/substrate/runtime-support/src/lib.rs +++ b/substrate/runtime-support/src/lib.rs @@ -18,6 +18,16 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "std")] +extern crate serde; + +#[cfg(feature = "std")] +#[allow(unused_imports)] #[macro_use] // can be removed when fixed: https://github.com/rust-lang/rust/issues/43497 +extern crate serde_derive; + +#[cfg(feature = "std")] +pub use serde_derive::*; + extern crate substrate_runtime_std as rstd; extern crate substrate_runtime_io as runtime_io; extern crate substrate_primitives as primitives; @@ -26,8 +36,10 @@ extern crate substrate_primitives as primitives; pub extern crate substrate_codec as codec; pub use self::storage::generator::Storage as GenericStorage; +pub mod dispatch; pub mod storage; mod hashable; pub use self::storage::{StorageVec, StorageList, StorageValue, StorageMap}; pub use self::hashable::Hashable; +pub use self::dispatch::{Parameter, Dispatchable, Callable, AuxDispatchable, AuxCallable, IsSubType, IsAuxSubType}; diff --git a/substrate/runtime-support/src/storage/generator.rs b/substrate/runtime-support/src/storage/generator.rs index b77b58d418124..d91e860f9ee51 100644 --- a/substrate/runtime-support/src/storage/generator.rs +++ b/substrate/runtime-support/src/storage/generator.rs @@ -50,6 +50,8 @@ use codec; use rstd::vec::Vec; #[doc(hidden)] pub use rstd::borrow::Borrow; +#[doc(hidden)] +pub use rstd::marker::PhantomData; /// Abstraction around storage. pub trait Storage { @@ -384,52 +386,52 @@ macro_rules! storage_items { }; // maps - ($name:ident : $prefix:expr => map [$kty: ty => $ty:ty]; $($t:tt)*) => { + ($name:ident : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { __storage_items_internal!(() () (Option<$ty>) (get) (take) $name: $prefix => map [$kty => $ty]); storage_items!($($t)*); }; - (pub $name:ident : $prefix:expr => map [$kty: ty => $ty:ty]; $($t:tt)*) => { + (pub $name:ident : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { __storage_items_internal!((pub) () (Option<$ty>) (get) (take) $name: $prefix => map [$kty => $ty]); storage_items!($($t)*); }; - ($name:ident : $prefix:expr => default map [$kty: ty => $ty:ty]; $($t:tt)*) => { + ($name:ident : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { __storage_items_internal!(() () ($ty) (get_or_default) (take_or_default) $name: $prefix => map [$kty => $ty]); storage_items!($($t)*); }; - (pub $name:ident : $prefix:expr => default map [$kty: ty => $ty:ty]; $($t:tt)*) => { + (pub $name:ident : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { __storage_items_internal!((pub) () ($ty) (get_or_default) (take_or_default) $name: $prefix => map [$kty => $ty]); storage_items!($($t)*); }; - ($name:ident : $prefix:expr => required map [$kty: ty => $ty:ty]; $($t:tt)*) => { + ($name:ident : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { __storage_items_internal!(() () ($ty) (require) (take_or_panic) $name: $prefix => map [$kty => $ty]); storage_items!($($t)*); }; - (pub $name:ident : $prefix:expr => required map [$kty: ty => $ty:ty]; $($t:tt)*) => { + (pub $name:ident : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { __storage_items_internal!((pub) () ($ty) (require) (take_or_panic) $name: $prefix => map [$kty => $ty]); storage_items!($($t)*); }; - ($name:ident get($getfn:ident) : $prefix:expr => map [$kty: ty => $ty:ty]; $($t:tt)*) => { + ($name:ident get($getfn:ident) : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { __storage_items_internal!(() ($getfn) (Option<$ty>) (get) (take) $name: $prefix => map [$kty => $ty]); storage_items!($($t)*); }; - (pub $name:ident get($getfn:ident) : $prefix:expr => map [$kty: ty => $ty:ty]; $($t:tt)*) => { + (pub $name:ident get($getfn:ident) : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { __storage_items_internal!((pub) ($getfn) (Option<$ty>) (get) (take) $name: $prefix => map [$kty => $ty]); storage_items!($($t)*); }; - ($name:ident get($getfn:ident) : $prefix:expr => default map [$kty: ty => $ty:ty]; $($t:tt)*) => { + ($name:ident get($getfn:ident) : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { __storage_items_internal!(() ($getfn) ($ty) (get_or_default) (take_or_default) $name: $prefix => map [$kty => $ty]); storage_items!($($t)*); }; - (pub $name:ident get($getfn:ident) : $prefix:expr => default map [$kty: ty => $ty:ty]; $($t:tt)*) => { + (pub $name:ident get($getfn:ident) : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { __storage_items_internal!((pub) ($getfn) ($ty) (get_or_default) (take_or_default) $name: $prefix => map [$kty => $ty]); storage_items!($($t)*); }; - ($name:ident get($getfn:ident) : $prefix:expr => required map [$kty: ty => $ty:ty]; $($t:tt)*) => { + ($name:ident get($getfn:ident) : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { __storage_items_internal!(() ($getfn) ($ty) (require) (take_or_panic) $name: $prefix => map [$kty => $ty]); storage_items!($($t)*); }; - (pub $name:ident get($getfn:ident) : $prefix:expr => required map [$kty: ty => $ty:ty]; $($t:tt)*) => { + (pub $name:ident get($getfn:ident) : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { __storage_items_internal!((pub) ($getfn) ($ty) (require) (take_or_panic) $name: $prefix => map [$kty => $ty]); storage_items!($($t)*); }; @@ -447,6 +449,544 @@ macro_rules! storage_items { () => () } +#[macro_export] +#[doc(hidden)] +macro_rules! __decl_storage_item { + // generator for values. + (($($vis:tt)*) ($traittype:ident as $traitinstance:ident) ($get_fn:ident) ($gettype:ty) ($getter:ident) ($taker:ident) $name:ident : $key:expr => $ty:ty) => { + __decl_storage_item!{ ($($vis)*) ($traittype as $traitinstance) () ($gettype) ($getter) ($taker) $name : $key => $ty } + }; + (($($vis:tt)*) ($traittype:ident as $traitinstance:ident) () ($gettype:ty) ($getter:ident) ($taker:ident) $name:ident : $key:expr => $ty:ty) => { + $($vis)* struct $name<$traitinstance: $traittype>($crate::storage::generator::PhantomData<$traitinstance>); + + impl<$traitinstance: $traittype> $crate::storage::generator::StorageValue<$ty> for $name<$traitinstance> { + type Query = $gettype; + + /// Get the storage key. + fn key() -> &'static [u8] { + $key + } + + /// Load the value from the provided storage instance. + fn get(storage: &S) -> Self::Query { + storage.$getter($key) + } + + /// Take a value from storage, removing it afterwards. + fn take(storage: &S) -> Self::Query { + storage.$taker($key) + } + } + }; + // generator for maps. + (($($vis:tt)*) ($traittype:ident as $traitinstance:ident) ($get_fn:ident) ($gettype:ty) ($getter:ident) ($taker:ident) $name:ident : $prefix:expr => map [$kty:ty => $ty:ty]) => { + __decl_storage_item!{ ($($vis)*) ($traittype as $traitinstance) () ($gettype) ($getter) ($taker) $name : $prefix => map [$kty => $ty] } + }; + (($($vis:tt)*) ($traittype:ident as $traitinstance:ident) () ($gettype:ty) ($getter:ident) ($taker:ident) $name:ident : $prefix:expr => map [$kty:ty => $ty:ty]) => { + $($vis)* struct $name<$traitinstance: $traittype>($crate::storage::generator::PhantomData<$traitinstance>); + + impl<$traitinstance: $traittype> $crate::storage::generator::StorageMap<$kty, $ty> for $name<$traitinstance> { + type Query = $gettype; + + /// Get the prefix key in storage. + fn prefix() -> &'static [u8] { + $prefix + } + + /// Get the storage key used to fetch a value corresponding to a specific key. + fn key_for(x: &$kty) -> Vec { + let mut key = $prefix.to_vec(); + key.extend($crate::codec::Slicable::encode(x)); + key + } + + /// Load the value associated with the given key from the map. + fn get(key: &$kty, storage: &S) -> Self::Query { + let key = <$name<$traitinstance> as $crate::storage::generator::StorageMap<$kty, $ty>>::key_for(key); + storage.$getter(&key[..]) + } + + /// Take the value, reading and removing it. + fn take(key: &$kty, storage: &S) -> Self::Query { + let key = <$name<$traitinstance> as $crate::storage::generator::StorageMap<$kty, $ty>>::key_for(key); + storage.$taker(&key[..]) + } + } + }; +} + +// TODO: revisit this idiom once we get `type`s in `impl`s. +/*impl Module { + type Now = super::Now; +}*/ + +/// Declares strongly-typed wrappers around codec-compatible types in storage. +/// +/// For now we implement a convenience trait with pre-specialised associated types, one for each +/// storage item. This allows you to gain access to publicly visisible storage items from a +/// module type. Currently you must disambiguate by using `::Item` rather than +/// the simpler `Module::Item`. Hopefully the rust guys with fix this soon. +#[macro_export] +macro_rules! decl_storage { + ( + trait $storetype:ident for $modulename:ident<$traitinstance:ident: $traittype:ident>; + $($t:tt)* + ) => { + __decl_storage_items!($traittype $traitinstance $($t)*); + trait $storetype { + __decl_store_items!($($t)*); + } + impl<$traitinstance: $traittype> $storetype for $modulename<$traitinstance> { + __impl_store_items!($traitinstance $($t)*); + } + impl<$traitinstance: $traittype> $modulename<$traitinstance> { + __impl_store_fns!($traitinstance $($t)*); + } + }; + ( + pub trait $storetype:ident for $modulename:ident<$traitinstance:ident: $traittype:ident>; + $($t:tt)* + ) => { + __decl_storage_items!($traittype $traitinstance $($t)*); + pub trait $storetype { + __decl_store_items!($($t)*); + } + impl<$traitinstance: $traittype> $storetype for $modulename<$traitinstance> { + __impl_store_items!($traitinstance $($t)*); + } + impl<$traitinstance: $traittype> $modulename<$traitinstance> { + __impl_store_fns!($traitinstance $($t)*); + } + } +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __decl_store_item { + ($name:ident) => { type $name; } +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __decl_store_items { + // simple values + ($name:ident : $key:expr => $ty:ty; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + (pub $name:ident : $key:expr => $ty:ty; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + ($name:ident : $key:expr => default $ty:ty; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + (pub $name:ident : $key:expr => default $ty:ty; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + ($name:ident : $key:expr => required $ty:ty; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + (pub $name:ident : $key:expr => required $ty:ty; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + + ($name:ident get($getfn:ident) : $key:expr => $ty:ty; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + (pub $name:ident get($getfn:ident) : $key:expr => $ty:ty; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + ($name:ident get($getfn:ident) : $key:expr => default $ty:ty; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + (pub $name:ident get($getfn:ident) : $key:expr => default $ty:ty; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + ($name:ident get($getfn:ident) : $key:expr => required $ty:ty; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + (pub $name:ident get($getfn:ident) : $key:expr => required $ty:ty; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + + // maps + ($name:ident : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + (pub $name:ident : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + ($name:ident : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + (pub $name:ident : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + ($name:ident : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + (pub $name:ident : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + + ($name:ident get($getfn:ident) : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + (pub $name:ident get($getfn:ident) : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + ($name:ident get($getfn:ident) : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + (pub $name:ident get($getfn:ident) : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + ($name:ident get($getfn:ident) : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + (pub $name:ident get($getfn:ident) : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_store_item!($name); __decl_store_items!($($t)*); + }; + + // exit + () => () +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __impl_store_fn { + ($traitinstance:ident $name:ident $get_fn:ident ($gettype:ty) $key:expr => $ty:ty) => { + pub fn $get_fn() -> $gettype { + <$name<$traitinstance> as $crate::storage::generator::StorageValue<$ty>> :: get(&$crate::storage::RuntimeStorage) + } + }; + ($traitinstance:ident $name:ident $get_fn:ident ($gettype:ty) $prefix:expr => map [$kty:ty => $ty:ty]) => { + pub fn $get_fn>(key: K) -> $gettype { + <$name<$traitinstance> as $crate::storage::generator::StorageMap<$kty, $ty>> :: get(key.borrow(), &$crate::storage::RuntimeStorage) + } + } +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __impl_store_fns { + // simple values + ($traitinstance:ident $name:ident : $key:expr => $ty:ty; $($t:tt)*) => { + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident : $key:expr => $ty:ty; $($t:tt)*) => { + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident $name:ident : $key:expr => default $ty:ty; $($t:tt)*) => { + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident : $key:expr => default $ty:ty; $($t:tt)*) => { + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident $name:ident : $key:expr => required $ty:ty; $($t:tt)*) => { + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident : $key:expr => required $ty:ty; $($t:tt)*) => { + __impl_store_fns!($traitinstance $($t)*); + }; + + ($traitinstance:ident $name:ident get($getfn:ident) : $key:expr => $ty:ty; $($t:tt)*) => { + __impl_store_fn!($traitinstance $name $getfn (Option<$ty>) $gettype $key => $ty); + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident get($getfn:ident) : $key:expr => $ty:ty; $($t:tt)*) => { + __impl_store_fn!($traitinstance $name $getfn (Option<$ty>) $key => $ty); + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident $name:ident get($getfn:ident) : $key:expr => default $ty:ty; $($t:tt)*) => { + __impl_store_fn!($traitinstance $name $getfn ($ty) $key => $ty); + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident get($getfn:ident) : $key:expr => default $ty:ty; $($t:tt)*) => { + __impl_store_fn!($traitinstance $name $getfn ($ty) $key => $ty); + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident $name:ident get($getfn:ident) : $key:expr => required $ty:ty; $($t:tt)*) => { + __impl_store_fn!($traitinstance $name $getfn ($ty) $key => $ty); + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident get($getfn:ident) : $key:expr => required $ty:ty; $($t:tt)*) => { + __impl_store_fn!($traitinstance $name $getfn ($ty) $key => $ty); + __impl_store_fns!($traitinstance $($t)*); + }; + + // maps + ($traitinstance:ident $name:ident : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident $name:ident : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident $name:ident : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_fns!($traitinstance $($t)*); + }; + + ($traitinstance:ident $name:ident get($getfn:ident) : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_fn!($traitinstance $name $getfn (Option<$ty>) $prefix:expr => map [$kty => $ty]); + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident get($getfn:ident) : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_fn!($traitinstance $name $getfn (Option<$ty>) $prefix:expr => map [$kty => $ty]); + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident $name:ident get($getfn:ident) : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_fn!($traitinstance $name $getfn ($ty) $prefix:expr => map [$kty => $ty]); + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident get($getfn:ident) : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_fn!($traitinstance $name $getfn ($ty) $prefix:expr => map [$kty => $ty]); + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident $name:ident get($getfn:ident) : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_fn!($traitinstance $name $getfn ($ty) $prefix:expr => map [$kty => $ty]); + __impl_store_fns!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident get($getfn:ident) : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_fn!($traitinstance $name $getfn ($ty) $prefix:expr => map [$kty => $ty]); + __impl_store_fns!($traitinstance $($t)*); + }; + + // exit + ($traitinstance:ident) => () +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __impl_store_item { + ($name:ident $traitinstance:ident) => { type $name = $name<$traitinstance>; } +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __impl_store_items { + // simple values + ($traitinstance:ident $name:ident : $key:expr => $ty:ty; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident : $key:expr => $ty:ty; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident $name:ident : $key:expr => default $ty:ty; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident : $key:expr => default $ty:ty; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident $name:ident : $key:expr => required $ty:ty; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident : $key:expr => required $ty:ty; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + + ($traitinstance:ident $name:ident get($getfn:ident) : $key:expr => $ty:ty; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident get($getfn:ident) : $key:expr => $ty:ty; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident $name:ident get($getfn:ident) : $key:expr => default $ty:ty; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident get($getfn:ident) : $key:expr => default $ty:ty; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident $name:ident get($getfn:ident) : $key:expr => required $ty:ty; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident get($getfn:ident) : $key:expr => required $ty:ty; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + + // maps + ($traitinstance:ident $name:ident : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident $name:ident : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident $name:ident : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + + ($traitinstance:ident $name:ident get($getfn:ident) : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident get($getfn:ident) : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident $name:ident get($getfn:ident) : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident get($getfn:ident) : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident $name:ident get($getfn:ident) : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + ($traitinstance:ident pub $name:ident get($getfn:ident) : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __impl_store_item!($name $traitinstance); + __impl_store_items!($traitinstance $($t)*); + }; + + // exit + ($traitinstance:ident) => () +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __decl_storage_items { + // simple values + ($traittype:ident $traitinstance:ident $name:ident : $key:expr => $ty:ty; $($t:tt)*) => { + __decl_storage_item!(() ($traittype as $traitinstance) () (Option<$ty>) (get) (take) $name: $key => $ty); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident pub $name:ident : $key:expr => $ty:ty; $($t:tt)*) => { + __decl_storage_item!((pub) ($traittype as $traitinstance) () (Option<$ty>) (get) (take) $name: $key => $ty); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident $name:ident : $key:expr => default $ty:ty; $($t:tt)*) => { + __decl_storage_item!(() ($traittype as $traitinstance) () ($ty) (get_or_default) (take_or_default) $name: $key => $ty); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident pub $name:ident : $key:expr => default $ty:ty; $($t:tt)*) => { + __decl_storage_item!((pub) ($traittype as $traitinstance) () ($ty) (get_or_default) (take_or_default) $name: $key => $ty); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident $name:ident : $key:expr => required $ty:ty; $($t:tt)*) => { + __decl_storage_item!(() ($traittype as $traitinstance) () ($ty) (require) (take_or_panic) $name: $key => $ty); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident pub $name:ident : $key:expr => required $ty:ty; $($t:tt)*) => { + __decl_storage_item!((pub) ($traittype as $traitinstance) () ($ty) (require) (take_or_panic) $name: $key => $ty); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + + ($traittype:ident $traitinstance:ident $name:ident get($getfn:ident) : $key:expr => $ty:ty; $($t:tt)*) => { + __decl_storage_item!(() ($traittype as $traitinstance) ($getfn) (Option<$ty>) (get) (take) $name: $key => $ty); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident pub $name:ident get($getfn:ident) : $key:expr => $ty:ty; $($t:tt)*) => { + __decl_storage_item!((pub) ($traittype as $traitinstance) ($getfn) (Option<$ty>) (get) (take) $name: $key => $ty); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident $name:ident get($getfn:ident) : $key:expr => default $ty:ty; $($t:tt)*) => { + __decl_storage_item!(() ($traittype as $traitinstance) ($getfn) ($ty) (get_or_default) (take_or_default) $name: $key => $ty); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident pub $name:ident get($getfn:ident) : $key:expr => default $ty:ty; $($t:tt)*) => { + __decl_storage_item!((pub) ($traittype as $traitinstance) ($getfn) ($ty) (get_or_default) (take_or_default) $name: $key => $ty); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident $name:ident get($getfn:ident) : $key:expr => required $ty:ty; $($t:tt)*) => { + __decl_storage_item!(() ($traittype as $traitinstance) ($getfn) ($ty) (require) (take_or_panic) $name: $key => $ty); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident pub $name:ident get($getfn:ident) : $key:expr => required $ty:ty; $($t:tt)*) => { + __decl_storage_item!((pub) ($traittype as $traitinstance) ($getfn) ($ty) (require) (take_or_panic) $name: $key => $ty); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + + // maps + ($traittype:ident $traitinstance:ident $name:ident : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_storage_item!(() ($traittype as $traitinstance) () (Option<$ty>) (get) (take) $name: $prefix => map [$kty => $ty]); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident pub $name:ident : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_storage_item!((pub) ($traittype as $traitinstance) () (Option<$ty>) (get) (take) $name: $prefix => map [$kty => $ty]); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident $name:ident : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_storage_item!(() ($traittype as $traitinstance) () ($ty) (get_or_default) (take_or_default) $name: $prefix => map [$kty => $ty]); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident pub $name:ident : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_storage_item!((pub) ($traittype as $traitinstance) () ($ty) (get_or_default) (take_or_default) $name: $prefix => map [$kty => $ty]); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident $name:ident : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_storage_item!(() ($traittype as $traitinstance) () ($ty) (require) (take_or_panic) $name: $prefix => map [$kty => $ty]); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident pub $name:ident : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_storage_item!((pub) ($traittype as $traitinstance) () ($ty) (require) (take_or_panic) $name: $prefix => map [$kty => $ty]); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + + ($traittype:ident $traitinstance:ident $name:ident get($getfn:ident) : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_storage_item!(() ($traittype as $traitinstance) ($getfn) (Option<$ty>) (get) (take) $name: $prefix => map [$kty => $ty]); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident pub $name:ident get($getfn:ident) : $prefix:expr => map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_storage_item!((pub) ($traittype as $traitinstance) ($getfn) (Option<$ty>) (get) (take) $name: $prefix => map [$kty => $ty]); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident $name:ident get($getfn:ident) : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_storage_item!(() ($traittype as $traitinstance) ($getfn) ($ty) (get_or_default) (take_or_default) $name: $prefix => map [$kty => $ty]); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident pub $name:ident get($getfn:ident) : $prefix:expr => default map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_storage_item!((pub) ($traittype as $traitinstance) ($getfn) ($ty) (get_or_default) (take_or_default) $name: $prefix => map [$kty => $ty]); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident $name:ident get($getfn:ident) : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_storage_item!(() ($traittype as $traitinstance) ($getfn) ($ty) (require) (take_or_panic) $name: $prefix => map [$kty => $ty]); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + ($traittype:ident $traitinstance:ident pub $name:ident get($getfn:ident) : $prefix:expr => required map [$kty:ty => $ty:ty]; $($t:tt)*) => { + __decl_storage_item!((pub) ($traittype as $traitinstance) ($getfn) ($ty) (require) (take_or_panic) $name: $prefix => map [$kty => $ty]); + __decl_storage_items!($traittype $traitinstance $($t)*); + }; + + // exit + ($traittype:ident $traitinstance:ident) => () +} + #[cfg(test)] mod tests { use std::collections::HashMap; diff --git a/substrate/runtime/consensus/Cargo.toml b/substrate/runtime/consensus/Cargo.toml new file mode 100644 index 0000000000000..c2de219535770 --- /dev/null +++ b/substrate/runtime/consensus/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "substrate-runtime-consensus" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +hex-literal = "0.1.0" +serde = { version = "1.0", default_features = false } +substrate-codec = { path = "../../codec", default_features = false } +substrate-runtime-std = { path = "../../runtime-std", default_features = false } +substrate-runtime-io = { path = "../../runtime-io", default_features = false } +substrate-runtime-support = { path = "../../runtime-support", default_features = false } +substrate-runtime-primitives = { path = "../primitives", default_features = false } + +[features] +default = ["std"] +std = [ + "serde/std", + "substrate-codec/std", + "substrate-runtime-std/std", + "substrate-runtime-io/std", + "substrate-runtime-support/std", + "substrate-runtime-primitives/std", +] diff --git a/substrate/runtime/consensus/src/lib.rs b/substrate/runtime/consensus/src/lib.rs new file mode 100644 index 0000000000000..7826c689e70c1 --- /dev/null +++ b/substrate/runtime/consensus/src/lib.rs @@ -0,0 +1,111 @@ +// 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 . + +//! Conensus module for runtime; manages the authority set ready for the native code. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[allow(unused_imports)] +#[macro_use] +extern crate substrate_runtime_std as rstd; + +#[macro_use] +extern crate substrate_runtime_support as runtime_support; + +extern crate substrate_runtime_io as runtime_io; +extern crate substrate_runtime_primitives as primitives; +extern crate substrate_codec as codec; + +use rstd::prelude::*; +use runtime_support::{storage, Parameter}; +use runtime_support::storage::unhashed::StorageVec; + +pub const AUTHORITY_AT: &'static[u8] = b":auth:"; +pub const AUTHORITY_COUNT: &'static[u8] = b":auth:len"; + +struct AuthorityStorageVec(rstd::marker::PhantomData); +impl StorageVec for AuthorityStorageVec { + type Item = S; + const PREFIX: &'static [u8] = AUTHORITY_AT; +} + +pub const CODE: &'static [u8] = b":code"; + +pub trait Trait { + type SessionKey: Parameter + Default; +} + +decl_module! { + pub struct Module; + pub enum PrivCall { + fn set_code(new: Vec) = 0; + fn dummy() = 1; + } +} + +impl Module { + /// Get the current set of authorities. These are the session keys. + pub fn authorities() -> Vec { + AuthorityStorageVec::::items() + } + + /// Set the new code. + fn set_code(new: Vec) { + storage::unhashed::put_raw(CODE, &new); + } + + fn dummy() {} + + /// Set the current set of authorities' session keys. + /// + /// Called by `next_session` only. + pub fn set_authorities(authorities: &[T::SessionKey]) { + AuthorityStorageVec::::set_items(authorities); + } + + /// Set a single authority by index. + pub fn set_authority(index: u32, key: &T::SessionKey) { + AuthorityStorageVec::::set_item(index, key); + } +} + +#[cfg(any(feature = "std", test))] +pub struct GenesisConfig { + pub authorities: Vec, +} + +#[cfg(any(feature = "std", test))] +impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { + authorities: vec![], + } + } +} + +#[cfg(any(feature = "std", test))] +impl primitives::BuildExternalities for GenesisConfig +{ + fn build_externalities(self) -> runtime_io::TestExternalities { + use codec::{Slicable, KeyedVec}; + let auth_count = self.authorities.len() as u32; + let mut r: runtime_io::TestExternalities = self.authorities.into_iter().enumerate().map(|(i, v)| + ((i as u32).to_keyed_vec(b":auth:"), v.encode()) + ).collect(); + r.insert(b":auth:len".to_vec(), auth_count.encode()); + r + } +} diff --git a/substrate/runtime/council/Cargo.toml b/substrate/runtime/council/Cargo.toml new file mode 100644 index 0000000000000..e3a5fe93e13de --- /dev/null +++ b/substrate/runtime/council/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "substrate-runtime-council" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +hex-literal = "0.1.0" +integer-sqrt = "0.1.0" +serde = { version = "1.0", default_features = false } +safe-mix = { path = "../../../safe-mix", default_features = false} +substrate-keyring = { path = "../../keyring", optional = true } +substrate-codec = { path = "../../codec", default_features = false } +substrate-primitives = { path = "../../primitives", default_features = false } +substrate-runtime-std = { path = "../../runtime-std", default_features = false } +substrate-runtime-io = { path = "../../runtime-io", default_features = false } +substrate-runtime-support = { path = "../../runtime-support", default_features = false } +substrate-runtime-primitives = { path = "../primitives", default_features = false } +substrate-runtime-consensus = { path = "../consensus", default_features = false } +substrate-runtime-democracy = { path = "../democracy", default_features = false } +substrate-runtime-session = { path = "../session", default_features = false } +substrate-runtime-staking = { path = "../staking", default_features = false } +substrate-runtime-system = { path = "../system", default_features = false } + +[features] +default = ["std"] +std = [ + "serde/std", + "safe-mix/std", + "substrate-keyring", + "substrate-codec/std", + "substrate-primitives/std", + "substrate-runtime-std/std", + "substrate-runtime-io/std", + "substrate-runtime-support/std", + "substrate-runtime-primitives/std", + "substrate-runtime-consensus/std", + "substrate-runtime-democracy/std", + "substrate-runtime-session/std", + "substrate-runtime-staking/std", + "substrate-runtime-system/std", +] diff --git a/substrate/runtime/council/src/lib.rs b/substrate/runtime/council/src/lib.rs new file mode 100644 index 0000000000000..9f7f4b21bbd1f --- /dev/null +++ b/substrate/runtime/council/src/lib.rs @@ -0,0 +1,1453 @@ +// 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 . + +//! Council system: Handles the voting in and maintenance of council members. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "std")] extern crate serde; + +extern crate integer_sqrt; +extern crate substrate_codec as codec; +extern crate substrate_primitives; +#[cfg(any(feature = "std", test))] extern crate substrate_keyring as keyring; +#[macro_use] extern crate substrate_runtime_std as rstd; +extern crate substrate_runtime_io as runtime_io; +#[macro_use] extern crate substrate_runtime_support as runtime_support; +extern crate substrate_runtime_primitives as primitives; +extern crate substrate_runtime_consensus as consensus; +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; + +use rstd::prelude::*; +use primitives::traits::{Zero, One, RefInto, As}; +use runtime_support::{StorageValue, StorageMap}; + +pub mod voting; + +// no polynomial attacks: +// +// all unbonded public operations should be constant time. +// all other public operations must be linear time in terms of prior public operations and: +// - those "valid" ones that cost nothing be limited to a constant number per single protected operation +// - the rest costing the same order as the computational complexity +// all protected operations must complete in at most O(public operations) +// +// we assume "beneficial" transactions will have the same access as attack transactions. +// +// any storage requirements should be bonded by the same order as the volume. + +// public operations: +// - express approvals (you pay in a "voter" bond the first time you do this; O(1); one extra DB entry, one DB change) +// - remove active voter (you get your "voter" bond back; O(1); one fewer DB entry, one DB change) +// - remove inactive voter (either you or the target is removed; if the target, you get their "voter" bond back; O(1); one fewer DB entry, one DB change) +// - submit candidacy (you pay a "candidate" bond; O(1); one extra DB entry, two DB changes) +// - present winner/runner-up (you may pay a "presentation" bond of O(voters) if the presentation is invalid; O(voters) compute; ) +// protected operations: +// - remove candidacy (remove all votes for a candidate) (one fewer DB entry, two DB changes) + +// to avoid a potentially problematic case of not-enough approvals prior to voting causing a +// back-to-back votes that have no way of ending, then there's a forced grace period between votes. +// to keep the system as stateless as possible (making it a bit easier to reason about), we just +// restrict when votes can begin to blocks that lie on boundaries (`voting_period`). + +// for an approval vote of C councilers: + +// top K runners-up are maintained between votes. all others are discarded. +// - candidate removed & bond returned when elected. +// - candidate removed & bond burned when discarded. + +// at the point that the vote ends (), all voters' balances are snapshotted. + +// for B blocks following, there's a counting period whereby each of the candidates that believe +// they fall in the top K+C voted can present themselves. they get the total stake +// recorded (based on the snapshot); an ordered list is maintained (the leaderboard). Noone may +// present themselves that, if elected, would result in being included twice on the council +// (important since existing councilers will will have their approval votes as it may be that they +// don't get removed), nor if existing presenters would mean they're not in the top K+C. + +// following B blocks, the top C candidates are elected and have their bond returned. the top C +// candidates and all other candidates beyond the top C+K are cleared. + +// vote-clearing happens lazily; for an approval to count, the most recent vote at the time of the +// voter's most recent vote must be no later than the most recent vote at the time that the +// candidate in the approval position was registered there. as candidates are removed from the +// register and others join in their place, this prevent an approval meant for an earlier candidate +// being used to elect a new candidate. + +// the candidate list increases as needed, but the contents (though not really the capacity) reduce +// after each vote as all but K entries are cleared. newly registering candidates must use cleared +// entries before they increase the capacity. + +pub type VoteIndex = u32; + +pub trait Trait: democracy::Trait {} + +decl_module! { + pub struct Module; + pub enum Call where aux: T::PublicAux { + fn set_approvals(aux, votes: Vec, index: VoteIndex) = 0; + fn reap_inactive_voter(aux, signed_index: u32, who: T::AccountId, who_index: u32, assumed_vote_index: VoteIndex) = 1; + fn retract_voter(aux, index: u32) = 2; + fn submit_candidacy(aux, slot: u32) = 3; + fn present_winner(aux, candidate: T::AccountId, total: T::Balance, index: VoteIndex) = 4; + } + pub enum PrivCall { + fn set_desired_seats(count: u32) = 0; + fn remove_member(who: T::AccountId) = 1; + fn set_presentation_duration(count: T::BlockNumber) = 2; + fn set_term_duration(count: T::BlockNumber) = 3; + } +} + +decl_storage! { + trait Store for Module; + + // parameters + // How much should be locked up in order to submit one's candidacy. + pub CandidacyBond get(candidacy_bond): b"cou:cbo" => required T::Balance; + // How much should be locked up in order to be able to submit votes. + pub VotingBond get(voting_bond): b"cou:vbo" => required T::Balance; + // The punishment, per voter, if you provide an invalid presentation. + pub PresentSlashPerVoter get(present_slash_per_voter): b"cou:pss" => required T::Balance; + // How many runners-up should have their approvals persist until the next vote. + pub CarryCount get(carry_count): b"cou:cco" => required u32; + // How long to give each top candidate to present themselves after the vote ends. + pub PresentationDuration get(presentation_duration): b"cou:pdu" => required T::BlockNumber; + // How many votes need to go by after a voter's last vote before they can be reaped if their + // approvals are moot. + pub InactiveGracePeriod get(inactivity_grace_period): b"cou:vgp" => required VoteIndex; + // How often (in blocks) to check for new votes. + pub VotingPeriod get(voting_period): b"cou:per" => required T::BlockNumber; + // How long each position is active for. + pub TermDuration get(term_duration): b"cou:trm" => required T::BlockNumber; + // Number of accounts that should be sitting on the council. + pub DesiredSeats get(desired_seats): b"cou:sts" => required u32; + + // permanent state (always relevant, changes only at the finalisation of voting) + // The current council. When there's a vote going on, this should still be used for executive + // matters. + pub ActiveCouncil get(active_council): b"cou:act" => default Vec<(T::AccountId, T::BlockNumber)>; + // The total number of votes that have happened or are in progress. + pub VoteCount get(vote_index): b"cou:vco" => default VoteIndex; + + // persistent state (always relevant, changes constantly) + // The last cleared vote index that this voter was last active at. + pub ApprovalsOf get(approvals_of): b"cou:apr" => default map [ T::AccountId => Vec ]; + // The vote index and list slot that the candidate `who` was registered or `None` if they are not + // currently registered. + pub RegisterInfoOf get(candidate_reg_info): b"cou:reg" => map [ T::AccountId => (VoteIndex, u32) ]; + // The last cleared vote index that this voter was last active at. + pub LastActiveOf get(voter_last_active): b"cou:lac" => map [ T::AccountId => VoteIndex ]; + // The present voter list. + pub Voters get(voters): b"cou:vrs" => default Vec; + // The present candidate list. + pub Candidates get(candidates): b"cou:can" => default Vec; // has holes + pub CandidateCount get(candidate_count): b"cou:cnc" => default u32; + + // temporary state (only relevant during finalisation/presentation) + // The accounts holding the seats that will become free on the next tally. + pub NextFinalise get(next_finalise): b"cou:nxt" => (T::BlockNumber, u32, Vec); + // The stakes as they were at the point that the vote ended. + pub SnapshotedStakes get(snapshoted_stakes): b"cou:sss" => required Vec; + // Get the leaderboard if we;re in the presentation phase. + pub Leaderboard get(leaderboard): b"cou:win" => Vec<(T::Balance, T::AccountId)>; // ORDERED low -> high +} + +impl Module { + + // exposed immutables. + + /// True if we're currently in a presentation period. + pub fn presentation_active() -> bool { + >::exists() + } + + /// If `who` a candidate at the moment? + pub fn is_a_candidate(who: &T::AccountId) -> bool { + >::exists(who) + } + + /// Determine the block that a vote can happen on which is no less than `n`. + pub fn next_vote_from(n: T::BlockNumber) -> T::BlockNumber { + let voting_period = Self::voting_period(); + (n + voting_period - One::one()) / voting_period * voting_period + } + + /// The block number on which the tally for the next election will happen. `None` only if the + /// desired seats of the council is zero. + pub fn next_tally() -> Option { + let desired_seats = Self::desired_seats(); + if desired_seats == 0 { + None + } else { + let c = Self::active_council(); + let (next_possible, count, coming) = + if let Some((tally_end, comers, leavers)) = Self::next_finalise() { + // if there's a tally in progress, then next tally can begin immediately afterwards + (tally_end, c.len() - leavers.len() + comers as usize, comers) + } else { + (>::block_number(), c.len(), 0) + }; + if count < desired_seats as usize { + Some(next_possible) + } else { + // next tally begins once enough council members expire to bring members below desired. + if desired_seats <= coming { + // the entire amount of desired seats is less than those new members - we'll have + // to wait until they expire. + Some(next_possible + Self::term_duration()) + } else { + Some(c[c.len() - (desired_seats - coming) as usize].1) + } + }.map(Self::next_vote_from) + } + } + + // dispatch + + /// Set candidate approvals. Approval slots stay valid as long as candidates in those slots + /// are registered. + fn set_approvals(aux: &T::PublicAux, votes: Vec, index: VoteIndex) { + assert!(!Self::presentation_active()); + assert_eq!(index, Self::vote_index()); + if !>::exists(aux.ref_into()) { + // not yet a voter - deduct bond. + >::reserve_balance(aux.ref_into(), Self::voting_bond()); + >::put({ + let mut v = Self::voters(); + v.push(aux.ref_into().clone()); + v + }); + } + >::insert(aux.ref_into().clone(), votes); + >::insert(aux.ref_into(), index); + } + + /// Remove a voter. For it not to be a bond-consuming no-op, all approved candidate indices + /// must now be either unregistered or registered to a candidate that registered the slot after + /// the voter gave their last approval set. + /// + /// May be called by anyone. Returns the voter deposit to `signed`. + fn reap_inactive_voter(aux: &T::PublicAux, signed_index: u32, who: T::AccountId, who_index: u32, assumed_vote_index: VoteIndex) { + assert!(!Self::presentation_active(), "cannot reap during presentation period"); + assert!(Self::voter_last_active(aux.ref_into()).is_some(), "reaper must be a voter"); + let last_active = Self::voter_last_active(&who).expect("target for inactivity cleanup must be active"); + assert!(assumed_vote_index == Self::vote_index(), "vote index not current"); + assert!(last_active < assumed_vote_index - Self::inactivity_grace_period(), "cannot reap during grace perid"); + let voters = Self::voters(); + let signed_index = signed_index as usize; + let who_index = who_index as usize; + assert!(signed_index < voters.len() && &voters[signed_index] == aux.ref_into(), "bad reporter index"); + assert!(who_index < voters.len() && voters[who_index] == who, "bad target index"); + + // will definitely kill one of signed or who now. + + let valid = !Self::approvals_of(&who).iter() + .zip(Self::candidates().iter()) + .any(|(&appr, addr)| + appr && + *addr != T::AccountId::default() && + Self::candidate_reg_info(addr) + .expect("all items in candidates list are registered").0 <= last_active); + + Self::remove_voter( + if valid { &who } else { aux.ref_into() }, + if valid { who_index } else { signed_index }, + voters + ); + if valid { + >::transfer_reserved_balance(&who, aux.ref_into(), Self::voting_bond()); + } else { + >::slash_reserved(aux.ref_into(), Self::voting_bond()); + } + } + + /// Remove a voter. All votes are cancelled and the voter deposit is returned. + fn retract_voter(aux: &T::PublicAux, index: u32) { + assert!(!Self::presentation_active(), "cannot retract when presenting"); + assert!(>::exists(aux.ref_into()), "cannot retract non-voter"); + let voters = Self::voters(); + let index = index as usize; + assert!(index < voters.len(), "retraction index invalid"); + assert!(&voters[index] == aux.ref_into(), "retraction index mismatch"); + Self::remove_voter(aux.ref_into(), index, voters); + >::unreserve_balance(aux.ref_into(), Self::voting_bond()); + } + + /// Submit oneself for candidacy. + /// + /// Account must have enough transferrable funds in it to pay the bond. + fn submit_candidacy(aux: &T::PublicAux, slot: u32) { + assert!(!Self::is_a_candidate(aux.ref_into()), "duplicate candidate submission"); + assert!(>::deduct_unbonded(aux.ref_into(), Self::candidacy_bond()), "candidate has not enough funds"); + + let slot = slot as usize; + let count = Self::candidate_count() as usize; + let candidates = Self::candidates(); + assert!( + (slot == count && count == candidates.len()) || + (slot < candidates.len() && candidates[slot] == T::AccountId::default()), + "invalid candidate slot" + ); + + let mut candidates = candidates; + if slot == candidates.len() { + candidates.push(aux.ref_into().clone()); + } else { + candidates[slot] = aux.ref_into().clone(); + } + >::put(candidates); + >::put(count as u32 + 1); + >::insert(aux.ref_into(), (Self::vote_index(), slot as u32)); + } + + /// Claim that `signed` is one of the top Self::carry_count() + current_vote().1 candidates. + /// Only works if the `block_number >= current_vote().0` and `< current_vote().0 + presentation_duration()`` + /// `signed` should have at least + fn present_winner(aux: &T::PublicAux, candidate: T::AccountId, total: T::Balance, index: VoteIndex) { + assert_eq!(index, Self::vote_index(), "index not current"); + let (_, _, expiring) = Self::next_finalise() + .expect("cannot present outside of presentation period"); + let stakes = Self::snapshoted_stakes(); + let voters = Self::voters(); + let bad_presentation_punishment = Self::present_slash_per_voter() * T::Balance::sa(voters.len()); + assert!(>::can_slash(aux.ref_into(), bad_presentation_punishment), "presenter must have sufficient slashable funds"); + + let mut leaderboard = Self::leaderboard().expect("leaderboard must exist while present phase active"); + assert!(total > leaderboard[0].0, "candidate not worthy of leaderboard"); + + if let Some(p) = Self::active_council().iter().position(|&(ref c, _)| c == &candidate) { + assert!(p < expiring.len(), "candidate must not form a duplicated member if elected"); + } + + let (registered_since, candidate_index): (VoteIndex, u32) = + Self::candidate_reg_info(&candidate).expect("presented candidate must be current"); + let actual_total = voters.iter() + .zip(stakes.iter()) + .filter_map(|(voter, stake)| + match Self::voter_last_active(voter) { + Some(b) if b >= registered_since => + Self::approvals_of(voter).get(candidate_index as usize) + .and_then(|approved| if *approved { Some(*stake) } else { None }), + _ => None, + }) + .fold(Zero::zero(), |acc, n| acc + n); + let dupe = leaderboard.iter().find(|&&(_, ref c)| c == &candidate).is_some(); + if total == actual_total && !dupe { + // insert into leaderboard + leaderboard[0] = (total, candidate); + leaderboard.sort_by_key(|&(t, _)| t); + >::put(leaderboard); + } else { + >::slash(aux.ref_into(), bad_presentation_punishment); + } + } + + /// Set the desired member count; if lower than the current count, then seats will not be up + /// election when they expire. If more, then a new vote will be started if one is not already + /// in progress. + fn set_desired_seats(count: u32) { + >::put(count); + } + + /// Remove a particular member. A tally will happen instantly (if not already in a presentation + /// period) to fill the seat if removal means that the desired members are not met. + /// This is effective immediately. + fn remove_member(who: T::AccountId) { + let new_council: Vec<(T::AccountId, T::BlockNumber)> = Self::active_council() + .into_iter() + .filter(|i| i.0 != who) + .collect(); + >::put(new_council); + } + + /// Set the presentation duration. If there is current a vote being presented for, will + /// invoke `finalise_vote`. + fn set_presentation_duration(count: T::BlockNumber) { + >::put(count); + } + + /// Set the presentation duration. If there is current a vote being presented for, will + /// invoke `finalise_vote`. + fn set_term_duration(count: T::BlockNumber) { + >::put(count); + } + + // private + + /// Check there's nothing to do this block + fn end_block(block_number: T::BlockNumber) { + if (block_number % Self::voting_period()).is_zero() { + if let Some(number) = Self::next_tally() { + if block_number == number { + Self::start_tally(); + } + } + } + if let Some((number, _, _)) = Self::next_finalise() { + if block_number == number { + Self::finalise_tally(); + } + } + } + + /// Remove a voter from the system. Trusts that Self::voters()[index] != voter. + fn remove_voter(voter: &T::AccountId, index: usize, mut voters: Vec) { + >::put({ voters.swap_remove(index); voters }); + >::remove(voter); + >::remove(voter); + } + + /// Close the voting, snapshot the staking and the number of seats that are actually up for grabs. + fn start_tally() { + let active_council = Self::active_council(); + let desired_seats = Self::desired_seats() as usize; + let number = >::block_number(); + let expiring = active_council.iter().take_while(|i| i.1 == number).map(|i| i.0.clone()).collect::>(); + if active_council.len() - expiring.len() < desired_seats { + let empty_seats = desired_seats - (active_council.len() - expiring.len()); + >::put((number + Self::presentation_duration(), empty_seats as u32, expiring)); + + let voters = Self::voters(); + let votes = voters.iter().map(>::balance).collect::>(); + >::put(votes); + + // initialise leaderboard. + let leaderboard_size = empty_seats + Self::carry_count() as usize; + >::put(vec![(T::Balance::zero(), T::AccountId::default()); leaderboard_size]); + } + } + + /// Finalise the vote, removing each of the `removals` and inserting `seats` of the most approved + /// candidates in their place. If the total council members is less than the desired membership + /// a new vote is started. + /// Clears all presented candidates, returning the bond of the elected ones. + fn finalise_tally() { + >::kill(); + let (_, coming, expiring): (T::BlockNumber, u32, Vec) = >::take() + .expect("finalise can only be called after a tally is started."); + let leaderboard: Vec<(T::Balance, T::AccountId)> = >::take().unwrap_or_default(); + let new_expiry = >::block_number() + Self::term_duration(); + + // return bond to winners. + let candidacy_bond = Self::candidacy_bond(); + for &(_, ref w) in leaderboard.iter() + .rev() + .take_while(|&&(b, _)| !b.is_zero()) + .take(coming as usize) + { + >::refund(w, candidacy_bond); + } + + // set the new council. + let mut new_council: Vec<_> = Self::active_council() + .into_iter() + .skip(expiring.len()) + .chain(leaderboard.iter() + .rev() + .take_while(|&&(b, _)| !b.is_zero()) + .take(coming as usize) + .cloned() + .map(|(_, a)| (a, new_expiry))) + .collect(); + new_council.sort_by_key(|&(_, expiry)| expiry); + >::put(new_council); + + // clear all except runners-up from candidate list. + let candidates = Self::candidates(); + let mut new_candidates = vec![T::AccountId::default(); candidates.len()]; // shrink later. + let runners_up = leaderboard.into_iter() + .rev() + .take_while(|&(b, _)| !b.is_zero()) + .skip(coming as usize) + .map(|(_, a)| { let i = Self::candidate_reg_info(&a).expect("runner up must be registered").1; (a, i) }); + let mut count = 0u32; + for (address, slot) in runners_up { + new_candidates[slot as usize] = address; + count += 1; + } + for (old, new) in candidates.iter().zip(new_candidates.iter()) { + if old != new { + // removed - kill it + >::remove(old); + } + } + // discard any superfluous slots. + if let Some(last_index) = new_candidates.iter().rposition(|c| *c != T::AccountId::default()) { + new_candidates.truncate(last_index + 1); + } + >::put(new_candidates); + >::put(count); + >::put(Self::vote_index() + 1); + } +} + +#[cfg(any(feature = "std", test))] +pub struct GenesisConfig { + // for the voting onto the council + pub candidacy_bond: T::Balance, + pub voter_bond: T::Balance, + pub present_slash_per_voter: T::Balance, + pub carry_count: u32, + pub active_council: Vec<(T::AccountId, T::BlockNumber)>, + pub approval_voting_period: T::BlockNumber, + pub presentation_duration: T::BlockNumber, + pub desired_seats: u32, + pub term_duration: T::BlockNumber, + pub inactive_grace_period: T::BlockNumber, + + + // for the council's votes. + pub cooloff_period: T::BlockNumber, + pub voting_period: T::BlockNumber, +} + +#[cfg(any(feature = "std", test))] +impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { + candidacy_bond: T::Balance::sa(9), + voter_bond: T::Balance::sa(0), + present_slash_per_voter: T::Balance::sa(1), + carry_count: 2, + inactive_grace_period: T::BlockNumber::sa(1), + active_council: vec![], + approval_voting_period: T::BlockNumber::sa(1000), + presentation_duration: T::BlockNumber::sa(1000), + desired_seats: 0, + term_duration: T::BlockNumber::sa(5), + cooloff_period: T::BlockNumber::sa(1000), + voting_period: T::BlockNumber::sa(3), + } + } +} + +#[cfg(any(feature = "std", test))] +impl primitives::BuildExternalities for GenesisConfig +{ + fn build_externalities(self) -> runtime_io::TestExternalities { + use codec::Slicable; + use runtime_io::twox_128; + + map![ + twox_128(>::key()).to_vec() => self.candidacy_bond.encode(), + twox_128(>::key()).to_vec() => self.voter_bond.encode(), + twox_128(>::key()).to_vec() => self.present_slash_per_voter.encode(), + twox_128(>::key()).to_vec() => self.carry_count.encode(), + twox_128(>::key()).to_vec() => self.presentation_duration.encode(), + twox_128(>::key()).to_vec() => self.approval_voting_period.encode(), + twox_128(>::key()).to_vec() => self.term_duration.encode(), + twox_128(>::key()).to_vec() => self.desired_seats.encode(), + twox_128(>::key()).to_vec() => self.inactive_grace_period.encode(), + twox_128(>::key()).to_vec() => self.active_council.encode(), + + twox_128(>::key()).to_vec() => self.cooloff_period.encode(), + twox_128(>::key()).to_vec() => self.voting_period.encode() + ] + } +} + +#[cfg(test)] +mod tests { + pub use super::*; + pub use runtime_io::with_externalities; + pub use substrate_primitives::H256; + use primitives::BuildExternalities; + use primitives::traits::{HasPublicAux, Identity}; + use primitives::testing::{Digest, Header}; + + impl_outer_dispatch! { + pub enum Proposal { + Staking = 0, + Democracy = 1, + } + } + + pub struct Test; + impl HasPublicAux for Test { + type PublicAux = u64; + } + impl consensus::Trait for Test { + type SessionKey = u64; + } + impl system::Trait for Test { + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = runtime_io::BlakeTwo256; + type Digest = Digest; + type AccountId = u64; + type Header = Header; + } + impl session::Trait for Test { + type PublicAux = ::PublicAux; + type ConvertAccountIdToSessionKey = Identity; + } + impl staking::Trait for Test { + type Balance = u64; + type DetermineContractAddress = staking::DummyContractAddressFor; + } + impl democracy::Trait for Test { + type Proposal = Proposal; + } + impl Trait for Test {} + + pub fn new_test_ext(with_council: bool) -> runtime_io::TestExternalities { + let mut t = system::GenesisConfig::::default().build_externalities(); + t.extend(consensus::GenesisConfig::{ + authorities: vec![], + }.build_externalities()); + t.extend(session::GenesisConfig::{ + session_length: 1, //??? or 2? + validators: vec![10, 20], + }.build_externalities()); + t.extend(staking::GenesisConfig::{ + sessions_per_era: 1, + current_era: 0, + balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + intentions: vec![], + validator_count: 2, + bonding_duration: 0, + transaction_fee: 0, + }.build_externalities()); + t.extend(democracy::GenesisConfig::{ + launch_period: 1, + voting_period: 3, + minimum_deposit: 1, + }.build_externalities()); + t.extend(GenesisConfig::{ + candidacy_bond: 9, + voter_bond: 3, + present_slash_per_voter: 1, + carry_count: 2, + inactive_grace_period: 1, + active_council: if with_council { vec![ + (1, 10), + (2, 10), + (3, 10) + ] } else { vec![] }, + approval_voting_period: 4, + presentation_duration: 2, + desired_seats: 2, + term_duration: 5, + cooloff_period: 2, + voting_period: 1, + }.build_externalities()); + t + } + + pub type System = system::Module; + pub type Staking = staking::Module; + pub type Democracy = democracy::Module; + pub type Council = Module; + + #[test] + fn params_should_work() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(1); + assert_eq!(Council::next_vote_from(1), 4); + assert_eq!(Council::next_vote_from(4), 4); + assert_eq!(Council::next_vote_from(5), 8); + assert_eq!(Council::vote_index(), 0); + assert_eq!(Council::candidacy_bond(), 9); + assert_eq!(Council::voting_bond(), 3); + assert_eq!(Council::present_slash_per_voter(), 1); + assert_eq!(Council::presentation_duration(), 2); + assert_eq!(Council::voting_period(), 4); + assert_eq!(Council::term_duration(), 5); + assert_eq!(Council::desired_seats(), 2); + assert_eq!(Council::carry_count(), 2); + + assert_eq!(Council::active_council(), vec![]); + assert_eq!(Council::next_tally(), Some(4)); + assert_eq!(Council::presentation_active(), false); + assert_eq!(Council::next_finalise(), None); + + assert_eq!(Council::candidates(), Vec::::new()); + assert_eq!(Council::is_a_candidate(&1), false); + assert_eq!(Council::candidate_reg_info(1), None); + + assert_eq!(Council::voters(), Vec::::new()); + assert_eq!(Council::voter_last_active(1), None); + assert_eq!(Council::approvals_of(1), vec![]); + }); + } + + #[test] + fn simple_candidate_submission_should_work() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(1); + assert_eq!(Council::candidates(), Vec::::new()); + assert_eq!(Council::candidate_reg_info(1), None); + assert_eq!(Council::candidate_reg_info(2), None); + assert_eq!(Council::is_a_candidate(&1), false); + assert_eq!(Council::is_a_candidate(&2), false); + + Council::submit_candidacy(&1, 0); + assert_eq!(Council::candidates(), vec![1]); + assert_eq!(Council::candidate_reg_info(1), Some((0, 0))); + assert_eq!(Council::candidate_reg_info(2), None); + assert_eq!(Council::is_a_candidate(&1), true); + assert_eq!(Council::is_a_candidate(&2), false); + + Council::submit_candidacy(&2, 1); + assert_eq!(Council::candidates(), vec![1, 2]); + assert_eq!(Council::candidate_reg_info(1), Some((0, 0))); + assert_eq!(Council::candidate_reg_info(2), Some((0, 1))); + assert_eq!(Council::is_a_candidate(&1), true); + assert_eq!(Council::is_a_candidate(&2), true); + }); + } + + fn new_test_ext_with_candidate_holes() -> runtime_io::TestExternalities { + let mut t = new_test_ext(false); + with_externalities(&mut t, || { + >::put(vec![0, 0, 1]); + >::put(1); + >::insert(1, (0, 2)); + }); + t + } + + #[test] + fn candidate_submission_using_free_slot_should_work() { + let mut t = new_test_ext_with_candidate_holes(); + + with_externalities(&mut t, || { + System::set_block_number(1); + assert_eq!(Council::candidates(), vec![0, 0, 1]); + + Council::submit_candidacy(&2, 1); + assert_eq!(Council::candidates(), vec![0, 2, 1]); + + Council::submit_candidacy(&3, 0); + assert_eq!(Council::candidates(), vec![3, 2, 1]); + }); + } + + #[test] + fn candidate_submission_using_alternative_free_slot_should_work() { + let mut t = new_test_ext_with_candidate_holes(); + + with_externalities(&mut t, || { + System::set_block_number(1); + assert_eq!(Council::candidates(), vec![0, 0, 1]); + + Council::submit_candidacy(&2, 0); + assert_eq!(Council::candidates(), vec![2, 0, 1]); + + Council::submit_candidacy(&3, 1); + assert_eq!(Council::candidates(), vec![2, 3, 1]); + }); + } + + #[test] + #[should_panic(expected = "invalid candidate slot")] + fn candidate_submission_not_using_free_slot_should_panic() { + let mut t = new_test_ext_with_candidate_holes(); + + with_externalities(&mut t, || { + System::set_block_number(1); + Council::submit_candidacy(&4, 3); + }); + } + + #[test] + #[should_panic(expected = "invalid candidate slot")] + fn bad_candidate_slot_submission_should_panic() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(1); + assert_eq!(Council::candidates(), Vec::::new()); + Council::submit_candidacy(&1, 1); + }); + } + + #[test] + #[should_panic(expected = "invalid candidate slot")] + fn non_free_candidate_slot_submission_should_panic() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(1); + assert_eq!(Council::candidates(), Vec::::new()); + Council::submit_candidacy(&1, 0); + Council::submit_candidacy(&2, 0); + }); + } + + #[test] + #[should_panic(expected = "duplicate candidate submission")] + fn dupe_candidate_submission_should_panic() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(1); + assert_eq!(Council::candidates(), Vec::::new()); + Council::submit_candidacy(&1, 0); + Council::submit_candidacy(&1, 1); + }); + } + + #[test] + #[should_panic(expected = "candidate has not enough funds")] + fn poor_candidate_submission_should_panic() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(1); + assert_eq!(Council::candidates(), Vec::::new()); + Council::submit_candidacy(&7, 0); + }); + } + + #[test] + fn voting_should_work() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(1); + + Council::submit_candidacy(&5, 0); + + Council::set_approvals(&1, vec![true], 0); + Council::set_approvals(&4, vec![true], 0); + + assert_eq!(Council::approvals_of(1), vec![true]); + assert_eq!(Council::approvals_of(4), vec![true]); + assert_eq!(Council::voters(), vec![1, 4]); + + Council::submit_candidacy(&2, 1); + Council::submit_candidacy(&3, 2); + + Council::set_approvals(&2, vec![false, true, true], 0); + Council::set_approvals(&3, vec![false, true, true], 0); + + assert_eq!(Council::approvals_of(1), vec![true]); + assert_eq!(Council::approvals_of(4), vec![true]); + assert_eq!(Council::approvals_of(2), vec![false, true, true]); + assert_eq!(Council::approvals_of(3), vec![false, true, true]); + + assert_eq!(Council::voters(), vec![1, 4, 2, 3]); + }); + } + + #[test] + fn resubmitting_voting_should_work() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(1); + + Council::submit_candidacy(&5, 0); + Council::set_approvals(&4, vec![true], 0); + + assert_eq!(Council::approvals_of(4), vec![true]); + + Council::submit_candidacy(&2, 1); + Council::submit_candidacy(&3, 2); + Council::set_approvals(&4, vec![true, false, true], 0); + + assert_eq!(Council::approvals_of(4), vec![true, false, true]); + }); + } + + #[test] + fn retracting_voter_should_work() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(1); + + Council::submit_candidacy(&5, 0); + Council::submit_candidacy(&2, 1); + Council::submit_candidacy(&3, 2); + + Council::set_approvals(&1, vec![true], 0); + Council::set_approvals(&2, vec![false, true, true], 0); + Council::set_approvals(&3, vec![false, true, true], 0); + Council::set_approvals(&4, vec![true, false, true], 0); + + assert_eq!(Council::voters(), vec![1, 2, 3, 4]); + assert_eq!(Council::approvals_of(1), vec![true]); + assert_eq!(Council::approvals_of(2), vec![false, true, true]); + assert_eq!(Council::approvals_of(3), vec![false, true, true]); + assert_eq!(Council::approvals_of(4), vec![true, false, true]); + + Council::retract_voter(&1, 0); + + assert_eq!(Council::voters(), vec![4, 2, 3]); + assert_eq!(Council::approvals_of(1), Vec::::new()); + assert_eq!(Council::approvals_of(2), vec![false, true, true]); + assert_eq!(Council::approvals_of(3), vec![false, true, true]); + assert_eq!(Council::approvals_of(4), vec![true, false, true]); + + Council::retract_voter(&2, 1); + + assert_eq!(Council::voters(), vec![4, 3]); + assert_eq!(Council::approvals_of(1), Vec::::new()); + assert_eq!(Council::approvals_of(2), Vec::::new()); + assert_eq!(Council::approvals_of(3), vec![false, true, true]); + assert_eq!(Council::approvals_of(4), vec![true, false, true]); + + Council::retract_voter(&3, 1); + + assert_eq!(Council::voters(), vec![4]); + assert_eq!(Council::approvals_of(1), Vec::::new()); + assert_eq!(Council::approvals_of(2), Vec::::new()); + assert_eq!(Council::approvals_of(3), Vec::::new()); + assert_eq!(Council::approvals_of(4), vec![true, false, true]); + }); + } + + #[test] + #[should_panic(expected = "retraction index mismatch")] + fn invalid_retraction_index_should_panic() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(1); + Council::submit_candidacy(&3, 0); + Council::set_approvals(&1, vec![true], 0); + Council::set_approvals(&2, vec![true], 0); + Council::retract_voter(&1, 1); + }); + } + + #[test] + #[should_panic(expected = "retraction index invalid")] + fn overflow_retraction_index_should_panic() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(1); + Council::submit_candidacy(&3, 0); + Council::set_approvals(&1, vec![true], 0); + Council::retract_voter(&1, 1); + }); + } + + #[test] + #[should_panic(expected = "cannot retract non-voter")] + fn non_voter_retraction_should_panic() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(1); + Council::submit_candidacy(&3, 0); + Council::set_approvals(&1, vec![true], 0); + Council::retract_voter(&2, 0); + }); + } + + #[test] + fn simple_tally_should_work() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(4); + assert!(!Council::presentation_active()); + + Council::submit_candidacy(&2, 0); + Council::submit_candidacy(&5, 1); + Council::set_approvals(&2, vec![true, false], 0); + Council::set_approvals(&5, vec![false, true], 0); + Council::end_block(System::block_number()); + + System::set_block_number(6); + assert!(Council::presentation_active()); + Council::present_winner(&4, 2, 11, 0); + Council::present_winner(&4, 5, 41, 0); + assert_eq!(Council::leaderboard(), Some(vec![(0, 0), (0, 0), (11, 2), (41, 5)])); + + Council::end_block(System::block_number()); + + assert!(!Council::presentation_active()); + assert_eq!(Council::active_council(), vec![(5, 11), (2, 11)]); + + assert!(!Council::is_a_candidate(&2)); + assert!(!Council::is_a_candidate(&5)); + assert_eq!(Council::vote_index(), 1); + assert_eq!(Council::voter_last_active(2), Some(0)); + assert_eq!(Council::voter_last_active(5), Some(0)); + }); + } + + #[test] + fn double_presentations_should_be_punished() { + with_externalities(&mut new_test_ext(false), || { + assert!(Staking::can_slash(&4, 10)); + + System::set_block_number(4); + Council::submit_candidacy(&2, 0); + Council::submit_candidacy(&5, 1); + Council::set_approvals(&2, vec![true, false], 0); + Council::set_approvals(&5, vec![false, true], 0); + Council::end_block(System::block_number()); + + System::set_block_number(6); + Council::present_winner(&4, 2, 11, 0); + Council::present_winner(&4, 5, 41, 0); + Council::present_winner(&4, 5, 41, 0); + Council::end_block(System::block_number()); + + assert_eq!(Council::active_council(), vec![(5, 11), (2, 11)]); + assert_eq!(Staking::balance(&4), 38); + }); + } + + #[test] + fn retracting_inactive_voter_should_work() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(4); + Council::submit_candidacy(&2, 0); + Council::set_approvals(&2, vec![true], 0); + Council::end_block(System::block_number()); + + System::set_block_number(6); + Council::present_winner(&4, 2, 11, 0); + Council::end_block(System::block_number()); + + System::set_block_number(8); + Council::submit_candidacy(&5, 0); + Council::set_approvals(&5, vec![true], 1); + Council::end_block(System::block_number()); + + System::set_block_number(10); + Council::present_winner(&4, 5, 41, 1); + Council::end_block(System::block_number()); + + Council::reap_inactive_voter(&5, + Council::voters().iter().position(|&i| i == 5).unwrap() as u32, + 2, Council::voters().iter().position(|&i| i == 2).unwrap() as u32, + 2 + ); + + assert_eq!(Council::voters(), vec![5]); + assert_eq!(Council::approvals_of(2).len(), 0); + assert_eq!(Staking::balance(&2), 17); + assert_eq!(Staking::balance(&5), 53); + }); + } + + #[test] + #[should_panic(expected = "candidate must not form a duplicated member if elected")] + fn presenting_for_double_election_should_panic() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(4); + Council::submit_candidacy(&2, 0); + Council::set_approvals(&2, vec![true], 0); + Council::end_block(System::block_number()); + + System::set_block_number(6); + Council::present_winner(&4, 2, 11, 0); + Council::end_block(System::block_number()); + + System::set_block_number(8); + Council::submit_candidacy(&2, 0); + Council::set_approvals(&2, vec![true], 1); + Council::end_block(System::block_number()); + + System::set_block_number(10); + Council::present_winner(&4, 2, 11, 1); + }); + } + + #[test] + fn retracting_inactive_voter_with_other_candidates_in_slots_should_work() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(4); + Council::submit_candidacy(&2, 0); + Council::set_approvals(&2, vec![true], 0); + Council::end_block(System::block_number()); + + System::set_block_number(6); + Council::present_winner(&4, 2, 11, 0); + Council::end_block(System::block_number()); + + System::set_block_number(8); + Council::submit_candidacy(&5, 0); + Council::set_approvals(&5, vec![true], 1); + Council::end_block(System::block_number()); + + System::set_block_number(10); + Council::present_winner(&4, 5, 41, 1); + Council::end_block(System::block_number()); + + System::set_block_number(11); + Council::submit_candidacy(&1, 0); + + Council::reap_inactive_voter(&5, + Council::voters().iter().position(|&i| i == 5).unwrap() as u32, + 2, Council::voters().iter().position(|&i| i == 2).unwrap() as u32, + 2 + ); + + assert_eq!(Council::voters(), vec![5]); + assert_eq!(Council::approvals_of(2).len(), 0); + assert_eq!(Staking::balance(&2), 17); + assert_eq!(Staking::balance(&5), 53); + }); + } + + #[test] + #[should_panic(expected = "bad reporter index")] + fn retracting_inactive_voter_with_bad_reporter_index_should_panic() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(4); + Council::submit_candidacy(&2, 0); + Council::set_approvals(&2, vec![true], 0); + Council::end_block(System::block_number()); + + System::set_block_number(6); + Council::present_winner(&4, 2, 8, 0); + Council::end_block(System::block_number()); + + System::set_block_number(8); + Council::submit_candidacy(&5, 0); + Council::set_approvals(&5, vec![true], 1); + Council::end_block(System::block_number()); + + System::set_block_number(10); + Council::present_winner(&4, 5, 38, 1); + Council::end_block(System::block_number()); + + Council::reap_inactive_voter(&2, + 42, + 2, Council::voters().iter().position(|&i| i == 2).unwrap() as u32, + 2 + ); + }); + } + + #[test] + #[should_panic(expected = "bad target index")] + fn retracting_inactive_voter_with_bad_target_index_should_panic() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(4); + Council::submit_candidacy(&2, 0); + Council::set_approvals(&2, vec![true], 0); + Council::end_block(System::block_number()); + + System::set_block_number(6); + Council::present_winner(&4, 2, 8, 0); + Council::end_block(System::block_number()); + + System::set_block_number(8); + Council::submit_candidacy(&5, 0); + Council::set_approvals(&5, vec![true], 1); + Council::end_block(System::block_number()); + + System::set_block_number(10); + Council::present_winner(&4, 5, 38, 1); + Council::end_block(System::block_number()); + + Council::reap_inactive_voter(&2, + Council::voters().iter().position(|&i| i == 2).unwrap() as u32, + 2, 42, + 2 + ); + }); + } + + #[test] + fn attempting_to_retract_active_voter_should_slash_reporter() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(4); + Council::submit_candidacy(&2, 0); + Council::submit_candidacy(&3, 1); + Council::submit_candidacy(&4, 2); + Council::submit_candidacy(&5, 3); + Council::set_approvals(&2, vec![true, false, false, false], 0); + Council::set_approvals(&3, vec![false, true, false, false], 0); + Council::set_approvals(&4, vec![false, false, true, false], 0); + Council::set_approvals(&5, vec![false, false, false, true], 0); + Council::end_block(System::block_number()); + + System::set_block_number(6); + Council::present_winner(&4, 2, 11, 0); + Council::present_winner(&4, 3, 21, 0); + Council::present_winner(&4, 4, 31, 0); + Council::present_winner(&4, 5, 41, 0); + Council::end_block(System::block_number()); + + System::set_block_number(8); + Council::set_desired_seats(3); + Council::end_block(System::block_number()); + + System::set_block_number(10); + Council::present_winner(&4, 2, 11, 1); + Council::present_winner(&4, 3, 21, 1); + Council::end_block(System::block_number()); + + Council::reap_inactive_voter(&4, + Council::voters().iter().position(|&i| i == 4).unwrap() as u32, + 2, Council::voters().iter().position(|&i| i == 2).unwrap() as u32, + 2 + ); + + assert_eq!(Council::voters(), vec![2, 3, 5]); + assert_eq!(Council::approvals_of(4).len(), 0); + assert_eq!(Staking::balance(&4), 37); + }); + } + + #[test] + #[should_panic(expected = "reaper must be a voter")] + fn attempting_to_retract_inactive_voter_by_nonvoter_should_panic() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(4); + Council::submit_candidacy(&2, 0); + Council::set_approvals(&2, vec![true], 0); + Council::end_block(System::block_number()); + + System::set_block_number(6); + Council::present_winner(&4, 2, 11, 0); + Council::end_block(System::block_number()); + + System::set_block_number(8); + Council::submit_candidacy(&5, 0); + Council::set_approvals(&5, vec![true], 1); + Council::end_block(System::block_number()); + + System::set_block_number(10); + Council::present_winner(&4, 5, 41, 1); + Council::end_block(System::block_number()); + + Council::reap_inactive_voter(&4, + 0, + 2, Council::voters().iter().position(|&i| i == 2).unwrap() as u32, + 2 + ); + }); + } + + #[test] + #[should_panic(expected = "candidate not worthy of leaderboard")] + fn presenting_loser_should_panic() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(4); + Council::submit_candidacy(&1, 0); + Council::set_approvals(&6, vec![true], 0); + Council::submit_candidacy(&2, 1); + Council::set_approvals(&2, vec![false, true], 0); + Council::submit_candidacy(&3, 2); + Council::set_approvals(&3, vec![false, false, true], 0); + Council::submit_candidacy(&4, 3); + Council::set_approvals(&4, vec![false, false, false, true], 0); + Council::submit_candidacy(&5, 4); + Council::set_approvals(&5, vec![false, false, false, false, true], 0); + Council::end_block(System::block_number()); + + System::set_block_number(6); + Council::present_winner(&4, 1, 60, 0); + Council::present_winner(&4, 3, 21, 0); + Council::present_winner(&4, 4, 31, 0); + Council::present_winner(&4, 5, 41, 0); + Council::present_winner(&4, 2, 11, 0); + }); + } + + #[test] + fn presenting_loser_first_should_not_matter() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(4); + Council::submit_candidacy(&1, 0); + Council::set_approvals(&6, vec![true], 0); + Council::submit_candidacy(&2, 1); + Council::set_approvals(&2, vec![false, true], 0); + Council::submit_candidacy(&3, 2); + Council::set_approvals(&3, vec![false, false, true], 0); + Council::submit_candidacy(&4, 3); + Council::set_approvals(&4, vec![false, false, false, true], 0); + Council::submit_candidacy(&5, 4); + Council::set_approvals(&5, vec![false, false, false, false, true], 0); + Council::end_block(System::block_number()); + + System::set_block_number(6); + Council::present_winner(&4, 2, 11, 0); + Council::present_winner(&4, 1, 60, 0); + Council::present_winner(&4, 3, 21, 0); + Council::present_winner(&4, 4, 31, 0); + Council::present_winner(&4, 5, 41, 0); + + assert_eq!(Council::leaderboard(), Some(vec![ + (21, 3), + (31, 4), + (41, 5), + (60, 1) + ])); + }); + } + + #[test] + #[should_panic(expected = "cannot present outside of presentation period")] + fn present_panics_outside_of_presentation_period() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(4); + assert!(!Council::presentation_active()); + Council::present_winner(&5, 5, 1, 0); + }); + } + + #[test] + #[should_panic(expected = "index not current")] + fn present_panics_with_invalid_vote_index() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(4); + Council::submit_candidacy(&2, 0); + Council::submit_candidacy(&5, 1); + Council::set_approvals(&2, vec![true, false], 0); + Council::set_approvals(&5, vec![false, true], 0); + Council::end_block(System::block_number()); + + System::set_block_number(6); + Council::present_winner(&4, 2, 11, 1); + }); + } + + #[test] + #[should_panic(expected = "presenter must have sufficient slashable funds")] + fn present_panics_when_presenter_is_poor() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(4); + assert!(!Council::presentation_active()); + + Council::submit_candidacy(&1, 0); + Council::submit_candidacy(&5, 1); + Council::set_approvals(&2, vec![true, false], 0); + Council::set_approvals(&5, vec![false, true], 0); + Council::end_block(System::block_number()); + + System::set_block_number(6); + assert_eq!(Staking::balance(&1), 1); + Council::present_winner(&1, 1, 30, 0); + }); + } + + #[test] + fn invalid_present_tally_should_slash() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(4); + assert!(!Council::presentation_active()); + assert_eq!(Staking::balance(&4), 40); + + Council::submit_candidacy(&2, 0); + Council::submit_candidacy(&5, 1); + Council::set_approvals(&2, vec![true, false], 0); + Council::set_approvals(&5, vec![false, true], 0); + Council::end_block(System::block_number()); + + System::set_block_number(6); + Council::present_winner(&4, 2, 80, 0); + + assert_eq!(Staking::balance(&4), 38); + }); + } + + #[test] + fn runners_up_should_be_kept() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(4); + assert!(!Council::presentation_active()); + + Council::submit_candidacy(&1, 0); + Council::set_approvals(&6, vec![true], 0); + Council::submit_candidacy(&2, 1); + Council::set_approvals(&2, vec![false, true], 0); + Council::submit_candidacy(&3, 2); + Council::set_approvals(&3, vec![false, false, true], 0); + Council::submit_candidacy(&4, 3); + Council::set_approvals(&4, vec![false, false, false, true], 0); + Council::submit_candidacy(&5, 4); + Council::set_approvals(&5, vec![false, false, false, false, true], 0); + + Council::end_block(System::block_number()); + + System::set_block_number(6); + assert!(Council::presentation_active()); + Council::present_winner(&4, 1, 60, 0); + assert_eq!(Council::leaderboard(), Some(vec![ + (0, 0), + (0, 0), + (0, 0), + (60, 1) + ])); + Council::present_winner(&4, 3, 21, 0); + Council::present_winner(&4, 4, 31, 0); + Council::present_winner(&4, 5, 41, 0); + assert_eq!(Council::leaderboard(), Some(vec![ + (21, 3), + (31, 4), + (41, 5), + (60, 1) + ])); + + Council::end_block(System::block_number()); + + assert!(!Council::presentation_active()); + assert_eq!(Council::active_council(), vec![(1, 11), (5, 11)]); + + assert!(!Council::is_a_candidate(&1)); + assert!(!Council::is_a_candidate(&5)); + assert!(!Council::is_a_candidate(&2)); + assert!(Council::is_a_candidate(&3)); + assert!(Council::is_a_candidate(&4)); + assert_eq!(Council::vote_index(), 1); + assert_eq!(Council::voter_last_active(2), Some(0)); + assert_eq!(Council::voter_last_active(3), Some(0)); + assert_eq!(Council::voter_last_active(4), Some(0)); + assert_eq!(Council::voter_last_active(5), Some(0)); + assert_eq!(Council::voter_last_active(6), Some(0)); + assert_eq!(Council::candidate_reg_info(3), Some((0, 2))); + assert_eq!(Council::candidate_reg_info(4), Some((0, 3))); + }); + } + + #[test] + fn second_tally_should_use_runners_up() { + with_externalities(&mut new_test_ext(false), || { + System::set_block_number(4); + Council::submit_candidacy(&1, 0); + Council::set_approvals(&6, vec![true], 0); + Council::submit_candidacy(&2, 1); + Council::set_approvals(&2, vec![false, true], 0); + Council::submit_candidacy(&3, 2); + Council::set_approvals(&3, vec![false, false, true], 0); + Council::submit_candidacy(&4, 3); + Council::set_approvals(&4, vec![false, false, false, true], 0); + Council::submit_candidacy(&5, 4); + Council::set_approvals(&5, vec![false, false, false, false, true], 0); + Council::end_block(System::block_number()); + + System::set_block_number(6); + Council::present_winner(&4, 1, 60, 0); + Council::present_winner(&4, 3, 21, 0); + Council::present_winner(&4, 4, 31, 0); + Council::present_winner(&4, 5, 41, 0); + Council::end_block(System::block_number()); + + System::set_block_number(8); + Council::set_approvals(&6, vec![false, false, true, false], 1); + Council::set_desired_seats(3); + Council::end_block(System::block_number()); + + System::set_block_number(10); + Council::present_winner(&4, 3, 81, 1); + Council::present_winner(&4, 4, 31, 1); + Council::end_block(System::block_number()); + + assert!(!Council::presentation_active()); + assert_eq!(Council::active_council(), vec![(1, 11), (5, 11), (3, 15)]); + + assert!(!Council::is_a_candidate(&1)); + assert!(!Council::is_a_candidate(&2)); + assert!(!Council::is_a_candidate(&3)); + assert!(!Council::is_a_candidate(&5)); + assert!(Council::is_a_candidate(&4)); + assert_eq!(Council::vote_index(), 2); + assert_eq!(Council::voter_last_active(2), Some(0)); + assert_eq!(Council::voter_last_active(3), Some(0)); + assert_eq!(Council::voter_last_active(4), Some(0)); + assert_eq!(Council::voter_last_active(5), Some(0)); + assert_eq!(Council::voter_last_active(6), Some(1)); + + assert_eq!(Council::candidate_reg_info(4), Some((0, 3))); + }); + } +} diff --git a/substrate/runtime/council/src/voting.rs b/substrate/runtime/council/src/voting.rs new file mode 100644 index 0000000000000..13cce3f9158ca --- /dev/null +++ b/substrate/runtime/council/src/voting.rs @@ -0,0 +1,457 @@ +// 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 . + +//! Council voting system. + +use rstd::prelude::*; +use rstd::borrow::Borrow; +use primitives::traits::{Executable, RefInto}; +use runtime_io::Hashing; +use runtime_support::{StorageValue, StorageMap, IsSubType}; +use {system, democracy}; +use super::{Trait, Module as Council}; + +decl_module! { + pub struct Module; + pub enum Call where aux: T::PublicAux { + fn propose(aux, proposal: Box) = 0; + fn vote(aux, proposal: T::Hash, approve: bool) = 1; + fn veto(aux, proposal_hash: T::Hash) = 2; + } + pub enum PrivCall { + fn set_cooloff_period(blocks: T::BlockNumber) = 0; + fn set_voting_period(blocks: T::BlockNumber) = 1; + } +} + +decl_storage! { + trait Store for Module; + + pub CooloffPeriod get(cooloff_period): b"cov:cooloff" => required T::BlockNumber; + pub VotingPeriod get(voting_period): b"cov:period" => required T::BlockNumber; + pub Proposals get(proposals): b"cov:prs" => default Vec<(T::BlockNumber, T::Hash)>; // ordered by expiry. + pub ProposalOf get(proposal_of): b"cov:pro" => map [ T::Hash => T::Proposal ]; + pub ProposalVoters get(proposal_voters): b"cov:voters:" => default map [ T::Hash => Vec ]; + pub CouncilVoteOf get(vote_of): b"cov:vote:" => map [ (T::Hash, T::AccountId) => bool ]; + pub VetoedProposal get(veto_of): b"cov:veto:" => map [ T::Hash => (T::BlockNumber, Vec) ]; +} + +impl Module { + pub fn is_vetoed>(proposal: B) -> bool { + Self::veto_of(proposal.borrow()) + .map(|(expiry, _): (T::BlockNumber, Vec)| >::block_number() < expiry) + .unwrap_or(false) + } + + pub fn will_still_be_councillor_at(who: &T::AccountId, n: T::BlockNumber) -> bool { + >::active_council().iter() + .find(|&&(ref a, _)| a == who) + .map(|&(_, expires)| expires > n) + .unwrap_or(false) + } + + pub fn is_councillor(who: &T::AccountId) -> bool { + >::active_council().iter() + .any(|&(ref a, _)| a == who) + } + + pub fn tally(proposal_hash: &T::Hash) -> (u32, u32, u32) { + Self::generic_tally(proposal_hash, |w: &T::AccountId, p: &T::Hash| Self::vote_of((*p, w.clone()))) + } + + // Dispatch + fn propose(aux: &T::PublicAux, proposal: Box) { + let expiry = >::block_number() + Self::voting_period(); + assert!(Self::will_still_be_councillor_at(aux.ref_into(), expiry)); + + let proposal_hash = T::Hashing::hash_of(&proposal); + + assert!(!Self::is_vetoed(&proposal_hash)); + + let mut proposals = Self::proposals(); + proposals.push((expiry, proposal_hash)); + proposals.sort_by_key(|&(expiry, _)| expiry); + Self::set_proposals(&proposals); + + >::insert(proposal_hash, *proposal); + >::insert(proposal_hash, vec![aux.ref_into().clone()]); + >::insert((proposal_hash, aux.ref_into().clone()), true); + } + + fn vote(aux: &T::PublicAux, proposal: T::Hash, approve: bool) { + if Self::vote_of((proposal, aux.ref_into().clone())).is_none() { + let mut voters = Self::proposal_voters(&proposal); + voters.push(aux.ref_into().clone()); + >::insert(proposal, voters); + } + >::insert((proposal, aux.ref_into().clone()), approve); + } + + fn veto(aux: &T::PublicAux, proposal_hash: T::Hash) { + assert!(Self::is_councillor(aux.ref_into()), "only councillors may veto council proposals"); + assert!(>::exists(&proposal_hash), "proposal must exist to be vetoed"); + + let mut existing_vetoers = Self::veto_of(&proposal_hash) + .map(|pair| pair.1) + .unwrap_or_else(Vec::new); + let insert_position = existing_vetoers.binary_search(aux.ref_into()) + .expect_err("a councillor may not veto a proposal twice"); + existing_vetoers.insert(insert_position, aux.ref_into().clone()); + Self::set_veto_of(&proposal_hash, >::block_number() + Self::cooloff_period(), existing_vetoers); + + Self::set_proposals(&Self::proposals().into_iter().filter(|&(_, h)| h != proposal_hash).collect::>()); + >::remove(proposal_hash); + >::remove(proposal_hash); + for (c, _) in >::active_council() { + >::remove((proposal_hash, c)); + } + } + + fn set_cooloff_period(blocks: T::BlockNumber) { + >::put(blocks); + } + + fn set_voting_period(blocks: T::BlockNumber) { + >::put(blocks); + } + + // private + + + fn set_veto_of(proposal: &T::Hash, expiry: T::BlockNumber, vetoers: Vec) { + >::insert(proposal, (expiry, vetoers)); + } + + fn kill_veto_of(proposal: &T::Hash) { + >::remove(proposal); + } + + fn take_tally(proposal_hash: &T::Hash) -> (u32, u32, u32) { + Self::generic_tally(proposal_hash, |w: &T::AccountId, p: &T::Hash| >::take((*p, w.clone()))) + } + + fn generic_tally Option>(proposal_hash: &T::Hash, vote_of: F) -> (u32, u32, u32) { + let c = >::active_council(); + let (approve, reject) = c.iter() + .filter_map(|&(ref a, _)| vote_of(a, proposal_hash)) + .map(|approve| if approve { (1, 0) } else { (0, 1) }) + .fold((0, 0), |(a, b), (c, d)| (a + c, b + d)); + (approve, reject, c.len() as u32 - approve - reject) + } + + fn set_proposals(p: &Vec<(T::BlockNumber, T::Hash)>) { + >::put(p); + } + + fn take_proposal_if_expiring_at(n: T::BlockNumber) -> Option<(T::Proposal, T::Hash)> { + let proposals = Self::proposals(); + match proposals.first() { + Some(&(expiry, hash)) if expiry == n => { + // yes this is horrible, but fixing it will need substantial work in storage. + Self::set_proposals(&proposals[1..].to_vec()); + let proposal = >::take(hash).expect("all queued proposal hashes must have associated proposals"); + Some((proposal, hash)) + } + _ => None, + } + } + + fn end_block(now: T::BlockNumber) { + while let Some((proposal, proposal_hash)) = Self::take_proposal_if_expiring_at(now) { + let tally = Self::take_tally(&proposal_hash); + if let Some(&democracy::PrivCall::cancel_referendum(ref_index)) = IsSubType::>::is_sub_type(&proposal) { + if let (_, 0, 0) = tally { + >::internal_cancel_referendum(ref_index); + } + } else { + if tally.0 > tally.1 + tally.2 { + Self::kill_veto_of(&proposal_hash); + match tally { + (_, 0, 0) => >::internal_start_referendum(proposal, democracy::VoteThreshold::SuperMajorityAgainst), + _ => >::internal_start_referendum(proposal, democracy::VoteThreshold::SimpleMajority), + }; + } + } + } + } +} + +impl Executable for Council { + fn execute() { + let n = >::block_number(); + Self::end_block(n); + >::end_block(n); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ::tests::*; + use runtime_support::Hashable; + use democracy::VoteThreshold; + + type CouncilVoting = super::Module; + + #[test] + fn basic_environment_works() { + with_externalities(&mut new_test_ext(true), || { + System::set_block_number(1); + assert_eq!(Staking::bonding_duration(), 0); + assert_eq!(CouncilVoting::cooloff_period(), 2); + assert_eq!(CouncilVoting::voting_period(), 1); + assert_eq!(CouncilVoting::will_still_be_councillor_at(&1, 1), true); + assert_eq!(CouncilVoting::will_still_be_councillor_at(&1, 10), false); + assert_eq!(CouncilVoting::will_still_be_councillor_at(&4, 10), false); + assert_eq!(CouncilVoting::is_councillor(&1), true); + assert_eq!(CouncilVoting::is_councillor(&4), false); + assert_eq!(CouncilVoting::proposals(), Vec::<(u64, H256)>::new()); + assert_eq!(CouncilVoting::proposal_voters(H256::default()), Vec::::new()); + assert_eq!(CouncilVoting::is_vetoed(&H256::default()), false); + assert_eq!(CouncilVoting::vote_of((H256::default(), 1)), None); + assert_eq!(CouncilVoting::tally(&H256::default()), (0, 0, 3)); + }); + } + + fn bonding_duration_proposal(value: u64) -> Proposal { + Proposal::Staking(staking::PrivCall::set_bonding_duration(value)) + } + + fn cancel_referendum_proposal(id: u32) -> Proposal { + Proposal::Democracy(democracy::PrivCall::cancel_referendum(id)) + } + + #[test] + fn referendum_cancellation_should_work_when_unanimous() { + with_externalities(&mut new_test_ext(true), || { + System::set_block_number(1); + let proposal = bonding_duration_proposal(42); + Democracy::internal_start_referendum(proposal.clone(), VoteThreshold::SuperMajorityApprove); + assert_eq!(Democracy::active_referendums(), vec![(0, 4, proposal, VoteThreshold::SuperMajorityApprove)]); + + let cancellation = cancel_referendum_proposal(0); + let hash = cancellation.blake2_256().into(); + CouncilVoting::propose(&1, Box::new(cancellation)); + CouncilVoting::vote(&2, hash, true); + CouncilVoting::vote(&3, hash, true); + assert_eq!(CouncilVoting::proposals(), vec![(2, hash)]); + CouncilVoting::end_block(System::block_number()); + + System::set_block_number(2); + CouncilVoting::end_block(System::block_number()); + assert_eq!(Democracy::active_referendums(), vec![]); + assert_eq!(Staking::bonding_duration(), 0); + }); + } + + #[test] + fn referendum_cancellation_should_fail_when_not_unanimous() { + with_externalities(&mut new_test_ext(true), || { + System::set_block_number(1); + let proposal = bonding_duration_proposal(42); + Democracy::internal_start_referendum(proposal.clone(), VoteThreshold::SuperMajorityApprove); + + let cancellation = cancel_referendum_proposal(0); + let hash = cancellation.blake2_256().into(); + CouncilVoting::propose(&1, Box::new(cancellation)); + CouncilVoting::vote(&2, hash, true); + CouncilVoting::vote(&3, hash, false); + CouncilVoting::end_block(System::block_number()); + + System::set_block_number(2); + CouncilVoting::end_block(System::block_number()); + assert_eq!(Democracy::active_referendums(), vec![(0, 4, proposal, VoteThreshold::SuperMajorityApprove)]); + }); + } + + #[test] + fn referendum_cancellation_should_fail_when_abstentions() { + with_externalities(&mut new_test_ext(true), || { + System::set_block_number(1); + let proposal = bonding_duration_proposal(42); + Democracy::internal_start_referendum(proposal.clone(), VoteThreshold::SuperMajorityApprove); + + let cancellation = cancel_referendum_proposal(0); + let hash = cancellation.blake2_256().into(); + CouncilVoting::propose(&1, Box::new(cancellation)); + CouncilVoting::vote(&2, hash, true); + CouncilVoting::end_block(System::block_number()); + + System::set_block_number(2); + CouncilVoting::end_block(System::block_number()); + assert_eq!(Democracy::active_referendums(), vec![(0, 4, proposal, VoteThreshold::SuperMajorityApprove)]); + }); + } + + #[test] + fn veto_should_work() { + with_externalities(&mut new_test_ext(true), || { + System::set_block_number(1); + let proposal = bonding_duration_proposal(42); + let hash = proposal.blake2_256().into(); + CouncilVoting::propose(&1, Box::new(proposal.clone())); + CouncilVoting::veto(&2, hash); + assert_eq!(CouncilVoting::proposals().len(), 0); + assert_eq!(Democracy::active_referendums().len(), 0); + }); + } + + #[test] + #[should_panic] + fn double_veto_should_panic() { + with_externalities(&mut new_test_ext(true), || { + System::set_block_number(1); + let proposal = bonding_duration_proposal(42); + let hash = proposal.blake2_256().into(); + CouncilVoting::propose(&1, Box::new(proposal.clone())); + CouncilVoting::veto(&2, hash); + + System::set_block_number(3); + CouncilVoting::propose(&1, Box::new(proposal.clone())); + CouncilVoting::veto(&2, hash); + }); + } + + #[test] + #[should_panic] + fn retry_in_cooloff_should_panic() { + with_externalities(&mut new_test_ext(true), || { + System::set_block_number(1); + let proposal = bonding_duration_proposal(42); + let hash = proposal.blake2_256().into(); + CouncilVoting::propose(&1, Box::new(proposal.clone())); + CouncilVoting::veto(&2, hash); + + System::set_block_number(2); + CouncilVoting::propose(&1, Box::new(proposal.clone())); + }); + } + + #[test] + fn retry_after_cooloff_should_work() { + with_externalities(&mut new_test_ext(true), || { + System::set_block_number(1); + let proposal = bonding_duration_proposal(42); + let hash = proposal.blake2_256().into(); + CouncilVoting::propose(&1, Box::new(proposal.clone())); + CouncilVoting::veto(&2, hash); + + System::set_block_number(3); + CouncilVoting::propose(&1, Box::new(proposal.clone())); + CouncilVoting::vote(&2, hash, false); + CouncilVoting::vote(&3, hash, true); + CouncilVoting::end_block(System::block_number()); + + System::set_block_number(4); + CouncilVoting::end_block(System::block_number()); + assert_eq!(CouncilVoting::proposals().len(), 0); + assert_eq!(Democracy::active_referendums(), vec![(0, 7, bonding_duration_proposal(42), VoteThreshold::SimpleMajority)]); + }); + } + + #[test] + fn alternative_double_veto_should_work() { + with_externalities(&mut new_test_ext(true), || { + System::set_block_number(1); + let proposal = bonding_duration_proposal(42); + let hash = proposal.blake2_256().into(); + CouncilVoting::propose(&1, Box::new(proposal.clone())); + CouncilVoting::veto(&2, hash); + + System::set_block_number(3); + CouncilVoting::propose(&1, Box::new(proposal.clone())); + CouncilVoting::veto(&3, hash); + assert_eq!(CouncilVoting::proposals().len(), 0); + assert_eq!(Democracy::active_referendums().len(), 0); + }); + } + + #[test] + fn simple_propose_should_work() { + with_externalities(&mut new_test_ext(true), || { + System::set_block_number(1); + let proposal = bonding_duration_proposal(42); + let hash = proposal.blake2_256().into(); + CouncilVoting::propose(&1, Box::new(proposal.clone())); + assert_eq!(CouncilVoting::proposals().len(), 1); + assert_eq!(CouncilVoting::proposal_voters(&hash), vec![1]); + assert_eq!(CouncilVoting::vote_of((hash, 1)), Some(true)); + assert_eq!(CouncilVoting::tally(&hash), (1, 0, 2)); + }); + } + + #[test] + fn unvoted_proposal_should_expire_without_action() { + with_externalities(&mut new_test_ext(true), || { + System::set_block_number(1); + let proposal = bonding_duration_proposal(42); + CouncilVoting::propose(&1, Box::new(proposal.clone())); + assert_eq!(CouncilVoting::tally(&proposal.blake2_256().into()), (1, 0, 2)); + CouncilVoting::end_block(System::block_number()); + + System::set_block_number(2); + CouncilVoting::end_block(System::block_number()); + assert_eq!(CouncilVoting::proposals().len(), 0); + assert_eq!(Democracy::active_referendums().len(), 0); + }); + } + + #[test] + fn unanimous_proposal_should_expire_with_biased_referendum() { + with_externalities(&mut new_test_ext(true), || { + System::set_block_number(1); + let proposal = bonding_duration_proposal(42); + CouncilVoting::propose(&1, Box::new(proposal.clone())); + CouncilVoting::vote(&2, proposal.blake2_256().into(), true); + CouncilVoting::vote(&3, proposal.blake2_256().into(), true); + assert_eq!(CouncilVoting::tally(&proposal.blake2_256().into()), (3, 0, 0)); + CouncilVoting::end_block(System::block_number()); + + System::set_block_number(2); + CouncilVoting::end_block(System::block_number()); + assert_eq!(CouncilVoting::proposals().len(), 0); + assert_eq!(Democracy::active_referendums(), vec![(0, 5, proposal, VoteThreshold::SuperMajorityAgainst)]); + }); + } + + #[test] + fn majority_proposal_should_expire_with_unbiased_referendum() { + with_externalities(&mut new_test_ext(true), || { + System::set_block_number(1); + let proposal = bonding_duration_proposal(42); + CouncilVoting::propose(&1, Box::new(proposal.clone())); + CouncilVoting::vote(&2, proposal.blake2_256().into(), true); + CouncilVoting::vote(&3, proposal.blake2_256().into(), false); + assert_eq!(CouncilVoting::tally(&proposal.blake2_256().into()), (2, 1, 0)); + CouncilVoting::end_block(System::block_number()); + + System::set_block_number(2); + CouncilVoting::end_block(System::block_number()); + assert_eq!(CouncilVoting::proposals().len(), 0); + assert_eq!(Democracy::active_referendums(), vec![(0, 5, proposal, VoteThreshold::SimpleMajority)]); + }); + } + + #[test] + #[should_panic] + fn propose_by_public_should_panic() { + with_externalities(&mut new_test_ext(true), || { + System::set_block_number(1); + let proposal = bonding_duration_proposal(42); + CouncilVoting::propose(&4, Box::new(proposal)); + }); + } +} diff --git a/substrate/runtime/democracy/Cargo.toml b/substrate/runtime/democracy/Cargo.toml new file mode 100644 index 0000000000000..74007bb9e06d2 --- /dev/null +++ b/substrate/runtime/democracy/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "substrate-runtime-democracy" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +hex-literal = "0.1.0" +serde = { version = "1.0", default_features = false } +safe-mix = { path = "../../../safe-mix", default_features = false} +substrate-codec = { path = "../../codec", default_features = false } +substrate-primitives = { path = "../../primitives", default_features = false } +substrate-runtime-std = { path = "../../runtime-std", default_features = false } +substrate-runtime-io = { path = "../../runtime-io", default_features = false } +substrate-runtime-support = { path = "../../runtime-support", default_features = false } +substrate-runtime-primitives = { path = "../primitives", default_features = false } +substrate-runtime-consensus = { path = "../consensus", default_features = false } +substrate-runtime-session = { path = "../session", default_features = false } +substrate-runtime-staking = { path = "../staking", default_features = false } +substrate-runtime-system = { path = "../system", default_features = false } + +[features] +default = ["std"] +std = [ + "serde/std", + "safe-mix/std", + "substrate-codec/std", + "substrate-primitives/std", + "substrate-runtime-std/std", + "substrate-runtime-io/std", + "substrate-runtime-support/std", + "substrate-runtime-primitives/std", + "substrate-runtime-consensus/std", + "substrate-runtime-session/std", + "substrate-runtime-staking/std", + "substrate-runtime-system/std", +] diff --git a/substrate/runtime/democracy/src/lib.rs b/substrate/runtime/democracy/src/lib.rs new file mode 100644 index 0000000000000..2ffe1441a383b --- /dev/null +++ b/substrate/runtime/democracy/src/lib.rs @@ -0,0 +1,659 @@ +// 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 . + +//! Democratic system: Handles administration of general stakeholder voting. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "std")] +extern crate serde; + +#[macro_use] +extern crate substrate_runtime_support as runtime_support; + +#[cfg(feature = "std")] +extern crate substrate_primitives; + +#[macro_use] +extern crate substrate_runtime_std as rstd; + +extern crate substrate_codec as codec; +extern crate substrate_runtime_io as runtime_io; +extern crate substrate_runtime_primitives as primitives; +extern crate substrate_runtime_consensus as consensus; +extern crate substrate_runtime_session as session; +extern crate substrate_runtime_staking as staking; +extern crate substrate_runtime_system as system; + +use rstd::prelude::*; +use primitives::traits::{Zero, Executable, RefInto, As}; +use runtime_support::{StorageValue, StorageMap, Parameter, Dispatchable, IsSubType}; + +mod vote_threshold; +pub use vote_threshold::{Approved, VoteThreshold}; + +/// A proposal index. +pub type PropIndex = u32; +/// A referendum index. +pub type ReferendumIndex = u32; + +pub trait Trait: staking::Trait + Sized { + type Proposal: Parameter + Dispatchable + IsSubType>; +} + +decl_module! { + pub struct Module; + pub enum Call where aux: T::PublicAux { + fn propose(aux, proposal: Box, value: T::Balance) = 0; + fn second(aux, proposal: PropIndex) = 1; + fn vote(aux, ref_index: ReferendumIndex, approve_proposal: bool) = 2; + } + pub enum PrivCall { + fn start_referendum(proposal: Box, vote_threshold: VoteThreshold) = 0; + fn cancel_referendum(ref_index: ReferendumIndex) = 1; + } +} + +decl_storage! { + trait Store for Module; + + // The number of (public) proposals that have been made so far. + pub PublicPropCount get(public_prop_count): b"dem:ppc" => default PropIndex; + // The public proposals. Unsorted. + pub PublicProps get(public_props): b"dem:pub" => default Vec<(PropIndex, T::Proposal, T::AccountId)>; + // Those who have locked a deposit. + pub DepositOf get(deposit_of): b"dem:dep:" => map [ PropIndex => (T::Balance, Vec) ]; + // How often (in blocks) new public referenda are launched. + pub LaunchPeriod get(launch_period): b"dem:lau" => required T::BlockNumber; + // The minimum amount to be used as a deposit for a public referendum proposal. + pub MinimumDeposit get(minimum_deposit): b"dem:min" => required T::Balance; + + // How often (in blocks) to check for new votes. + pub VotingPeriod get(voting_period): b"dem:per" => required T::BlockNumber; + + // The next free referendum index, aka the number of referendums started so far. + pub ReferendumCount get(referendum_count): b"dem:rco" => default ReferendumIndex; + // The next referendum index that should be tallied. + pub NextTally get(next_tally): b"dem:nxt" => default ReferendumIndex; + // Information concerning any given referendum. + pub ReferendumInfoOf get(referendum_info): b"dem:pro:" => map [ ReferendumIndex => (T::BlockNumber, T::Proposal, VoteThreshold) ]; + + // Get the voters for the current proposal. + pub VotersFor get(voters_for): b"dem:vtr:" => default map [ ReferendumIndex => Vec ]; + + // Get the vote, if Some, of `who`. + pub VoteOf get(vote_of): b"dem:vot:" => map [ (ReferendumIndex, T::AccountId) => bool ]; +} + +impl Module { + + // exposed immutables. + + /// Get the amount locked in support of `proposal`; false if proposal isn't a valid proposal + /// index. + pub fn locked_for(proposal: PropIndex) -> Option { + Self::deposit_of(proposal).map(|(d, l)| d * T::Balance::sa(l.len())) + } + + /// Return true if `ref_index` is an on-going referendum. + pub fn is_active_referendum(ref_index: ReferendumIndex) -> bool { + >::exists(ref_index) + } + + /// Get all referendums currently active. + pub fn active_referendums() -> Vec<(ReferendumIndex, T::BlockNumber, T::Proposal, VoteThreshold)> { + let next = Self::next_tally(); + let last = Self::referendum_count(); + (next..last).into_iter() + .filter_map(|i| Self::referendum_info(i).map(|(n, p, t)| (i, n, p, t))) + .collect() + } + + /// Get all referendums ready for tally at block `n`. + pub fn maturing_referendums_at(n: T::BlockNumber) -> Vec<(ReferendumIndex, T::BlockNumber, T::Proposal, VoteThreshold)> { + let next = Self::next_tally(); + let last = Self::referendum_count(); + (next..last).into_iter() + .filter_map(|i| Self::referendum_info(i).map(|(n, p, t)| (i, n, p, t))) + .take_while(|&(_, block_number, _, _)| block_number == n) + .collect() + } + + /// Get the voters for the current proposal. + pub fn tally(ref_index: ReferendumIndex) -> (T::Balance, T::Balance) { + Self::voters_for(ref_index).iter() + .map(|a| (>::balance(a), Self::vote_of((ref_index, a.clone())).expect("all items come from `voters`; for an item to be in `voters` there must be a vote registered; qed"))) + .map(|(bal, vote)| if vote { (bal, Zero::zero()) } else { (Zero::zero(), bal) }) + .fold((Zero::zero(), Zero::zero()), |(a, b), (c, d)| (a + c, b + d)) + } + + // dispatching. + + /// Propose a sensitive action to be taken. + fn propose(aux: &T::PublicAux, proposal: Box, value: T::Balance) { + assert!(value >= Self::minimum_deposit()); + assert!(>::deduct_unbonded(aux.ref_into(), value)); + + let index = Self::public_prop_count(); + >::put(index + 1); + >::insert(index, (value, vec![aux.ref_into().clone()])); + + let mut props = Self::public_props(); + props.push((index, (*proposal).clone(), aux.ref_into().clone())); + >::put(props); + } + + /// Propose a sensitive action to be taken. + fn second(aux: &T::PublicAux, proposal: PropIndex) { + let mut deposit = Self::deposit_of(proposal).expect("can only second an existing proposal"); + assert!(>::deduct_unbonded(aux.ref_into(), deposit.0)); + + deposit.1.push(aux.ref_into().clone()); + >::insert(proposal, deposit); + } + + /// Vote in a referendum. If `approve_proposal` is true, the vote is to enact the proposal; + /// false would be a vote to keep the status quo.. + fn vote(aux: &T::PublicAux, ref_index: ReferendumIndex, approve_proposal: bool) { + if !Self::is_active_referendum(ref_index) { + panic!("vote given for invalid referendum.") + } + if >::balance(aux.ref_into()).is_zero() { + panic!("transactor must have balance to signal approval."); + } + if !>::exists(&(ref_index, aux.ref_into().clone())) { + let mut voters = Self::voters_for(ref_index); + voters.push(aux.ref_into().clone()); + >::insert(ref_index, voters); + } + >::insert(&(ref_index, aux.ref_into().clone()), approve_proposal); + } + + /// Start a referendum. + fn start_referendum(proposal: Box, vote_threshold: VoteThreshold) { + Self::inject_referendum(>::block_number() + Self::voting_period(), *proposal, vote_threshold); + } + + /// Remove a referendum. + fn cancel_referendum(ref_index: ReferendumIndex) { + Self::clear_referendum(ref_index); + } + + // exposed mutables. + + /// Start a referendum. Can be called directly by the council. + pub fn internal_start_referendum(proposal: T::Proposal, vote_threshold: VoteThreshold) { + >::inject_referendum(>::block_number() + >::voting_period(), proposal, vote_threshold); + } + + /// Remove a referendum. Can be called directly by the council. + pub fn internal_cancel_referendum(ref_index: ReferendumIndex) { + >::clear_referendum(ref_index); + } + + // private. + + /// Start a referendum + fn inject_referendum( + end: T::BlockNumber, + proposal: T::Proposal, + vote_threshold: VoteThreshold + ) -> ReferendumIndex { + let ref_index = Self::referendum_count(); + if ref_index > 0 && Self::referendum_info(ref_index - 1).map(|i| i.0 > end).unwrap_or(false) { + panic!("Cannot inject a referendum that ends earlier than preceeding referendum"); + } + + >::put(ref_index + 1); + >::insert(ref_index, (end, proposal, vote_threshold)); + ref_index + } + + /// Remove all info on a referendum. + fn clear_referendum(ref_index: ReferendumIndex) { + >::remove(ref_index); + >::remove(ref_index); + for v in Self::voters_for(ref_index) { + >::remove((ref_index, v)); + } + } + + /// Current era is ending; we should finish up any proposals. + fn end_block(now: T::BlockNumber) { + // pick out another public referendum if it's time. + if (now % Self::launch_period()).is_zero() { + let mut public_props = Self::public_props(); + if let Some((winner_index, _)) = public_props.iter() + .enumerate() + .max_by_key(|x| Self::locked_for((x.1).0).expect("All current public proposals have an amount locked")) + { + let (prop_index, proposal, _) = public_props.swap_remove(winner_index); + let (deposit, depositors): (T::Balance, Vec) = + >::take(prop_index).expect("depositors always exist for current proposals"); + // refund depositors + for d in &depositors { + >::refund(d, deposit); + } + >::put(public_props); + Self::inject_referendum(now + Self::voting_period(), proposal, VoteThreshold::SuperMajorityApprove); + } + } + + // tally up votes for any expiring referenda. + for (index, _, proposal, vote_threshold) in Self::maturing_referendums_at(now) { + let (approve, against) = Self::tally(index); + let total_stake = >::total_stake(); + Self::clear_referendum(index); + if vote_threshold.approved(approve, against, total_stake) { + proposal.dispatch(); + } + >::put(index + 1); + } + } +} + +impl Executable for Module { + fn execute() { + Self::end_block(>::block_number()); + } +} + +#[cfg(any(feature = "std", test))] +pub struct GenesisConfig { + pub launch_period: T::BlockNumber, + pub voting_period: T::BlockNumber, + pub minimum_deposit: T::Balance, +} + +#[cfg(any(feature = "std", test))] +impl GenesisConfig { + pub fn new() -> Self { + GenesisConfig { + launch_period: T::BlockNumber::sa(1), + voting_period: T::BlockNumber::sa(1), + minimum_deposit: T::Balance::sa(1), + } + } + + pub fn extended() -> Self { + GenesisConfig { + launch_period: T::BlockNumber::sa(1), + voting_period: T::BlockNumber::sa(3), + minimum_deposit: T::Balance::sa(1), + } + } +} + +#[cfg(any(feature = "std", test))] +impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { + launch_period: T::BlockNumber::sa(1000), + voting_period: T::BlockNumber::sa(1000), + minimum_deposit: T::Balance::sa(0), + } + } +} + +#[cfg(any(feature = "std", test))] +impl primitives::BuildExternalities for GenesisConfig +{ + fn build_externalities(self) -> runtime_io::TestExternalities { + use codec::Slicable; + use runtime_io::twox_128; + + map![ + twox_128(>::key()).to_vec() => self.launch_period.encode(), + twox_128(>::key()).to_vec() => self.voting_period.encode(), + twox_128(>::key()).to_vec() => self.minimum_deposit.encode() + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use runtime_io::with_externalities; + use substrate_primitives::H256; + use primitives::BuildExternalities; + use primitives::traits::{HasPublicAux, Identity}; + use primitives::testing::{Digest, Header}; + + impl_outer_dispatch! { + pub enum Proposal { + Session = 0, + Staking = 1, + Democracy = 2, + } + } + + pub struct Test; + impl HasPublicAux for Test { + type PublicAux = u64; + } + impl consensus::Trait for Test { + type SessionKey = u64; + } + impl system::Trait for Test { + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = runtime_io::BlakeTwo256; + type Digest = Digest; + type AccountId = u64; + type Header = Header; + } + impl session::Trait for Test { + type PublicAux = ::PublicAux; + type ConvertAccountIdToSessionKey = Identity; + } + impl staking::Trait for Test { + type Balance = u64; + type DetermineContractAddress = staking::DummyContractAddressFor; + } + impl Trait for Test { + type Proposal = Proposal; + } + + fn new_test_ext() -> runtime_io::TestExternalities { + let mut t = system::GenesisConfig::::default().build_externalities(); + t.extend(consensus::GenesisConfig::{ + authorities: vec![], + }.build_externalities()); + t.extend(session::GenesisConfig::{ + session_length: 1, //??? or 2? + validators: vec![10, 20], + }.build_externalities()); + t.extend(staking::GenesisConfig::{ + sessions_per_era: 1, + current_era: 0, + balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + intentions: vec![], + validator_count: 2, + bonding_duration: 3, + transaction_fee: 0, + }.build_externalities()); + t.extend(GenesisConfig::{ + launch_period: 1, + voting_period: 1, + minimum_deposit: 1, + }.build_externalities()); + t + } + + type System = system::Module; + type Session = session::Module; + type Staking = staking::Module; + type Democracy = Module; + + #[test] + fn params_should_work() { + with_externalities(&mut new_test_ext(), || { + assert_eq!(Democracy::launch_period(), 1); + assert_eq!(Democracy::voting_period(), 1); + assert_eq!(Democracy::minimum_deposit(), 1); + assert_eq!(Democracy::referendum_count(), 0); + assert_eq!(Staking::sessions_per_era(), 1); + assert_eq!(Staking::total_stake(), 210); + }); + } + + fn propose_sessions_per_era(who: u64, value: u64, locked: u64) { + Democracy::propose(&who, Box::new(Proposal::Staking(staking::PrivCall::set_sessions_per_era(value))), locked); + } + + #[test] + fn locked_for_should_work() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(1); + propose_sessions_per_era(1, 2, 2); + propose_sessions_per_era(1, 4, 4); + propose_sessions_per_era(1, 3, 3); + assert_eq!(Democracy::locked_for(0), Some(2)); + assert_eq!(Democracy::locked_for(1), Some(4)); + assert_eq!(Democracy::locked_for(2), Some(3)); + }); + } + + #[test] + fn single_proposal_should_work() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(1); + propose_sessions_per_era(1, 2, 1); + Democracy::end_block(System::block_number()); + + System::set_block_number(2); + let r = 0; + Democracy::vote(&1, r, true); + + assert_eq!(Democracy::referendum_count(), 1); + assert_eq!(Democracy::voters_for(r), vec![1]); + assert_eq!(Democracy::vote_of((r, 1)), Some(true)); + assert_eq!(Democracy::tally(r), (10, 0)); + + Democracy::end_block(System::block_number()); + Staking::check_new_era(); + + assert_eq!(Staking::era_length(), 2); + }); + } + + #[test] + fn deposit_for_proposals_should_be_taken() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(1); + propose_sessions_per_era(1, 2, 5); + Democracy::second(&2, 0); + Democracy::second(&5, 0); + Democracy::second(&5, 0); + Democracy::second(&5, 0); + assert_eq!(Staking::balance(&1), 5); + assert_eq!(Staking::balance(&2), 15); + assert_eq!(Staking::balance(&5), 35); + }); + } + + #[test] + fn deposit_for_proposals_should_be_returned() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(1); + propose_sessions_per_era(1, 2, 5); + Democracy::second(&2, 0); + Democracy::second(&5, 0); + Democracy::second(&5, 0); + Democracy::second(&5, 0); + Democracy::end_block(System::block_number()); + assert_eq!(Staking::balance(&1), 10); + assert_eq!(Staking::balance(&2), 20); + assert_eq!(Staking::balance(&5), 50); + }); + } + + #[test] + #[should_panic] + fn proposal_with_deposit_below_minimum_should_panic() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(1); + propose_sessions_per_era(1, 2, 0); + }); + } + + #[test] + #[should_panic] + fn poor_proposer_should_panic() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(1); + propose_sessions_per_era(1, 2, 11); + }); + } + + #[test] + #[should_panic] + fn poor_seconder_should_panic() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(1); + propose_sessions_per_era(2, 2, 11); + Democracy::second(&1, 0); + }); + } + + fn propose_bonding_duration(who: u64, value: u64, locked: u64) { + Democracy::propose(&who, Box::new(Proposal::Staking(staking::PrivCall::set_bonding_duration(value))), locked); + } + + #[test] + fn runners_up_should_come_after() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(0); + propose_bonding_duration(1, 2, 2); + propose_bonding_duration(1, 4, 4); + propose_bonding_duration(1, 3, 3); + Democracy::end_block(System::block_number()); + + System::set_block_number(1); + Democracy::vote(&1, 0, true); + Democracy::end_block(System::block_number()); + Staking::check_new_era(); + assert_eq!(Staking::bonding_duration(), 4); + + System::set_block_number(2); + Democracy::vote(&1, 1, true); + Democracy::end_block(System::block_number()); + Staking::check_new_era(); + assert_eq!(Staking::bonding_duration(), 3); + + System::set_block_number(3); + Democracy::vote(&1, 2, true); + Democracy::end_block(System::block_number()); + Staking::check_new_era(); + assert_eq!(Staking::bonding_duration(), 2); + }); + } + + fn sessions_per_era_proposal(value: u64) -> Proposal { + Proposal::Staking(staking::PrivCall::set_sessions_per_era(value)) + } + + #[test] + fn simple_passing_should_work() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(1); + let r = Democracy::inject_referendum(1, sessions_per_era_proposal(2), VoteThreshold::SuperMajorityApprove); + Democracy::vote(&1, r, true); + + assert_eq!(Democracy::voters_for(r), vec![1]); + assert_eq!(Democracy::vote_of((r, 1)), Some(true)); + assert_eq!(Democracy::tally(r), (10, 0)); + + Democracy::end_block(System::block_number()); + Staking::check_new_era(); + + assert_eq!(Staking::era_length(), 2); + }); + } + + #[test] + fn cancel_referendum_should_work() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(1); + let r = Democracy::inject_referendum(1, sessions_per_era_proposal(2), VoteThreshold::SuperMajorityApprove); + Democracy::vote(&1, r, true); + Democracy::cancel_referendum(r); + + Democracy::end_block(System::block_number()); + Staking::check_new_era(); + + assert_eq!(Staking::era_length(), 1); + }); + } + + #[test] + fn simple_failing_should_work() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(1); + let r = Democracy::inject_referendum(1, sessions_per_era_proposal(2), VoteThreshold::SuperMajorityApprove); + Democracy::vote(&1, r, false); + + assert_eq!(Democracy::voters_for(r), vec![1]); + assert_eq!(Democracy::vote_of((r, 1)), Some(false)); + assert_eq!(Democracy::tally(r), (0, 10)); + + Democracy::end_block(System::block_number()); + Staking::check_new_era(); + + assert_eq!(Staking::era_length(), 1); + }); + } + + #[test] + fn controversial_voting_should_work() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(1); + let r = Democracy::inject_referendum(1, sessions_per_era_proposal(2), VoteThreshold::SuperMajorityApprove); + Democracy::vote(&1, r, true); + Democracy::vote(&2, r, false); + Democracy::vote(&3, r, false); + Democracy::vote(&4, r, true); + Democracy::vote(&5, r, false); + Democracy::vote(&6, r, true); + + assert_eq!(Democracy::tally(r), (110, 100)); + + Democracy::end_block(System::block_number()); + Staking::check_new_era(); + + assert_eq!(Staking::era_length(), 2); + }); + } + + #[test] + fn controversial_low_turnout_voting_should_work() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(1); + let r = Democracy::inject_referendum(1, sessions_per_era_proposal(2), VoteThreshold::SuperMajorityApprove); + Democracy::vote(&5, r, false); + Democracy::vote(&6, r, true); + + assert_eq!(Democracy::tally(r), (60, 50)); + + Democracy::end_block(System::block_number()); + Staking::check_new_era(); + + assert_eq!(Staking::era_length(), 1); + }); + } + + #[test] + fn passing_low_turnout_voting_should_work() { + with_externalities(&mut new_test_ext(), || { + assert_eq!(Staking::era_length(), 1); + assert_eq!(Staking::total_stake(), 210); + + System::set_block_number(1); + let r = Democracy::inject_referendum(1, sessions_per_era_proposal(2), VoteThreshold::SuperMajorityApprove); + Democracy::vote(&4, r, true); + Democracy::vote(&5, r, false); + Democracy::vote(&6, r, true); + + assert_eq!(Democracy::tally(r), (100, 50)); + + Democracy::end_block(System::block_number()); + Staking::check_new_era(); + + assert_eq!(Staking::era_length(), 2); + }); + } +} diff --git a/substrate/runtime/democracy/src/vote_threshold.rs b/substrate/runtime/democracy/src/vote_threshold.rs new file mode 100644 index 0000000000000..c40f611e34373 --- /dev/null +++ b/substrate/runtime/democracy/src/vote_threshold.rs @@ -0,0 +1,75 @@ +// 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 . + +//! Voting thresholds. + +use primitives::traits::IntegerSquareRoot; +use codec::{Input, Slicable}; +use rstd::ops::{Add, Mul, Div}; + +/// A means of determining if a vote is past pass threshold. +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Serialize, Debug))] +pub enum VoteThreshold { + /// A supermajority of approvals is needed to pass this vote. + SuperMajorityApprove, + /// A supermajority of rejects is needed to fail this vote. + SuperMajorityAgainst, + /// A simple majority of approvals is needed to pass this vote. + SimpleMajority, +} + +impl Slicable for VoteThreshold { + fn decode(input: &mut I) -> Option { + input.read_byte().and_then(|v| match v { + 0 => Some(VoteThreshold::SuperMajorityApprove), + 1 => Some(VoteThreshold::SuperMajorityAgainst), + 2 => Some(VoteThreshold::SimpleMajority), + _ => None, + }) + } + + fn using_encoded R>(&self, f: F) -> R { + f(&[match *self { + VoteThreshold::SuperMajorityApprove => 0u8, + VoteThreshold::SuperMajorityAgainst => 1u8, + VoteThreshold::SimpleMajority => 2u8, + }]) + } +} + +pub trait Approved { + /// Given `approve` votes for and `against` votes against from a total electorate size of + /// `electorate` (`electorate - (approve + against)` are abstainers), then returns true if the + /// overall outcome is in favour of approval. + fn approved(&self, approve: Balance, against: Balance, electorate: Balance) -> bool; +} + +impl + Mul + Div + Copy> Approved for VoteThreshold { + /// Given `approve` votes for and `against` votes against from a total electorate size of + /// `electorate` (`electorate - (approve + against)` are abstainers), then returns true if the + /// overall outcome is in favour of approval. + fn approved(&self, approve: Balance, against: Balance, electorate: Balance) -> bool { + let voters = approve + against; + match *self { + VoteThreshold::SuperMajorityApprove => + voters.integer_sqrt() * approve / electorate.integer_sqrt() > against, + VoteThreshold::SuperMajorityAgainst => + approve > voters.integer_sqrt() * against / electorate.integer_sqrt(), + VoteThreshold::SimpleMajority => approve > against, + } + } +} diff --git a/substrate/runtime/executive/Cargo.toml b/substrate/runtime/executive/Cargo.toml new file mode 100644 index 0000000000000..5b1280c2490fa --- /dev/null +++ b/substrate/runtime/executive/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "substrate-runtime-executive" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +hex-literal = "0.1.0" +serde = { version = "1.0", default_features = false } +substrate-codec = { path = "../../codec", default_features = false } +substrate-runtime-std = { path = "../../runtime-std", default_features = false } +substrate-runtime-io = { path = "../../runtime-io", default_features = false } +substrate-runtime-support = { path = "../../runtime-support", default_features = false } +substrate-runtime-primitives = { path = "../primitives", default_features = false } +substrate-runtime-system = { path = "../system", default_features = false } + +[dev-dependencies] +substrate-primitives = { path = "../../primitives" } +substrate-runtime-session = { path = "../session" } +substrate-runtime-staking = { path = "../staking" } +substrate-runtime-consensus = { path = "../consensus" } + +[features] +default = ["std"] +std = [ + "substrate-runtime-std/std", + "substrate-runtime-support/std", + "serde/std", + "substrate-codec/std", + "substrate-runtime-primitives/std", + "substrate-runtime-io/std", + "substrate-runtime-system/std", +] diff --git a/substrate/runtime/executive/src/lib.rs b/substrate/runtime/executive/src/lib.rs new file mode 100644 index 0000000000000..a99bd28c987f1 --- /dev/null +++ b/substrate/runtime/executive/src/lib.rs @@ -0,0 +1,284 @@ +// 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 . + +//! Executive: Handles all of the top-level stuff; essentially just executing blocks/extrinsics. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate substrate_runtime_std as rstd; +extern crate substrate_runtime_support as runtime_support; +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)] +#[macro_use] +extern crate hex_literal; + +#[cfg(test)] +extern crate substrate_primitives; + +#[cfg(test)] +extern crate substrate_runtime_consensus as consensus; + +#[cfg(test)] +extern crate substrate_runtime_session as session; + +#[cfg(test)] +extern crate substrate_runtime_staking as staking; + +#[cfg(feature = "std")] extern crate serde; + +use rstd::prelude::*; +use rstd::marker::PhantomData; +use runtime_io::Hashing; +use primitives::traits::{self, Header, Zero, One, Checkable, Applyable, CheckEqual, Executable, MakePayment}; +use codec::Slicable; + +pub struct Executive< + System, + Block, + Payment, + Finalisation, +>(PhantomData<(System, Block, Payment, Finalisation)>); + +impl< + System: system::Trait, + Block: traits::Block

, + Payment: MakePayment, + Finalisation: Executable, +> Executive where + Block::Extrinsic: Checkable + Slicable, + ::Checked: Applyable +{ + /// Start the execution of a particular block. + pub fn initialise_block(header: &System::Header) { + >::initialise(header.number(), header.parent_hash(), header.extrinsics_root()); + } + + fn initial_checks(block: &Block) { + let header = block.header(); + + // check parent_hash is correct. + let n = header.number().clone(); + assert!( + n > System::BlockNumber::zero() && >::block_hash(n - System::BlockNumber::one()) == *header.parent_hash(), + "Parent hash should be valid." + ); + + // check transaction trie root represents the transactions. + let txs = block.extrinsics().iter().map(Slicable::encode).collect::>(); + let txs = txs.iter().map(Vec::as_slice).collect::>(); + let txs_root = System::Hashing::enumerated_trie_root(&txs); + header.extrinsics_root().check_equal(&txs_root); + assert!(header.extrinsics_root() == &txs_root, "Transaction trie root must be valid."); + } + + /// Actually execute all transitioning for `block`. + pub fn execute_block(block: Block) { + Self::initialise_block(block.header()); + + // any initial checks + Self::initial_checks(&block); + + // execute transactions + let (header, extrinsics) = block.deconstruct(); + extrinsics.into_iter().for_each(Self::apply_extrinsic); + + // post-transactional book-keeping. + Finalisation::execute(); + + // any final checks + Self::final_checks(&header); + + // any stuff that we do after taking the storage root. + Self::post_finalise(&header); + } + + /// Finalise the block - it is up the caller to ensure that all header fields are valid + /// except state-root. + pub fn finalise_block() -> System::Header { + Finalisation::execute(); + + let header = >::finalise(); + Self::post_finalise(&header); + + header + } + + /// Apply outside of the block execution function. + /// This doesn't attempt to validate anything regarding the block. + pub fn apply_extrinsic(utx: Block::Extrinsic) { + // Verify the signature is good. + let tx = match utx.check() { + Ok(tx) => tx, + Err(_) => panic!("All transactions should be properly signed"), + }; + + { + // check index + let expected_index = >::account_index(tx.sender()); + assert!(tx.index() == &expected_index, "All transactions should have the correct nonce"); + + // increment nonce in storage + >::inc_account_index(tx.sender()); + } + + // pay any fees. + Payment::make_payment(tx.sender()); + + // decode parameters and dispatch + tx.apply(); + } + + fn final_checks(header: &System::Header) { + // check digest + assert!(header.digest() == &>::digest()); + + // remove temporaries. + >::finalise(); + + // check storage root. + let storage_root = System::Hashing::storage_root(); + header.state_root().check_equal(&storage_root); + assert!(header.state_root() == &storage_root, "Storage root must match that calculated."); + } + + fn post_finalise(header: &System::Header) { + // store the header hash in storage; we can't do it before otherwise there would be a + // cyclic dependency. + >::record_block_hash(header) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use staking::Call; + use runtime_io::with_externalities; + use substrate_primitives::H256; + use primitives::BuildExternalities; + use primitives::traits::{HasPublicAux, Identity, Header as HeaderT}; + use primitives::testing::{Digest, Header, Block}; + + pub struct Test; + impl HasPublicAux for Test { + type PublicAux = u64; + } + impl consensus::Trait for Test { + type SessionKey = u64; + } + impl system::Trait for Test { + type Index = u64; + type BlockNumber = u64; + type Hash = substrate_primitives::H256; + type Hashing = runtime_io::BlakeTwo256; + type Digest = Digest; + type AccountId = u64; + type Header = Header; + } + impl session::Trait for Test { + type PublicAux = ::PublicAux; + type ConvertAccountIdToSessionKey = Identity; + } + impl staking::Trait for Test { + type Balance = u64; + type DetermineContractAddress = staking::DummyContractAddressFor; + } + + type TestXt = primitives::testing::TestXt>; + type Executive = super::Executive, staking::Module, (session::Module, staking::Module)>; + + #[test] + fn staking_balance_transfer_dispatch_works() { + let mut t = system::GenesisConfig::::default().build_externalities(); + t.extend(staking::GenesisConfig:: { + sessions_per_era: 0, + current_era: 0, + balances: vec![(1, 111)], + intentions: vec![], + validator_count: 0, + bonding_duration: 0, + transaction_fee: 10, + }.build_externalities()); + let xt = primitives::testing::TestXt((1, 0, Call::transfer(2, 69))); + with_externalities(&mut t, || { + Executive::initialise_block(&Header::new(1, H256::default(), H256::default(), [69u8; 32].into(), Digest::default())); + Executive::apply_extrinsic(xt); + assert_eq!(>::balance(&1), 32); + assert_eq!(>::balance(&2), 69); + }); + } + + fn new_test_ext() -> runtime_io::TestExternalities { + let mut t = system::GenesisConfig::::default().build_externalities(); + t.extend(consensus::GenesisConfig::::default().build_externalities()); + t.extend(session::GenesisConfig::::default().build_externalities()); + t.extend(staking::GenesisConfig::::default().build_externalities()); + t + } + + #[test] + fn block_import_works() { + with_externalities(&mut new_test_ext(), || { + Executive::execute_block(Block { + header: Header { + parent_hash: [69u8; 32].into(), + number: 1, + state_root: hex!("9228e363883f4f5a01981985b5598d1a767e987eb3ccea017a0e14cac7acc79d").into(), + extrinsics_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), + digest: Digest { logs: vec![], }, + }, + extrinsics: vec![], + }); + }); + } + + #[test] + #[should_panic] + fn block_import_of_bad_state_root_fails() { + with_externalities(&mut new_test_ext(), || { + Executive::execute_block(Block { + header: Header { + parent_hash: [69u8; 32].into(), + number: 1, + state_root: [0u8; 32].into(), + extrinsics_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), + digest: Digest { logs: vec![], }, + }, + extrinsics: vec![], + }); + }); + } + + #[test] + #[should_panic] + fn block_import_of_bad_extrinsic_root_fails() { + with_externalities(&mut new_test_ext(), || { + Executive::execute_block(Block { + header: Header { + parent_hash: [69u8; 32].into(), + number: 1, + state_root: hex!("93dde1251278e65430baf291337ba219bacfa9ad583c52513b12cf1974109a97").into(), + extrinsics_root: [0u8; 32].into(), + digest: Digest { logs: vec![], }, + }, + extrinsics: vec![], + }); + }); + } +} diff --git a/substrate/runtime/primitives/Cargo.toml b/substrate/runtime/primitives/Cargo.toml new file mode 100644 index 0000000000000..cc3f035715430 --- /dev/null +++ b/substrate/runtime/primitives/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "substrate-runtime-primitives" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +num-traits = { version = "0.2", default_features = false } +integer-sqrt = "0.1.0" +serde = { version = "1.0", optional = true } +serde_derive = { version = "1.0", optional = true } +substrate-codec = { path = "../../codec", default_features = false } +substrate-primitives = { path = "../../primitives", default_features = false } +substrate-runtime-std = { path = "../../runtime-std", default_features = false } +substrate-runtime-io = { path = "../../runtime-io", default_features = false } +substrate-runtime-support = { path = "../../runtime-support", default_features = false } + +[features] +default = ["std"] +std = [ + "num-traits/std", + "serde", + "serde_derive", + "substrate-runtime-std/std", + "substrate-runtime-io/std", + "substrate-runtime-support/std", + "substrate-codec/std", + "substrate-primitives/std", +] diff --git a/substrate/runtime/primitives/src/generic.rs b/substrate/runtime/primitives/src/generic.rs new file mode 100644 index 0000000000000..81c7ca44c69a4 --- /dev/null +++ b/substrate/runtime/primitives/src/generic.rs @@ -0,0 +1,398 @@ +// 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 . + +//! Generic implementations of Extrinsic/Header/Block. + +#[cfg(feature = "std")] use serde::Serialize; +use rstd::prelude::*; +use codec::{Slicable, Input}; +use runtime_support::AuxDispatchable; +use traits; +use rstd::ops; + +#[cfg(feature = "std")] +use std::fmt::{self, Debug}; + +#[cfg(feature = "std")] +pub trait MaybeSerializeDebug: Serialize + Debug {} +#[cfg(feature = "std")] +impl MaybeSerializeDebug for T {} + +#[cfg(not(feature = "std"))] +pub trait MaybeSerializeDebug {} +#[cfg(not(feature = "std"))] +impl MaybeSerializeDebug for T {} + +pub trait Member: MaybeSerializeDebug + Eq + PartialEq + Clone {} +impl Member for T {} + +/// A vetted and verified extrinsic from the external world. +#[derive(PartialEq, Eq, Clone)] +#[cfg_attr(feature = "std", derive(Serialize, Debug))] +pub struct Extrinsic where + AccountId: Member, + Index: Member, + Call: Member, +{ + /// Who signed it (note this is not a signature). + pub signed: AccountId, + /// The number of extrinsics have come before from the same signer. + pub index: Index, + /// The function that should be called. + pub function: Call, +} + +impl Slicable for Extrinsic where + AccountId: Member + Slicable, + Index: Member + Slicable, + Call: Member + Slicable +{ + fn decode(input: &mut I) -> Option { + Some(Extrinsic { + signed: Slicable::decode(input)?, + index: Slicable::decode(input)?, + function: Slicable::decode(input)?, + }) + } + + fn encode(&self) -> Vec { + let mut v = Vec::new(); + + self.signed.using_encoded(|s| v.extend(s)); + self.index.using_encoded(|s| v.extend(s)); + self.function.using_encoded(|s| v.extend(s)); + + v + } +} + +/// A extrinsics right from the external world. Unchecked. +#[derive(PartialEq, Eq, Clone)] +#[cfg_attr(feature = "std", derive(Serialize))] +pub struct UncheckedExtrinsic where + AccountId: Member, + Index: Member, + Call: Member, + Signature: Member +{ + /// The actual extrinsic information. + pub extrinsic: Extrinsic, + /// The signature; should be an Ed25519 signature applied to the serialised `extrinsic` field. + pub signature: Signature, +} + +impl Slicable for UncheckedExtrinsic where + AccountId: Member + Slicable, + Index: Member + Slicable, + Call: Member + Slicable, + Signature: Member + Slicable +{ + fn decode(input: &mut I) -> Option { + // This is a little more complicated than usual since the binary format must be compatible + // with substrate's generic `Vec` type. Basically this just means accepting that there + // will be a prefix of u32, which has the total number of bytes following (we don't need + // to use this). + let _length_do_not_remove_me_see_above: u32 = Slicable::decode(input)?; + + Some(UncheckedExtrinsic { + extrinsic: Slicable::decode(input)?, + signature: Slicable::decode(input)?, + }) + } + + fn encode(&self) -> Vec { + let mut v = Vec::new(); + + // need to prefix with the total length as u32 to ensure it's binary comptible with + // Vec. we'll make room for it here, then overwrite once we know the length. + v.extend(&[0u8; 4]); + + self.extrinsic.signed.using_encoded(|s| v.extend(s)); + self.extrinsic.index.using_encoded(|s| v.extend(s)); + self.extrinsic.function.using_encoded(|s| v.extend(s)); + self.signature.using_encoded(|s| v.extend(s)); + + let length = (v.len() - 4) as u32; + length.using_encoded(|s| v[0..4].copy_from_slice(s)); + + v + } +} + +#[cfg(feature = "std")] +impl fmt::Debug for UncheckedExtrinsic where + AccountId: Member, + Index: Member, + Call: Member, + Signature: Member, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "UncheckedExtrinsic({:?})", self.extrinsic) + } +} + +impl traits::Checkable for UncheckedExtrinsic where + AccountId: Member, + Index: Member, + Call: Member, + Signature: Member + traits::Verify, + Extrinsic: Slicable +{ + type Checked = CheckedExtrinsic; + + fn check(self) -> Result { + if ::codec::Slicable::using_encoded(&self.extrinsic, |msg| + self.signature.verify(msg, &self.extrinsic.signed) + ) { + Ok(CheckedExtrinsic(self)) + } else { + Err(self) + } + } +} + +/// A type-safe indicator that a extrinsic has been checked. +#[derive(PartialEq, Eq, Clone)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct CheckedExtrinsic + (UncheckedExtrinsic) +where + AccountId: Member, + Index: Member, + Call: Member, + Signature: Member; + +impl CheckedExtrinsic +where + AccountId: Member, + Index: Member, + Call: Member, + Signature: Member +{ + /// Get a reference to the checked signature. + pub fn signature(&self) -> &Signature { + &self.0.signature + } +} + +impl ops::Deref + for CheckedExtrinsic +where + AccountId: Member, + Index: Member, + Call: Member, + Signature: Member +{ + type Target = Extrinsic; + + fn deref(&self) -> &Self::Target { + &self.0.extrinsic + } +} + +impl traits::Applyable + for CheckedExtrinsic +where + AccountId: Member, + Index: Member, + Call: Member + AuxDispatchable, + Signature: Member +{ + type Index = Index; + type AccountId = AccountId; + + fn index(&self) -> &Self::Index { + &self.0.extrinsic.index + } + + fn sender(&self) -> &Self::AccountId { + &self.0.extrinsic.signed + } + + fn apply(self) { + let xt = self.0.extrinsic; + xt.function.dispatch(&xt.signed); + } +} + +#[derive(Default, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "std", derive(Debug, Serialize))] +pub struct Digest { + pub logs: Vec, +} +impl Slicable for Digest where + Item: Member + Slicable +{ + fn decode(input: &mut I) -> Option { + Some(Digest { logs: Slicable::decode(input)? }) + } + fn using_encoded R>(&self, f: F) -> R { + self.logs.using_encoded(f) + } +} +impl traits::Digest for Digest where + Item: Member + Slicable +{ + type Item = Item; + fn push(&mut self, item: Self::Item) { + self.logs.push(item); + } +} + +/// Abstraction over a block header for a substrate chain. +#[derive(PartialEq, Eq, Clone)] +#[cfg_attr(feature = "std", derive(Debug, Serialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "std", serde(deny_unknown_fields))] +pub struct Header where + Number: Member, + Hash: Member, + DigestItem: Member, +{ + /// The parent hash. + pub parent_hash: Hash, + /// The block number. + pub number: Number, + /// The state trie merkle root + pub state_root: Hash, + /// The merkle root of the extrinsics. + pub extrinsics_root: Hash, + /// A chain-specific digest of data useful for light clients or referencing auxiliary data. + pub digest: Digest, +} + +impl Slicable for Header where + Number: Member + Slicable, + Hash: Member + Slicable, + DigestItem: Member + Slicable, +{ + fn decode(input: &mut I) -> Option { + Some(Header { + parent_hash: Slicable::decode(input)?, + number: Slicable::decode(input)?, + state_root: Slicable::decode(input)?, + extrinsics_root: Slicable::decode(input)?, + digest: Slicable::decode(input)?, + }) + } + + fn encode(&self) -> Vec { + let mut v = Vec::new(); + self.parent_hash.using_encoded(|s| v.extend(s)); + self.number.using_encoded(|s| v.extend(s)); + self.state_root.using_encoded(|s| v.extend(s)); + self.extrinsics_root.using_encoded(|s| v.extend(s)); + self.digest.using_encoded(|s| v.extend(s)); + v + } +} +impl traits::Header for Header where + Number: Member + Slicable, + Hash: Member + Slicable, + DigestItem: Member + Slicable, + { + type Number = Number; + type Hash = Hash; + type Digest = Digest; + + fn number(&self) -> &Self::Number { &self.number } + fn extrinsics_root(&self) -> &Self::Hash { &self.extrinsics_root } + fn state_root(&self) -> &Self::Hash { &self.state_root } + fn parent_hash(&self) -> &Self::Hash { &self.parent_hash } + fn digest(&self) -> &Self::Digest { &self.digest } + fn new( + number: Self::Number, + extrinsics_root: Self::Hash, + state_root: Self::Hash, + parent_hash: Self::Hash, + digest: Self::Digest + ) -> Self { + Header { + number, extrinsics_root: extrinsics_root, state_root, parent_hash, digest + } + } +} + +/// Abstraction over a substrate block. +#[derive(PartialEq, Eq, Clone)] +#[cfg_attr(feature = "std", derive(Debug, Serialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "std", serde(deny_unknown_fields))] +pub struct Block where + Number: Member, + Hash: Member, + DigestItem: Member, + AccountId: Member, + Index: Member, + Call: Member, + Signature: Member +{ + /// The block header. + pub header: Header, + /// The accompanying extrinsics. + pub extrinsics: Vec>, +} + +impl Slicable + for Block +where + Number: Member, + Hash: Member, + DigestItem: Member, + AccountId: Member, + Index: Member, + Call: Member, + Signature: Member, + Header: Slicable, + UncheckedExtrinsic: Slicable, +{ + fn decode(input: &mut I) -> Option { + Some(Block { + header: Slicable::decode(input)?, + extrinsics: Slicable::decode(input)?, + }) + } + fn encode(&self) -> Vec { + let mut v: Vec = Vec::new(); + v.extend(self.header.encode()); + v.extend(self.extrinsics.encode()); + v + } +} + +impl traits::Block + for Block +where + Number: Member + Slicable, + Hash: Member + Slicable, + DigestItem: Member + Slicable, + AccountId: Member, + Index: Member, + Call: Member, + Signature: Member +{ + type Extrinsic = UncheckedExtrinsic; + type Header = Header; + fn header(&self) -> &Self::Header { + &self.header + } + fn extrinsics(&self) -> &[Self::Extrinsic] { + &self.extrinsics[..] + } + fn deconstruct(self) -> (Self::Header, Vec) { + (self.header, self.extrinsics) + } +} diff --git a/substrate/runtime/primitives/src/lib.rs b/substrate/runtime/primitives/src/lib.rs new file mode 100644 index 0000000000000..69a3039de6aee --- /dev/null +++ b/substrate/runtime/primitives/src/lib.rs @@ -0,0 +1,87 @@ +// 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 . + +//! System manager: Handles all of the top-level stuff; executing block/transaction, setting code +//! and depositing logs. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "std")] +extern crate serde; + +#[cfg(feature = "std")] +#[macro_use] +extern crate serde_derive; + +extern crate num_traits; +extern crate integer_sqrt; +extern crate substrate_runtime_std as rstd; +extern crate substrate_runtime_io as runtime_io; +extern crate substrate_runtime_support as runtime_support; +extern crate substrate_codec as codec; +extern crate substrate_primitives; + +#[cfg(feature = "std")] use std::collections::HashMap; + +#[cfg(feature = "std")] +pub mod testing; + +pub mod traits; +pub mod generic; + +#[cfg(feature = "std")] +pub type BuiltExternalities = HashMap, Vec>; + +#[cfg(feature = "std")] +pub trait BuildExternalities { + fn build_externalities(self) -> BuiltExternalities; +} + +#[macro_export] +macro_rules! __impl_outer_config_types { + ($concrete:ident $config:ident $snake:ident $($rest:ident)*) => { + #[cfg(any(feature = "std", test))] + pub type $config = $snake::GenesisConfig<$concrete>; + __impl_outer_config_types! {$concrete $($rest)*} + }; + ($concrete:ident) => () +} + +#[macro_export] +/// Implement the output "meta" module configuration struct. +macro_rules! impl_outer_config { + ( pub struct $main:ident for $concrete:ident { $( $config:ident => $snake:ident, )* } ) => { + __impl_outer_config_types! { $concrete $( $config $snake )* } + #[cfg(any(feature = "std", test))] + pub struct $main { + $( + pub $snake: Option<$config>, + )* + } + #[cfg(any(feature = "std", test))] + impl $crate::BuildExternalities for $main { + fn build_externalities(self) -> $crate::BuiltExternalities { + let mut s = $crate::BuiltExternalities::new(); + $( + if let Some(extra) = self.$snake { + s.extend(extra.build_externalities()); + } + )* + s + } + } + } +} diff --git a/substrate/runtime/primitives/src/testing.rs b/substrate/runtime/primitives/src/testing.rs new file mode 100644 index 0000000000000..7300d8d1765b5 --- /dev/null +++ b/substrate/runtime/primitives/src/testing.rs @@ -0,0 +1,150 @@ +// 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 . + +//! Testing utilities. + +use serde; +use codec::{Slicable, Input}; +use runtime_support::AuxDispatchable; +use substrate_primitives::H256; +use traits::{self, Checkable, Applyable}; + +#[derive(Default, PartialEq, Eq, Clone, Serialize, Debug)] +pub struct Digest { + pub logs: Vec, +} +impl Slicable for Digest { + fn decode(input: &mut I) -> Option { + Vec::::decode(input).map(|logs| Digest { logs }) + } + fn using_encoded R>(&self, f: F) -> R { + self.logs.using_encoded(f) + } +} +impl traits::Digest for Digest { + type Item = u64; + fn push(&mut self, item: Self::Item) { + self.logs.push(item); + } +} + +#[derive(PartialEq, Eq, Clone, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] +pub struct Header { + pub parent_hash: H256, + pub number: u64, + pub state_root: H256, + pub extrinsics_root: H256, + pub digest: Digest, +} +impl Slicable for Header { + fn decode(input: &mut I) -> Option { + Some(Header { + parent_hash: Slicable::decode(input)?, + number: Slicable::decode(input)?, + state_root: Slicable::decode(input)?, + extrinsics_root: Slicable::decode(input)?, + digest: Slicable::decode(input)?, + }) + } + + fn encode(&self) -> Vec { + let mut v = Vec::new(); + self.parent_hash.using_encoded(|s| v.extend(s)); + self.number.using_encoded(|s| v.extend(s)); + self.state_root.using_encoded(|s| v.extend(s)); + self.extrinsics_root.using_encoded(|s| v.extend(s)); + self.digest.using_encoded(|s| v.extend(s)); + v + } +} +impl traits::Header for Header { + type Number = u64; + type Hash = H256; + type Digest = Digest; + fn number(&self) -> &Self::Number { &self.number } + fn extrinsics_root(&self) -> &Self::Hash { &self.extrinsics_root } + fn state_root(&self) -> &Self::Hash { &self.state_root } + fn parent_hash(&self) -> &Self::Hash { &self.parent_hash } + fn digest(&self) -> &Self::Digest { &self.digest } + fn new( + number: Self::Number, + extrinsics_root: Self::Hash, + state_root: Self::Hash, + parent_hash: Self::Hash, + digest: Self::Digest + ) -> Self { + Header { + number, extrinsics_root: extrinsics_root, state_root, parent_hash, digest + } + } +} + +#[derive(PartialEq, Eq, Clone, Serialize, Debug)] +pub struct Block { + pub header: Header, + pub extrinsics: Vec, +} +impl Slicable for Block { + fn decode(input: &mut I) -> Option { + Some(Block { + header: Slicable::decode(input)?, + extrinsics: Slicable::decode(input)?, + }) + } + fn encode(&self) -> Vec { + let mut v: Vec = Vec::new(); + v.extend(self.header.encode()); + v.extend(self.extrinsics.encode()); + v + } +} +impl traits::Block for Block { + type Extrinsic = Xt; + type Header = Header; + fn header(&self) -> &Self::Header { + &self.header + } + fn extrinsics(&self) -> &[Self::Extrinsic] { + &self.extrinsics[..] + } + fn deconstruct(self) -> (Self::Header, Vec) { + (self.header, self.extrinsics) + } +} + +#[derive(PartialEq, Eq, Clone, Serialize, Debug)] +pub struct TestXt(pub (u64, u64, Call)); +impl Slicable for TestXt { + fn decode(input: &mut I) -> Option { + Some(TestXt(Slicable::decode(input)?)) + } + fn encode(&self) -> Vec { + self.0.encode() + } +} +impl Checkable for TestXt { + type Checked = Self; + fn check(self) -> Result { Ok(self) } +} +impl + Slicable + Sized + serde::Serialize> Applyable for TestXt { + type AccountId = u64; + type Index = u64; + fn sender(&self) -> &u64 { &(self.0).0 } + fn index(&self) -> &u64 { &(self.0).1 } + fn apply(self) { (self.0).2.dispatch(&(self.0).0); } +} diff --git a/substrate/runtime/primitives/src/traits.rs b/substrate/runtime/primitives/src/traits.rs new file mode 100644 index 0000000000000..1e2008c976819 --- /dev/null +++ b/substrate/runtime/primitives/src/traits.rs @@ -0,0 +1,293 @@ +// 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 . + +//! Primitives for the runtime modules. + +use rstd::prelude::*; +use rstd; +#[cfg(not(feature = "std"))] use runtime_io; +use substrate_primitives; +use codec::{Input, Slicable}; +use substrate_primitives::hash::H512; +pub use integer_sqrt::IntegerSquareRoot; +pub use num_traits::{Zero, One, Bounded}; +use rstd::ops::{Add, Sub, Mul, Div, Rem, AddAssign, SubAssign, MulAssign, DivAssign, RemAssign}; + +/// Means of signature verification. +pub trait Verify { + /// Type of the signer. + type Signer; + /// Verify a signature. + fn verify(&self, msg: &[u8], signer: &Self::Signer) -> bool; +} + +/// Ed25519 signature verify. +#[derive(Eq, PartialEq, Clone)] +#[cfg_attr(feature = "std", derive(Debug, Serialize))] +pub struct Ed25519Signature(H512); +impl Verify for Ed25519Signature { + type Signer = [u8; 32]; + fn verify(&self, msg: &[u8], signer: &Self::Signer) -> bool { + ::runtime_io::ed25519_verify(&(self.0).0, msg, &signer[..]) + } +} +impl Slicable for Ed25519Signature { + fn decode(input: &mut I) -> Option { Some(Ed25519Signature(Slicable::decode(input)?,)) } + fn using_encoded R>(&self, f: F) -> R { self.0.using_encoded(f) } +} +impl From for Ed25519Signature { + fn from(h: H512) -> Ed25519Signature { + Ed25519Signature(h) + } +} + +/// Simple payment making trait, operating on a single generic `AccountId` type. +pub trait MakePayment { + /// Make some sort of payment concerning `who`. + fn make_payment(who: &AccountId); +} + +impl MakePayment for () { + fn make_payment(_: &T) {} +} + +/// Extensible conversion trait. Generic over both source and destination types. +pub trait Convert { + /// Make conversion. + fn convert(a: A) -> B; +} + +/// Simple trait similar to `Into`, except that it can be used to convert numerics between each +/// other. +pub trait As { + /// Convert forward (ala `Into::into`). + fn as_(self) -> T; + /// Convert backward (ala `From::from`). + fn sa(T) -> Self; +} + +macro_rules! impl_numerics { + ( $( $t:ty ),* ) => { + $( + impl_numerics!($t: u8, u16, u32, u64, usize, i8, i16, i32, i64, isize,); + )* + }; + ( $f:ty : $t:ty, $( $rest:ty, )* ) => { + impl As<$t> for $f { + fn as_(self) -> $t { self as $t } + fn sa(t: $t) -> Self { t as Self } + } + impl_numerics!($f: $( $rest, )*); + }; + ( $f:ty : ) => {} +} + +impl_numerics!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize); + +pub struct Identity; +impl Convert for Identity { + fn convert(a: T) -> T { a } +} + +pub trait HasPublicAux { + type PublicAux; +} + +pub trait RefInto { + fn ref_into(&self) -> &T; +} +impl RefInto for T { + fn ref_into(&self) -> &T { &self } +} + +pub trait SimpleArithmetic: + Zero + One + IntegerSquareRoot + As + + Add + AddAssign + + Sub + SubAssign + + Mul + MulAssign + + Div + DivAssign + + Rem + RemAssign + + PartialOrd + Ord +{} +impl + + Add + AddAssign + + Sub + SubAssign + + Mul + MulAssign + + Div + DivAssign + + Rem + RemAssign + + PartialOrd + Ord +> SimpleArithmetic for T {} + +pub trait SimpleBitOps: + Sized + + rstd::ops::BitOr + + rstd::ops::BitAnd +{} +impl + + rstd::ops::BitAnd +> SimpleBitOps for T {} + +/// Something that can be executed. +pub trait Executable { + fn execute(); +} + +impl Executable for () { + fn execute() {} +} +impl Executable for (A, B) { + fn execute() { + A::execute(); + B::execute(); + } +} + +/// Something that acts like a `Digest` - it can have `Log`s `push`ed onto it and these `Log`s are +/// each `Slicable`. +pub trait Digest { + type Item: Sized; + fn push(&mut self, item: Self::Item); +} + +impl Digest for substrate_primitives::Digest { + type Item = substrate_primitives::block::Log; + fn push(&mut self, item: Self::Item) { + self.logs.push(item); + } +} + +/// Something which fulfills the abstract idea of a Substrate header. It has types for a `Number`, +/// a `Hash` and a `Digest`. It provides access to an `extrinsics_root`, `state_root` and +/// `parent_hash`, as well as a `digest` and a block `number`. +/// +/// You can also create a `new` one from those fields. +pub trait Header: Sized + Slicable { + type Number: Sized; + type Hash: Sized; + type Digest: Sized; + fn number(&self) -> &Self::Number; + fn extrinsics_root(&self) -> &Self::Hash; + fn state_root(&self) -> &Self::Hash; + fn parent_hash(&self) -> &Self::Hash; + fn digest(&self) -> &Self::Digest; + fn new( + number: Self::Number, + extrinsics_root: Self::Hash, + state_root: Self::Hash, + parent_hash: Self::Hash, + digest: Self::Digest + ) -> Self; +} + +impl Header for substrate_primitives::Header { + type Number = substrate_primitives::block::Number; + type Hash = substrate_primitives::block::HeaderHash; + type Digest = substrate_primitives::block::Digest; + fn number(&self) -> &Self::Number { &self.number } + fn extrinsics_root(&self) -> &Self::Hash { &self.transaction_root } + fn state_root(&self) -> &Self::Hash { &self.state_root } + fn parent_hash(&self) -> &Self::Hash { &self.parent_hash } + fn digest(&self) -> &Self::Digest { &self.digest } + fn new( + number: Self::Number, + extrinsics_root: Self::Hash, + state_root: Self::Hash, + parent_hash: Self::Hash, + digest: Self::Digest + ) -> Self { + substrate_primitives::Header { + number: number, + transaction_root: extrinsics_root, + state_root: state_root, + parent_hash: parent_hash, + digest: digest, + } + } +} + +/// Something which fulfills the abstract idea of a Substrate block. It has types for an +/// `Extrinsic` piece of information as well as a `Header`. +/// +/// You can get an iterator over each of the `extrinsics` and retrieve the `header`. +pub trait Block { + type Extrinsic: Sized; + type Header: Header; + fn header(&self) -> &Self::Header; + fn extrinsics(&self) -> &[Self::Extrinsic]; + fn deconstruct(self) -> (Self::Header, Vec); +} + +impl Block for substrate_primitives::Block { + type Extrinsic = substrate_primitives::block::Transaction; + type Header = substrate_primitives::Header; + fn header(&self) -> &Self::Header { + &self.header + } + fn extrinsics(&self) -> &[Self::Extrinsic] { + &self.transactions[..] + } + fn deconstruct(self) -> (Self::Header, Vec) { + (self.header, self.transactions) + } +} + +/// A "checkable" piece of information, used by the standard Substrate Executive in order to +/// check the validity of a piece of extrinsic information, usually by verifying the signature. +pub trait Checkable: Sized { + type Checked: Sized; + fn check(self) -> Result; +} + +/// An "executable" piece of information, used by the standard Substrate Executive in order to +/// enact a piece of extrinsic information by marshalling and dispatching to a named functioon +/// call. +/// +/// Also provides information on to whom this information is attributable and an index that allows +/// each piece of attributable information to be disambiguated. +pub trait Applyable { + type AccountId; + type Index; + fn index(&self) -> &Self::Index; + fn sender(&self) -> &Self::AccountId; + fn apply(self); +} + +/// Something that can be checked for equality and printed out to a debug channel if bad. +pub trait CheckEqual { + fn check_equal(&self, other: &Self); +} + +impl CheckEqual for substrate_primitives::H256 { + #[cfg(feature = "std")] + fn check_equal(&self, other: &Self) { + use substrate_primitives::hexdisplay::HexDisplay; + if &self.0 != &other.0 { + println!("Hash: given={}, expected={}", HexDisplay::from(&self.0), HexDisplay::from(&other.0)); + } + } + + #[cfg(not(feature = "std"))] + fn check_equal(&self, other: &Self) { + if self != other { + runtime_io::print("Hash not equal"); + runtime_io::print(&self.0[..]); + runtime_io::print(&other.0[..]); + } + } +} diff --git a/substrate/runtime/session/Cargo.toml b/substrate/runtime/session/Cargo.toml new file mode 100644 index 0000000000000..17bdeb7264096 --- /dev/null +++ b/substrate/runtime/session/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "substrate-runtime-session" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +hex-literal = "0.1.0" +serde = { version = "1.0", default_features = false } +safe-mix = { path = "../../../safe-mix", default_features = false} +substrate-keyring = { path = "../../keyring", optional = true } +substrate-codec = { path = "../../codec", default_features = false } +substrate-primitives = { path = "../../primitives", default_features = false } +substrate-runtime-std = { path = "../../runtime-std", default_features = false } +substrate-runtime-io = { path = "../../runtime-io", default_features = false } +substrate-runtime-support = { path = "../../runtime-support", default_features = false } +substrate-runtime-primitives = { path = "../primitives", default_features = false } +substrate-runtime-consensus = { path = "../consensus", default_features = false } +substrate-runtime-system = { path = "../system", default_features = false } + +[features] +default = ["std"] +std = [ + "serde/std", + "safe-mix/std", + "substrate-keyring", + "substrate-codec/std", + "substrate-primitives/std", + "substrate-runtime-std/std", + "substrate-runtime-io/std", + "substrate-runtime-support/std", + "substrate-runtime-primitives/std", + "substrate-runtime-consensus/std", + "substrate-runtime-system/std", +] diff --git a/substrate/runtime/session/src/lib.rs b/substrate/runtime/session/src/lib.rs new file mode 100644 index 0000000000000..cdaa9302eb1bc --- /dev/null +++ b/substrate/runtime/session/src/lib.rs @@ -0,0 +1,345 @@ +// 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 . + +//! Session manager: is told the validators and allows them to manage their session keys for the +//! consensus module. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "std")] +extern crate serde; + +#[cfg(any(feature = "std", test))] +extern crate substrate_keyring as keyring; + +#[cfg(any(feature = "std", test))] +extern crate substrate_primitives; + +#[cfg_attr(feature = "std", macro_use)] +extern crate substrate_runtime_std as rstd; + +#[macro_use] +extern crate substrate_runtime_support as runtime_support; + +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_consensus as consensus; +extern crate substrate_runtime_system as system; + +use rstd::prelude::*; +use primitives::traits::{Zero, One, RefInto, Executable, Convert}; +use runtime_support::{StorageValue, StorageMap}; + +pub trait Trait: consensus::Trait + system::Trait { + type PublicAux: RefInto; + type ConvertAccountIdToSessionKey: Convert; +} + +decl_module! { + pub struct Module; + pub enum Call where aux: T::PublicAux { + fn set_key(aux, key: T::SessionKey) = 0; + } + pub enum PrivCall { + fn set_length(new: T::BlockNumber) = 0; + fn force_new_session() = 1; + } +} +decl_storage! { + trait Store for Module; + + // The current set of validators. + pub Validators get(validators): b"ses:val" => required Vec; + // Current length of the session. + 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; + + // Block at which the session length last changed. + LastLengthChange: b"ses:llc" => T::BlockNumber; + // The next key for a given validator. + NextKeyFor: b"ses:nxt:" => map [ T::AccountId => T::SessionKey ]; + // The next session length. + NextSessionLength: b"ses:nln" => T::BlockNumber; +} + +impl Module { + /// The number of validators currently. + pub fn validator_count() -> u32 { + >::get().len() as u32 // TODO: can probably optimised + } + + /// The last length change, if there was one, zero if not. + pub fn last_length_change() -> T::BlockNumber { + >::get().unwrap_or_else(T::BlockNumber::zero) + } + + /// Sets the session key of `_validator` to `_key`. This doesn't take effect until the next + /// session. + fn set_key(aux: &T::PublicAux, key: T::SessionKey) { + // set new value for next session + >::insert(aux.ref_into(), key) + } + + /// Set a new era length. Won't kick in until the next era change (at current length). + fn set_length(new: T::BlockNumber) { + >::put(new); + } + + /// Forces a new session. + fn force_new_session() { + Self::rotate_session(); + } + + // INTERNAL API (available to other runtime modules) + + /// Set the current set of validators. + /// + /// Called by `staking::next_era()` only. `next_session` should be called after this in order to + /// update the session keys to the next validator set. + pub fn set_validators(new: &[T::AccountId]) { + >::put(&new.to_vec()); // TODO: optimise. + >::set_authorities( + &new.iter().cloned().map(T::ConvertAccountIdToSessionKey::convert).collect::>() + ); + } + + /// Hook to be called after transaction processing. + pub fn check_rotate_session() { + // do this last, after the staking system has had chance to switch out the authorities for the + // new set. + // check block number and call next_session if necessary. + let block_number = >::block_number(); + if ((block_number - Self::last_length_change()) % Self::length()).is_zero() { + Self::rotate_session(); + } + } + + /// Move onto next session: register the new authority set. + pub fn rotate_session() { + // Increment current session index. + >::put(>::get() + One::one()); + + // Enact era length change. + if let Some(next_len) = >::take() { + let block_number = >::block_number(); + >::put(next_len); + >::put(block_number); + } + + // Update any changes in session keys. + Self::validators().iter().enumerate().for_each(|(i, v)| { + if let Some(n) = >::take(v) { + >::set_authority(i as u32, &n); + } + }); + } +} + +impl Executable for Module { + fn execute() { + Self::check_rotate_session(); + } +} + +#[cfg(any(feature = "std", test))] +pub struct GenesisConfig { + pub session_length: T::BlockNumber, + pub validators: Vec, +} + +#[cfg(any(feature = "std", test))] +impl GenesisConfig where T::AccountId: From { + pub fn simple() -> Self where T::AccountId: From<[u8; 32]> { + use primitives::traits::As; + use keyring::Keyring::*; + let three = [3u8; 32]; + GenesisConfig { + session_length: T::BlockNumber::sa(2), + validators: vec![T::AccountId::from(One), T::AccountId::from(Two), T::AccountId::from(three)], + } + } + + pub fn extended() -> Self { + use primitives::traits::As; + use keyring::Keyring::*; + GenesisConfig { + session_length: T::BlockNumber::sa(1), + validators: vec![T::AccountId::from(Alice), T::AccountId::from(Bob), T::AccountId::from(Charlie)], + } + } +} + +#[cfg(any(feature = "std", test))] +impl Default for GenesisConfig { + fn default() -> Self { + use primitives::traits::As; + GenesisConfig { + session_length: T::BlockNumber::sa(1000), + validators: vec![], + } + } +} + +#[cfg(any(feature = "std", test))] +impl primitives::BuildExternalities for GenesisConfig +{ + fn build_externalities(self) -> runtime_io::TestExternalities { + use runtime_io::twox_128; + use codec::Slicable; + use primitives::traits::As; + map![ + twox_128(>::key()).to_vec() => self.session_length.encode(), + twox_128(>::key()).to_vec() => T::BlockNumber::sa(0).encode(), + twox_128(>::key()).to_vec() => self.validators.encode() + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use runtime_io::with_externalities; + use substrate_primitives::H256; + use primitives::BuildExternalities; + use primitives::traits::{HasPublicAux, Identity}; + use primitives::testing::{Digest, Header}; + + pub struct Test; + impl HasPublicAux for Test { + type PublicAux = u64; + } + impl consensus::Trait for Test { + type SessionKey = u64; + } + impl system::Trait for Test { + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = runtime_io::BlakeTwo256; + type Digest = Digest; + type AccountId = u64; + type Header = Header; + } + impl Trait for Test { + type PublicAux = ::PublicAux; + type ConvertAccountIdToSessionKey = Identity; + } + + type System = system::Module; + type Consensus = consensus::Module; + type Session = Module; + + fn new_test_ext() -> runtime_io::TestExternalities { + let mut t = system::GenesisConfig::::default().build_externalities(); + t.extend(consensus::GenesisConfig::{ + authorities: vec![1, 2, 3], + }.build_externalities()); + t.extend(GenesisConfig::{ + session_length: 2, + validators: vec![1, 2, 3], + }.build_externalities()); + t + } + + #[test] + fn simple_setup_should_work() { + with_externalities(&mut new_test_ext(), || { + assert_eq!(Consensus::authorities(), vec![1, 2, 3]); + assert_eq!(Session::length(), 2); + assert_eq!(Session::validators(), vec![1, 2, 3]); + }); + } + + #[test] + fn session_length_change_should_work() { + with_externalities(&mut new_test_ext(), || { + // Block 1: Change to length 3; no visible change. + System::set_block_number(1); + Session::set_length(3); + Session::check_rotate_session(); + assert_eq!(Session::length(), 2); + assert_eq!(Session::current_index(), 0); + + // Block 2: Length now changed to 3. Index incremented. + System::set_block_number(2); + Session::set_length(3); + Session::check_rotate_session(); + assert_eq!(Session::length(), 3); + assert_eq!(Session::current_index(), 1); + + // Block 3: Length now changed to 3. Index incremented. + System::set_block_number(3); + Session::check_rotate_session(); + assert_eq!(Session::length(), 3); + assert_eq!(Session::current_index(), 1); + + // Block 4: Change to length 2; no visible change. + System::set_block_number(4); + Session::set_length(2); + Session::check_rotate_session(); + assert_eq!(Session::length(), 3); + assert_eq!(Session::current_index(), 1); + + // Block 5: Length now changed to 2. Index incremented. + System::set_block_number(5); + Session::check_rotate_session(); + assert_eq!(Session::length(), 2); + assert_eq!(Session::current_index(), 2); + + // Block 6: No change. + System::set_block_number(6); + Session::check_rotate_session(); + assert_eq!(Session::length(), 2); + assert_eq!(Session::current_index(), 2); + + // Block 7: Next index. + System::set_block_number(7); + Session::check_rotate_session(); + assert_eq!(Session::length(), 2); + assert_eq!(Session::current_index(), 3); + }); + } + + #[test] + fn session_change_should_work() { + with_externalities(&mut new_test_ext(), || { + // Block 1: No change + System::set_block_number(1); + Session::check_rotate_session(); + assert_eq!(Consensus::authorities(), vec![1, 2, 3]); + + // Block 2: Session rollover, but no change. + System::set_block_number(2); + Session::check_rotate_session(); + assert_eq!(Consensus::authorities(), vec![1, 2, 3]); + + // Block 3: Set new key for validator 2; no visible change. + System::set_block_number(3); + Session::set_key(&2, 5); + assert_eq!(Consensus::authorities(), vec![1, 2, 3]); + + Session::check_rotate_session(); + assert_eq!(Consensus::authorities(), vec![1, 2, 3]); + + // Block 4: Session rollover, authority 2 changes. + System::set_block_number(4); + Session::check_rotate_session(); + assert_eq!(Consensus::authorities(), vec![1, 5, 3]); + }); + } +} diff --git a/substrate/runtime/staking/Cargo.toml b/substrate/runtime/staking/Cargo.toml new file mode 100644 index 0000000000000..75350d1319bed --- /dev/null +++ b/substrate/runtime/staking/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "substrate-runtime-staking" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +hex-literal = "0.1.0" +serde = { version = "1.0", default_features = false } +safe-mix = { path = "../../../safe-mix", default_features = false} +substrate-keyring = { path = "../../keyring", optional = true } +substrate-codec = { path = "../../codec", default_features = false } +substrate-primitives = { path = "../../primitives", default_features = false } +substrate-runtime-std = { path = "../../runtime-std", default_features = false } +substrate-runtime-io = { path = "../../runtime-io", default_features = false } +substrate-runtime-support = { path = "../../runtime-support", default_features = false } +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-session = { path = "../session", default_features = false } + +[features] +default = ["std"] +std = [ + "serde/std", + "safe-mix/std", + "substrate-keyring", + "substrate-codec/std", + "substrate-primitives/std", + "substrate-runtime-std/std", + "substrate-runtime-io/std", + "substrate-runtime-support/std", + "substrate-runtime-primitives/std", + "substrate-runtime-session/std", + "substrate-runtime-system/std", +] diff --git a/substrate/runtime/staking/src/lib.rs b/substrate/runtime/staking/src/lib.rs new file mode 100644 index 0000000000000..947b08b240066 --- /dev/null +++ b/substrate/runtime/staking/src/lib.rs @@ -0,0 +1,1017 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Substrate Demo. + +// Substrate Demo is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate Demo is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate Demo. If not, see . + +//! Staking manager: Handles balances and periodically determines the best set of validators. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "std")] +extern crate serde; + +#[macro_use] +extern crate substrate_runtime_support as runtime_support; + +#[cfg_attr(feature = "std", macro_use)] +extern crate substrate_runtime_std as rstd; + +extern crate substrate_codec as codec; +extern crate substrate_primitives; +extern crate substrate_runtime_io as runtime_io; +extern crate substrate_runtime_primitives as primitives; +extern crate substrate_runtime_consensus as consensus; +extern crate substrate_runtime_session as session; +extern crate substrate_runtime_system as system; + +#[cfg(test)] use std::fmt::Debug; +use rstd::prelude::*; +use rstd::cmp; +use rstd::cell::RefCell; +use rstd::marker::PhantomData; +use rstd::collections::btree_map::{BTreeMap, Entry}; +use codec::Slicable; +use runtime_support::{StorageValue, StorageMap, Parameter}; +use primitives::traits::{Zero, One, Bounded, RefInto, SimpleArithmetic, Executable, MakePayment}; + +#[cfg(test)] +#[derive(Debug, PartialEq, Clone)] +pub enum LockStatus { + Liquid, + LockedUntil(BlockNumber), + Staked, +} + +#[cfg(not(test))] +#[derive(PartialEq, Clone)] +pub enum LockStatus { + Liquid, + LockedUntil(BlockNumber), + Staked, +} + +pub trait ContractAddressFor { + fn contract_address_for(code: &[u8], origin: &AccountId) -> AccountId; +} + +impl ContractAddressFor for Hashing where + Hashing: runtime_io::Hashing, + AccountId: Sized + Slicable + From, + Hashing::Output: Slicable +{ + fn contract_address_for(code: &[u8], origin: &AccountId) -> AccountId { + let mut dest_pre = Hashing::hash(code).encode(); + origin.using_encoded(|s| dest_pre.extend(s)); + AccountId::from(Hashing::hash(&dest_pre)) + } +} + +pub trait Trait: system::Trait + session::Trait { + /// The balance of an account. + type Balance: Parameter + SimpleArithmetic + Default + Copy; + type DetermineContractAddress: ContractAddressFor; +} + +decl_module! { + pub struct Module; + pub enum Call where aux: T::PublicAux { + fn transfer(aux, dest: T::AccountId, value: T::Balance) = 0; + fn stake(aux) = 1; + fn unstake(aux) = 2; + } + pub enum PrivCall { + fn set_sessions_per_era(new: T::BlockNumber) = 0; + fn set_bonding_duration(new: T::BlockNumber) = 1; + fn set_validator_count(new: u32) = 2; + fn force_new_era() = 3; + } +} + +decl_storage! { + trait Store for Module; + + // The length of the bonding duration in eras. + pub BondingDuration get(bonding_duration): b"sta:loc" => required T::BlockNumber; + // The length of a staking era in sessions. + pub ValidatorCount get(validator_count): b"sta:vac" => required u32; + // The length of a staking era in sessions. + pub SessionsPerEra get(sessions_per_era): b"sta:spe" => required T::BlockNumber; + // The total amount of stake on the system. + pub TotalStake get(total_stake): b"sta:tot" => required T::Balance; + // The fee to be paid for making a transaction. + pub TransactionFee get(transaction_fee): b"sta:fee" => required T::Balance; + + // The current era index. + pub CurrentEra get(current_era): b"sta:era" => required T::BlockNumber; + // All the accounts with a desire to stake. + pub Intentions: b"sta:wil:" => default Vec; + // The next value of sessions per era. + pub NextSessionsPerEra get(next_sessions_per_era): b"sta:nse" => T::BlockNumber; + // The block number at which the era length last changed. + pub LastEraLengthChange get(last_era_length_change): b"sta:lec" => default T::BlockNumber; + + // The balance of a given account. + pub FreeBalance get(free_balance): b"sta:bal:" => default map [ T::AccountId => T::Balance ]; + + // The amount of the balance of a given account that is exterally reserved; this can still get + // slashed, but gets slashed last of all. + pub ReservedBalance get(reserved_balance): b"sta:lbo:" => default map [ T::AccountId => T::Balance ]; + + // The block at which the `who`'s funds become entirely liquid. + pub Bondage get(bondage): b"sta:bon:" => default map [ T::AccountId => T::BlockNumber ]; + + // The code associated with an account. + pub CodeOf: b"sta:cod:" => default map [ T::AccountId => Vec ]; // TODO Vec values should be optimised to not do a length prefix. + + // The storage items associated with an account/key. + pub StorageOf: b"sta:sto:" => map [ (T::AccountId, Vec) => Vec ]; // TODO: keys should also be able to take AsRef to ensure Vecs can be passed as &[u8] +} + +impl Module { + + // PUBLIC IMMUTABLES + + /// The length of a staking era in blocks. + pub fn era_length() -> T::BlockNumber { + Self::sessions_per_era() * >::length() + } + + /// The combined balance of `who`. + pub fn balance(who: &T::AccountId) -> T::Balance { + Self::free_balance(who) + Self::reserved_balance(who) + } + + /// Some result as `slash(who, value)` (but without the side-effects) asuming there are no + /// balance changes in the meantime. + pub fn can_slash(who: &T::AccountId, value: T::Balance) -> bool { + Self::balance(who) >= value + } + + /// The block at which the `who`'s funds become entirely liquid. + pub fn unlock_block(who: &T::AccountId) -> LockStatus { + match Self::bondage(who) { + i if i == T::BlockNumber::max_value() => LockStatus::Staked, + i if i <= >::block_number() => LockStatus::Liquid, + i => LockStatus::LockedUntil(i), + } + } + + /// Create a smart-contract account. + pub fn create(aux: &T::PublicAux, code: &[u8], value: T::Balance) { + // commit anything that made it this far to storage + if let Some(commit) = Self::effect_create(aux.ref_into(), code, value, DirectExt) { + Self::commit_state(commit); + } + } + + // PUBLIC DISPATCH + + /// Transfer some unlocked staking balance to another staker. + /// TODO: probably want to state gas-limit and gas-price. + fn transfer(aux: &T::PublicAux, dest: T::AccountId, value: T::Balance) { + // commit anything that made it this far to storage + if let Some(commit) = Self::effect_transfer(aux.ref_into(), &dest, value, DirectExt) { + Self::commit_state(commit); + } + } + + /// Declare the desire to stake for the transactor. + /// + /// Effects will be felt at the beginning of the next era. + fn stake(aux: &T::PublicAux) { + let mut intentions = >::get(); + // can't be in the list twice. + assert!(intentions.iter().find(|&t| t == aux.ref_into()).is_none(), "Cannot stake if already staked."); + intentions.push(aux.ref_into().clone()); + >::put(intentions); + >::insert(aux.ref_into(), T::BlockNumber::max_value()); + } + + /// Retract the desire to stake for the transactor. + /// + /// Effects will be felt at the beginning of the next era. + fn unstake(aux: &T::PublicAux) { + let mut intentions = >::get(); + if let Some(position) = intentions.iter().position(|t| t == aux.ref_into()) { + intentions.swap_remove(position); + } else { + panic!("Cannot unstake if not already staked."); + } + >::put(intentions); + >::insert(aux.ref_into(), Self::current_era() + Self::bonding_duration()); + } + + // PRIV DISPATCH + + /// Set the number of sessions in an era. + fn set_sessions_per_era(new: T::BlockNumber) { + >::put(&new); + } + + /// The length of the bonding duration in eras. + fn set_bonding_duration(new: T::BlockNumber) { + >::put(&new); + } + + /// The length of a staking era in sessions. + fn set_validator_count(new: u32) { + >::put(&new); + } + + /// Force there to be a new era. This also forces a new session immediately after. + fn force_new_era() { + Self::new_era(); + >::rotate_session(); + } + + // PUBLIC MUTABLES (DANGEROUS) + + /// Deduct from an unbonded balance. true if it happened. + pub fn deduct_unbonded(who: &T::AccountId, value: T::Balance) -> bool { + if let LockStatus::Liquid = Self::unlock_block(who) { + let b = Self::free_balance(who); + if b >= value { + >::insert(who, b - value); + return true; + } + } + false + } + + /// Refund some balance. + pub fn refund(who: &T::AccountId, value: T::Balance) { + >::insert(who, Self::free_balance(who) + value) + } + + /// Will slash any balance, but prefer free over reserved. + pub fn slash(who: &T::AccountId, value: T::Balance) -> bool { + let free_balance = Self::free_balance(who); + let free_slash = cmp::min(free_balance, value); + >::insert(who, &(free_balance - free_slash)); + if free_slash < value { + Self::slash_reserved(who, value - free_slash) + } else { + true + } + } + + /// Moves `value` from balance to reserved balance. + pub fn reserve_balance(who: &T::AccountId, value: T::Balance) { + let b = Self::free_balance(who); + assert!(b >= value); + >::insert(who, b - value); + >::insert(who, Self::reserved_balance(who) + value); + } + + /// Moves `value` from reserved balance to balance. + pub fn unreserve_balance(who: &T::AccountId, value: T::Balance) { + let b = Self::reserved_balance(who); + let value = cmp::min(b, value); + >::insert(who, b - value); + >::insert(who, Self::free_balance(who) + value); + } + + /// Moves `value` from reserved balance to balance. + pub fn slash_reserved(who: &T::AccountId, value: T::Balance) -> bool { + let b = Self::reserved_balance(who); + let slash = cmp::min(b, value); + >::insert(who, b - slash); + value == slash + } + + /// Moves `value` from reserved balance to balance. + pub fn transfer_reserved_balance(slashed: &T::AccountId, beneficiary: &T::AccountId, value: T::Balance) -> bool { + let b = Self::reserved_balance(slashed); + let slash = cmp::min(b, value); + >::insert(slashed, b - slash); + >::insert(beneficiary, Self::free_balance(beneficiary) + slash); + slash == value + } + + /// Hook to be called after to transaction processing. + pub fn check_new_era() { + // check block number and call new_era if necessary. + if (>::block_number() - Self::last_era_length_change()) % Self::era_length() == Zero::zero() { + Self::new_era(); + } + } + + /// The era has changed - enact new staking set. + /// + /// NOTE: This always happens immediately before a session change to ensure that new validators + /// get a chance to set their session keys. + fn new_era() { + // Increment current era. + >::put(&(>::get() + One::one())); + + // Enact era length change. + if let Some(next_spe) = Self::next_sessions_per_era() { + if next_spe != Self::sessions_per_era() { + >::put(&next_spe); + >::put(&>::block_number()); + } + } + + // evaluate desired staking amounts and nominations and optimise to find the best + // combination of validators, then use session::internal::set_validators(). + // for now, this just orders would-be stakers by their balances and chooses the top-most + // >::get() of them. + let mut intentions = >::get() + .into_iter() + .map(|v| (Self::balance(&v), v)) + .collect::>(); + intentions.sort_unstable_by(|&(ref b1, _), &(ref b2, _)| b2.cmp(&b1)); + >::set_validators( + &intentions.into_iter() + .map(|(_, v)| v) + .take(>::get() as usize) + .collect::>() + ); + } +} + +impl Executable for Module { + fn execute() { + Self::check_new_era(); + } +} + +// Each identity's stake may be in one of three bondage states, given by an integer: +// - n | n <= >::get(): inactive: free to be transferred. +// - ~0: active: currently representing a validator. +// - n | n > >::get(): deactivating: recently representing a validator and not yet +// ready for transfer. + +struct ChangeEntry { + balance: Option, + code: Option>, + storage: BTreeMap, Option>>, +} + +// Cannot derive(Default) since it erroneously bounds T by Default. +impl Default for ChangeEntry { + fn default() -> Self { + ChangeEntry { + balance: Default::default(), + code: Default::default(), + storage: Default::default(), + } + } +} + +impl ChangeEntry { + pub fn balance_changed(b: T::Balance) -> Self { + ChangeEntry { balance: Some(b), code: None, storage: Default::default() } + } +} + +type State = BTreeMap<::AccountId, ChangeEntry>; + +trait Externalities { + fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option>; + fn get_code(&self, account: &T::AccountId) -> Vec; + fn get_balance(&self, account: &T::AccountId) -> T::Balance; +} + +struct Ext where + F1 : Fn(&T::AccountId, &[u8]) -> Option>, + F3 : Fn(&T::AccountId) -> Vec, + F5 : Fn(&T::AccountId) -> T::Balance +{ + do_get_storage: F1, + do_get_code: F3, + do_get_balance: F5, + _unused: PhantomData, +} + +struct DirectExt; +impl Externalities for DirectExt { + fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option> { + >::get(&(account.clone(), location.to_vec())) + } + fn get_code(&self, account: &T::AccountId) -> Vec { + >::get(account) + } + fn get_balance(&self, account: &T::AccountId) -> T::Balance { + >::get(account) + } +} + +impl Externalities for Ext where + F1 : Fn(&T::AccountId, &[u8]) -> Option>, + F3 : Fn(&T::AccountId) -> Vec, + F5 : Fn(&T::AccountId) -> T::Balance +{ + fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option> { + (self.do_get_storage)(account, location) + } + fn get_code(&self, account: &T::AccountId) -> Vec { + (self.do_get_code)(account) + } + fn get_balance(&self, account: &T::AccountId) -> T::Balance { + (self.do_get_balance)(account) + } +} + +impl Module { + fn commit_state(s: State) { + for (address, changed) in s.into_iter() { + if let Some(balance) = changed.balance { + >::insert(&address, balance); + } + if let Some(code) = changed.code { + >::insert(&address, &code); + } + for (k, v) in changed.storage.into_iter() { + if let Some(value) = v { + >::insert((address.clone(), k), &value); + } else { + >::remove((address.clone(), k)); + } + } + } + } + + fn merge_state(commit_state: State, local: &mut State) { + for (address, changed) in commit_state.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); + } + } + } + } + + fn effect_create>( + transactor: &T::AccountId, + code: &[u8], + value: T::Balance, + ext: E + ) -> Option> { + let from_balance = ext.get_balance(transactor); + // TODO: a fee. + assert!(from_balance >= value); + + let dest = T::DetermineContractAddress::contract_address_for(code, transactor); + + // early-out if degenerate. + if &dest == transactor { + return None; + } + + let mut local = BTreeMap::new(); + + // two inserts are safe + assert!(&dest != transactor); + local.insert(dest, ChangeEntry { balance: Some(value), code: Some(code.to_vec()), storage: Default::default() }); + local.insert(transactor.clone(), ChangeEntry::balance_changed(from_balance - value)); + + Some(local) + } + + fn effect_transfer>( + transactor: &T::AccountId, + dest: &T::AccountId, + value: T::Balance, + ext: E + ) -> Option> { + let from_balance = ext.get_balance(transactor); + assert!(from_balance >= value); + + let to_balance = ext.get_balance(dest); + assert!(>::get(transactor) <= >::get(dest)); + assert!(to_balance + value > to_balance); // no overflow + + // TODO: a fee, based upon gaslimit/gasprice. + // TODO: consider storing upper-bound for contract's gas limit in fixed-length runtime + // code in contract itself and use that. + + let local: RefCell> = RefCell::new(BTreeMap::new()); + + if transactor != dest { + let mut local = local.borrow_mut(); + local.insert(transactor.clone(), ChangeEntry::balance_changed(from_balance - value)); + local.insert(dest.clone(), ChangeEntry::balance_changed(to_balance + value)); + } + + let should_commit = { + // Our local ext: Should be used for any transfers and creates that happen internally. + let ext = || Ext { + do_get_storage: |account: &T::AccountId, location: &[u8]| + local.borrow().get(account) + .and_then(|a| a.storage.get(location)) + .cloned() + .unwrap_or_else(|| ext.get_storage(account, location)), + do_get_code: |account: &T::AccountId| + local.borrow().get(account) + .and_then(|a| a.code.clone()) + .unwrap_or_else(|| ext.get_code(account)), + do_get_balance: |account: &T::AccountId| + local.borrow().get(account) + .and_then(|a| a.balance) + .unwrap_or_else(|| ext.get_balance(account)), + _unused: Default::default(), + }; + let mut _transfer = |inner_dest: &T::AccountId, value: T::Balance| { + if let Some(commit_state) = Self::effect_transfer(dest, inner_dest, value, ext()) { + Self::merge_state(commit_state, &mut *local.borrow_mut()); + } + }; + let mut _create = |code: &[u8], value: T::Balance| { + if let Some(commit_state) = Self::effect_create(dest, code, value, ext()) { + Self::merge_state(commit_state, &mut *local.borrow_mut()); + } + }; + let mut _put_storage = |location: Vec, value: Option>| { + local.borrow_mut() + .entry(dest.clone()) + .or_insert(Default::default()) + .storage.insert(location, value); + }; + + // TODO: logging (logs are just appended into a notable storage-based vector and cleared every + // block). + // TODO: execute code with ext(), put_storage, create and transfer as externalities. + true + }; + + if should_commit { + Some(local.into_inner()) + } else { + None + } + } +} + +impl MakePayment for Module { + fn make_payment(transactor: &T::AccountId) { + let b = Self::free_balance(transactor); + let transaction_fee = Self::transaction_fee(); + assert!(b >= transaction_fee, "attempt to transact without enough funds to pay fee"); + >::insert(transactor, b - transaction_fee); + } +} + +#[cfg(any(feature = "std", test))] +pub struct DummyContractAddressFor; +#[cfg(any(feature = "std", test))] +impl ContractAddressFor for DummyContractAddressFor { + fn contract_address_for(_code: &[u8], origin: &u64) -> u64 { + origin + 1 + } +} + +#[cfg(any(feature = "std", test))] +pub struct GenesisConfig { + pub sessions_per_era: T::BlockNumber, + pub current_era: T::BlockNumber, + pub balances: Vec<(T::AccountId, T::Balance)>, + pub intentions: Vec, + pub validator_count: u64, + pub bonding_duration: T::BlockNumber, + pub transaction_fee: T::Balance, +} + +#[cfg(any(feature = "std", test))] +impl GenesisConfig where T::AccountId: From { + pub fn simple() -> Self { + use primitives::traits::As; + 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_fee: T::Balance::sa(0), + } + } + + pub fn extended() -> Self { + use primitives::traits::As; + 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_fee: T::Balance::sa(1), + } + } +} + +#[cfg(any(feature = "std", test))] +impl Default for GenesisConfig { + fn default() -> Self { + use primitives::traits::As; + 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_fee: T::Balance::sa(0), + } + } +} + +#[cfg(any(feature = "std", test))] +impl primitives::BuildExternalities for GenesisConfig { + fn build_externalities(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(>::key()).to_vec() => self.intentions.encode(), + twox_128(>::key()).to_vec() => self.sessions_per_era.encode(), + twox_128(>::key()).to_vec() => self.validator_count.encode(), + twox_128(>::key()).to_vec() => self.bonding_duration.encode(), + twox_128(>::key()).to_vec() => self.transaction_fee.encode(), + twox_128(>::key()).to_vec() => self.current_era.encode(), + twox_128(>::key()).to_vec() => total_stake.encode() + ]; + + for (who, value) in self.balances.into_iter() { + r.insert(twox_128(&>::key_for(who)).to_vec(), value.encode()); + } + r + } +} + +#[cfg(test)] +mod tests { + use super::*; + use runtime_io::with_externalities; + use substrate_primitives::H256; + use primitives::BuildExternalities; + use primitives::traits::{HasPublicAux, Identity}; + use primitives::testing::{Digest, Header}; + + pub struct Test; + impl HasPublicAux for Test { + type PublicAux = u64; + } + impl consensus::Trait for Test { + type SessionKey = u64; + } + impl system::Trait for Test { + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = runtime_io::BlakeTwo256; + type Digest = Digest; + type AccountId = u64; + type Header = Header; + } + impl session::Trait for Test { + type PublicAux = ::PublicAux; + type ConvertAccountIdToSessionKey = Identity; + } + impl Trait for Test { + type Balance = u64; + type DetermineContractAddress = DummyContractAddressFor; + } + + fn new_test_ext(session_length: u64, sessions_per_era: u64, current_era: u64, monied: bool) -> runtime_io::TestExternalities { + let mut t = system::GenesisConfig::::default().build_externalities(); + t.extend(consensus::GenesisConfig::{ + authorities: vec![], + }.build_externalities()); + t.extend(session::GenesisConfig::{ + session_length, + validators: vec![10, 20], + }.build_externalities()); + t.extend(GenesisConfig::{ + sessions_per_era, + current_era, + balances: if monied { vec![(1, 10), (2, 20), (3, 30), (4, 40)] } else { vec![] }, + intentions: vec![], + validator_count: 2, + bonding_duration: 3, + transaction_fee: 0, + }.build_externalities()); + t + } + + type System = system::Module; + type Session = session::Module; + type Staking = Module; + + #[test] + fn staking_should_work() { + with_externalities(&mut new_test_ext(1, 2, 0, true), || { + assert_eq!(Staking::era_length(), 2); + assert_eq!(Staking::validator_count(), 2); + assert_eq!(Staking::bonding_duration(), 3); + assert_eq!(Session::validators(), vec![10, 20]); + + // Block 1: Add three validators. No obvious change. + System::set_block_number(1); + Staking::stake(&1); + Staking::stake(&2); + Staking::stake(&4); + Staking::check_new_era(); + assert_eq!(Session::validators(), vec![10, 20]); + + // Block 2: New validator set now. + System::set_block_number(2); + Staking::check_new_era(); + assert_eq!(Session::validators(), vec![4, 2]); + + // Block 3: Unstake highest, introduce another staker. No change yet. + System::set_block_number(3); + Staking::stake(&3); + Staking::unstake(&4); + Staking::check_new_era(); + + // Block 4: New era - validators change. + System::set_block_number(4); + Staking::check_new_era(); + assert_eq!(Session::validators(), vec![3, 2]); + + // Block 5: Transfer stake from highest to lowest. No change yet. + System::set_block_number(5); + Staking::transfer(&4, 1, 40); + Staking::check_new_era(); + + // Block 6: Lowest now validator. + System::set_block_number(6); + Staking::check_new_era(); + assert_eq!(Session::validators(), vec![1, 3]); + + // Block 7: Unstake three. No change yet. + System::set_block_number(7); + Staking::unstake(&3); + Staking::check_new_era(); + assert_eq!(Session::validators(), vec![1, 3]); + + // Block 8: Back to one and two. + System::set_block_number(8); + Staking::check_new_era(); + assert_eq!(Session::validators(), vec![1, 2]); + }); + } + + #[test] + fn staking_eras_work() { + with_externalities(&mut new_test_ext(1, 2, 0, true), || { + 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); + + // Block 1: No change. + System::set_block_number(1); + Staking::check_new_era(); + 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(); + assert_eq!(Staking::sessions_per_era(), 2); + assert_eq!(Staking::last_era_length_change(), 0); + assert_eq!(Staking::current_era(), 1); + + // Block 3: Schedule an era length change; no visible changes. + System::set_block_number(3); + Staking::set_sessions_per_era(3); + Staking::check_new_era(); + 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(); + 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(); + 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_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(); + assert_eq!(Staking::sessions_per_era(), 3); + assert_eq!(Staking::last_era_length_change(), 4); + assert_eq!(Staking::current_era(), 3); + }); + } + + #[test] + fn staking_balance_works() { + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + >::insert(1, 42); + assert_eq!(Staking::free_balance(&1), 42); + assert_eq!(Staking::reserved_balance(&1), 0); + assert_eq!(Staking::balance(&1), 42); + assert_eq!(Staking::free_balance(&2), 0); + assert_eq!(Staking::reserved_balance(&2), 0); + assert_eq!(Staking::balance(&2), 0); + }); + } + + #[test] + fn staking_balance_transfer_works() { + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + >::insert(1, 111); + Staking::transfer(&1, 2, 69); + assert_eq!(Staking::balance(&1), 42); + assert_eq!(Staking::balance(&2), 69); + }); + } + + #[test] + #[should_panic] + fn staking_balance_transfer_when_bonded_panics() { + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + >::insert(1, 111); + Staking::stake(&1); + Staking::transfer(&1, 2, 69); + }); + } + + #[test] + fn reserving_balance_should_work() { + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + >::insert(1, 111); + + assert_eq!(Staking::balance(&1), 111); + assert_eq!(Staking::free_balance(&1), 111); + assert_eq!(Staking::reserved_balance(&1), 0); + + Staking::reserve_balance(&1, 69); + + assert_eq!(Staking::balance(&1), 111); + assert_eq!(Staking::free_balance(&1), 42); + assert_eq!(Staking::reserved_balance(&1), 69); + }); + } + + #[test] + #[should_panic] + fn staking_balance_transfer_when_reserved_panics() { + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + >::insert(1, 111); + Staking::reserve_balance(&1, 69); + Staking::transfer(&1, 2, 69); + }); + } + + #[test] + fn deducting_balance_should_work() { + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + >::insert(1, 111); + assert!(Staking::deduct_unbonded(&1, 69)); + assert_eq!(Staking::free_balance(&1), 42); + }); + } + + #[test] + fn deducting_balance_should_fail_when_bonded() { + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + >::insert(1, 111); + >::insert(1, 2); + System::set_block_number(1); + assert_eq!(Staking::unlock_block(&1), LockStatus::LockedUntil(2)); + assert!(!Staking::deduct_unbonded(&1, 69)); + }); + } + + #[test] + fn refunding_balance_should_work() { + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + >::insert(1, 42); + Staking::refund(&1, 69); + assert_eq!(Staking::free_balance(&1), 111); + }); + } + + #[test] + fn slashing_balance_should_work() { + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + >::insert(1, 111); + Staking::reserve_balance(&1, 69); + assert!(Staking::slash(&1, 69)); + assert_eq!(Staking::free_balance(&1), 0); + assert_eq!(Staking::reserved_balance(&1), 42); + }); + } + + #[test] + fn slashing_incomplete_balance_should_work() { + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + >::insert(1, 42); + Staking::reserve_balance(&1, 21); + assert!(!Staking::slash(&1, 69)); + assert_eq!(Staking::free_balance(&1), 0); + assert_eq!(Staking::reserved_balance(&1), 0); + }); + } + + #[test] + fn unreserving_balance_should_work() { + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + >::insert(1, 111); + Staking::reserve_balance(&1, 111); + Staking::unreserve_balance(&1, 42); + assert_eq!(Staking::reserved_balance(&1), 69); + assert_eq!(Staking::free_balance(&1), 42); + }); + } + + #[test] + fn slashing_reserved_balance_should_work() { + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + >::insert(1, 111); + Staking::reserve_balance(&1, 111); + assert!(Staking::slash_reserved(&1, 42)); + assert_eq!(Staking::reserved_balance(&1), 69); + assert_eq!(Staking::free_balance(&1), 0); + }); + } + + #[test] + fn slashing_incomplete_reserved_balance_should_work() { + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + >::insert(1, 111); + Staking::reserve_balance(&1, 42); + assert!(!Staking::slash_reserved(&1, 69)); + assert_eq!(Staking::free_balance(&1), 69); + assert_eq!(Staking::reserved_balance(&1), 0); + }); + } + + #[test] + fn transferring_reserved_balance_should_work() { + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + >::insert(1, 111); + Staking::reserve_balance(&1, 111); + assert!(Staking::transfer_reserved_balance(&1, &2, 42)); + assert_eq!(Staking::reserved_balance(&1), 69); + assert_eq!(Staking::free_balance(&1), 0); + assert_eq!(Staking::reserved_balance(&2), 0); + assert_eq!(Staking::free_balance(&2), 42); + }); + } + + #[test] + fn transferring_incomplete_reserved_balance_should_work() { + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + >::insert(1, 111); + Staking::reserve_balance(&1, 42); + assert!(!Staking::transfer_reserved_balance(&1, &2, 69)); + assert_eq!(Staking::reserved_balance(&1), 0); + assert_eq!(Staking::free_balance(&1), 69); + assert_eq!(Staking::reserved_balance(&2), 0); + assert_eq!(Staking::free_balance(&2), 42); + }); + } +} diff --git a/substrate/runtime/system/Cargo.toml b/substrate/runtime/system/Cargo.toml new file mode 100644 index 0000000000000..89d4e48e201b8 --- /dev/null +++ b/substrate/runtime/system/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "substrate-runtime-system" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +hex-literal = "0.1.0" +serde = { version = "1.0", default_features = false } +safe-mix = { path = "../../../safe-mix", default_features = false} +substrate-codec = { path = "../../codec", default_features = false } +substrate-primitives = { path = "../../primitives", default_features = false } +substrate-runtime-std = { path = "../../runtime-std", default_features = false } +substrate-runtime-io = { path = "../../runtime-io", default_features = false } +substrate-runtime-support = { path = "../../runtime-support", default_features = false } +substrate-runtime-primitives = { path = "../primitives", default_features = false } + +[features] +default = ["std"] +std = [ + "serde/std", + "safe-mix/std", + "substrate-codec/std", + "substrate-primitives/std", + "substrate-runtime-std/std", + "substrate-runtime-io/std", + "substrate-runtime-support/std", + "substrate-runtime-primitives/std", +] diff --git a/substrate/runtime/system/src/lib.rs b/substrate/runtime/system/src/lib.rs new file mode 100644 index 0000000000000..84f89678a6a34 --- /dev/null +++ b/substrate/runtime/system/src/lib.rs @@ -0,0 +1,178 @@ +// 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 . + +//! System manager: Handles lowest level stuff like depositing logs, basic set up and take down of +//! temporary storage entries, access to old block hashes. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg_attr(any(feature = "std", test), macro_use)] +extern crate substrate_runtime_std as rstd; + +#[macro_use] +extern crate substrate_runtime_support as runtime_support; + +#[cfg(feature = "std")] +extern crate serde; + +extern crate substrate_runtime_io as runtime_io; +extern crate substrate_codec as codec; +extern crate substrate_runtime_primitives as primitives; +extern crate safe_mix; + +use rstd::prelude::*; +use runtime_io::Hashing; +use primitives::traits::{self, CheckEqual, SimpleArithmetic, SimpleBitOps, Zero, One, Bounded}; +use runtime_support::{StorageValue, StorageMap, Parameter}; +use safe_mix::TripletMix; + +#[cfg(any(feature = "std", test))] +use rstd::marker::PhantomData; +#[cfg(any(feature = "std", test))] +use codec::Slicable; + +#[cfg(any(feature = "std", test))] +use runtime_io::{twox_128, TestExternalities}; + +pub trait Trait { + type Index: Parameter + Default + SimpleArithmetic + Copy; + type BlockNumber: Parameter + SimpleArithmetic + Default + Bounded + Copy; + type Hash: Parameter + SimpleBitOps + Default + Copy + CheckEqual; + type Hashing: Hashing; + type Digest: Parameter + Default + traits::Digest; + type AccountId: Parameter + Ord + Default; + type Header: traits::Header; +} + +decl_module! { + pub struct Module; +} + +decl_storage! { + trait Store for Module; + + pub AccountIndex get(account_index): b"sys:non" => default map [ T::AccountId => T::Index ]; + pub BlockHash get(block_hash): b"sys:old" => required map [ T::BlockNumber => T::Hash ]; + + RandomSeed get(random_seed): b"sys:rnd" => required T::Hash; + // The current block number being processed. Set by `execute_block`. + Number get(block_number): b"sys:num" => required T::BlockNumber; + ParentHash get(parent_hash): b"sys:pha" => required T::Hash; + ExtrinsicsRoot get(extrinsics_root): b"sys:txr" => required T::Hash; + Digest get(digest): b"sys:dig" => default T::Digest; +} + +impl Module { + /// Start the execution of a particular block. + pub fn initialise(number: &T::BlockNumber, parent_hash: &T::Hash, txs_root: &T::Hash) { + // populate environment. + >::put(number); + >::put(parent_hash); + >::put(txs_root); + >::put(Self::calculate_random()); + } + + /// Remove temporary "environment" entries in storage. + pub fn finalise() -> T::Header { + >::kill(); + + let number = >::take(); + let parent_hash = >::take(); + let digest = >::take(); + let extrinsics_root = >::take(); + let storage_root = T::Hashing::storage_root(); + ::new(number, extrinsics_root, storage_root, parent_hash, digest) + } + + /// Deposits a log and ensures it matches the blocks log data. + pub fn deposit_log(item: ::Item) { + let mut l = >::get(); + traits::Digest::push(&mut l, item); + >::put(l); + } + + /// Records a particular block number and hash combination. + pub fn record_block_hash>(header: &H) { + // store the header hash in storage; we can't do it before otherwise there would be a + // cyclic dependency. + >::insert(header.number(), &T::Hashing::hash_of(header)); + } + + /// Initializes the state following the determination of the genesis block. + pub fn initialise_genesis_state>(header: &H) { + Self::record_block_hash(header); + } + + /// Calculate the current block's random seed. + fn calculate_random() -> T::Hash { + (0..81) + .scan( + { let mut n = Self::block_number().clone(); n -= T::BlockNumber::one(); n }, + |c, _| { if *c > T::BlockNumber::zero() { *c -= T::BlockNumber::one() }; Some(c.clone()) + }) + .map(Self::block_hash) + .triplet_mix() + } + + /// Get the basic externalities for this module, useful for tests. + #[cfg(any(feature = "std", test))] + pub fn externalities() -> TestExternalities { + map![ + twox_128(&>::key_for(T::BlockNumber::zero())).to_vec() => [69u8; 32].encode(), // TODO: replace with Hash::default().encode + twox_128(>::key()).to_vec() => T::BlockNumber::one().encode(), + twox_128(>::key()).to_vec() => [69u8; 32].encode(), // TODO: replace with Hash::default().encode + twox_128(>::key()).to_vec() => T::Hash::default().encode() + ] + } + + /// Set the block number to something in particular. Can be used as an alternative to + /// `initialise` for tests that don't need to bother with the other environment entries. + #[cfg(any(feature = "std", test))] + pub fn set_block_number(n: T::BlockNumber) { + >::put(n); + } + + /// Increment a particular account's nonce by 1. + pub fn inc_account_index(who: &T::AccountId) { + >::insert(who, Self::account_index(who) + T::Index::one()); + } +} + +#[cfg(any(feature = "std", test))] +pub struct GenesisConfig(PhantomData); + +#[cfg(any(feature = "std", test))] +impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig(PhantomData) + } +} + +#[cfg(any(feature = "std", test))] +impl primitives::BuildExternalities for GenesisConfig +{ + fn build_externalities(self) -> runtime_io::TestExternalities { + use runtime_io::twox_128; + use codec::Slicable; + + map![ + twox_128(&>::key_for(T::BlockNumber::zero())).to_vec() => [69u8; 32].encode(), + twox_128(>::key()).to_vec() => 1u64.encode(), + twox_128(>::key()).to_vec() => [69u8; 32].encode(), + twox_128(>::key()).to_vec() => [0u8; 32].encode() + ] + } +} diff --git a/substrate/runtime/timestamp/Cargo.toml b/substrate/runtime/timestamp/Cargo.toml new file mode 100644 index 0000000000000..f9382e24be470 --- /dev/null +++ b/substrate/runtime/timestamp/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "substrate-runtime-timestamp" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +hex-literal = "0.1.0" +serde = { version = "1.0", default_features = false } +substrate-runtime-std = { path = "../../runtime-std", default_features = false } +substrate-runtime-support = { path = "../../runtime-support", default_features = false } +substrate-runtime-primitives = { path = "../primitives", default_features = false } +substrate-codec = { path = "../../codec", default_features = false } + +[dev-dependencies] +substrate-runtime-io = { path = "../../runtime-io", default_features = true } + +[features] +default = ["std"] +std = [ + "substrate-runtime-std/std", + "substrate-runtime-support/std", + "substrate-runtime-primitives/std", + "serde/std", + "substrate-codec/std", +] diff --git a/substrate/runtime/timestamp/src/lib.rs b/substrate/runtime/timestamp/src/lib.rs new file mode 100644 index 0000000000000..c651fd8753f43 --- /dev/null +++ b/substrate/runtime/timestamp/src/lib.rs @@ -0,0 +1,93 @@ +// 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 . + +//! Timestamp manager: just handles the current timestamp. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg_attr(test, macro_use)] +extern crate substrate_runtime_std as rstd; + +#[macro_use] +extern crate substrate_runtime_support as runtime_support; + +#[cfg(test)] +extern crate substrate_runtime_io as runtime_io; + +extern crate substrate_runtime_primitives as runtime_primitives; +extern crate substrate_codec as codec; + +use runtime_support::{StorageValue, Parameter}; +use runtime_primitives::traits::HasPublicAux; + +pub trait Trait: HasPublicAux { + type Value: Parameter + Default; +} + +decl_module! { + pub struct Module; + pub enum Call where aux: T::PublicAux { + fn set(aux, now: T::Value) = 0; + } +} + +decl_storage! { + pub trait Store for Module; + pub Now get(now): b"tim:val" => required T::Value; +} + +impl Module { + pub fn get() -> T::Value { + ::Now::get() + } + + /// Set the current time. + fn set(_aux: &T::PublicAux, now: T::Value) { + ::Now::put(now); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use runtime_io::{with_externalities, twox_128, TestExternalities}; + use codec::Joiner; + use runtime_support::storage::StorageValue; + + struct TraitImpl; + impl HasPublicAux for TraitImpl { + type PublicAux = u64; + } + impl Trait for TraitImpl { + type Value = u64; + } + type Timestamp = Module; + + #[test] + fn timestamp_works() { + + let mut t: TestExternalities = map![ + twox_128(::Now::key()).to_vec() => vec![].and(&42u64) + ]; + + with_externalities(&mut t, || { + assert_eq!(::Now::get(), 42); + Timestamp::aux_dispatch(Call::set(69), &0); + assert_eq!(Timestamp::now(), 69); + }); + } +} diff --git a/substrate/test-runtime/src/transaction.rs b/substrate/test-runtime/src/transaction.rs index 81fd37f779a5b..19ddba99e4ef3 100644 --- a/substrate/test-runtime/src/transaction.rs +++ b/substrate/test-runtime/src/transaction.rs @@ -53,7 +53,7 @@ impl Slicable for Transaction { } } -impl ::codec::NonTrivialSlicable for Transaction {} + #[cfg(test)] mod tests { diff --git a/substrate/test-runtime/src/unchecked_transaction.rs b/substrate/test-runtime/src/unchecked_transaction.rs index 72b60098c11a8..0aff8d472738e 100644 --- a/substrate/test-runtime/src/unchecked_transaction.rs +++ b/substrate/test-runtime/src/unchecked_transaction.rs @@ -60,7 +60,7 @@ impl Slicable for UncheckedTransaction { } } -impl ::codec::NonTrivialSlicable for UncheckedTransaction {} + #[cfg(test)] mod tests { diff --git a/substrate/test-runtime/wasm/Cargo.lock b/substrate/test-runtime/wasm/Cargo.lock index 8993d9c049a4d..6f379e9dad73c 100644 --- a/substrate/test-runtime/wasm/Cargo.lock +++ b/substrate/test-runtime/wasm/Cargo.lock @@ -621,8 +621,9 @@ name = "substrate-runtime-support" version = "0.1.0" dependencies = [ "ed25519 0.1.0", - "environmental 0.1.0", "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-io 0.1.0", diff --git a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm index 997a70bdad4fa..1e934f702c35d 100644 Binary files a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm and b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm differ diff --git a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm old mode 100755 new mode 100644 index 74df34491a094..b8bcd03d83941 Binary files a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm and b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm differ