diff --git a/.gitignore b/.gitignore index a7e0f6be3d18f..efe1d5d61f48e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ polkadot/runtime/wasm/target/ substrate/executor/wasm/target/ substrate/test-runtime/wasm/target/ +demo/runtime/wasm/target/ **/._* diff --git a/Cargo.lock b/Cargo.lock index 1b364299fbb2c..d270ab4d0c558 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,6 +181,77 @@ name = "crunchy" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "demo-cli" +version = "0.1.0" +dependencies = [ + "clap 2.29.4 (registry+https://github.com/rust-lang/crates.io-index)", + "demo-executor 0.1.0", + "demo-primitives 0.1.0", + "demo-runtime 0.1.0", + "ed25519 0.1.0", + "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hex-literal 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)", + "substrate-client 0.1.0", + "substrate-codec 0.1.0", + "substrate-executor 0.1.0", + "substrate-primitives 0.1.0", + "substrate-rpc-servers 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-state-machine 0.1.0", + "triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "demo-executor" +version = "0.1.0" +dependencies = [ + "demo-primitives 0.1.0", + "demo-runtime 0.1.0", + "ed25519 0.1.0", + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-executor 0.1.0", + "substrate-keyring 0.1.0", + "substrate-primitives 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-support 0.1.0", + "substrate-state-machine 0.1.0", + "triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "demo-primitives" +version = "0.1.0" +dependencies = [ + "pretty_assertions 0.4.1 (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-std 0.1.0", + "substrate-serializer 0.1.0", +] + +[[package]] +name = "demo-runtime" +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)", + "substrate-codec 0.1.0", + "substrate-keyring 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 = "difference" version = "1.0.0" @@ -617,6 +688,11 @@ dependencies = [ "xmltree 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "integer-sqrt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "iovec" version = "0.1.2" @@ -1524,6 +1600,7 @@ 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]] @@ -2077,6 +2154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum hyper 0.11.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4de6edd503089841ebfa88341e1c00fb19b6bf93d820d908db15960fd31226" "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" "checksum igd 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "356a0dc23a4fa0f8ce4777258085d00a01ea4923b2efd93538fc44bf5e1bda76" +"checksum integer-sqrt 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8833702c315192502093b244e29c6ab9c55454adfe21b879a87a039ea8fe8520" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" "checksum ipnetwork 0.12.7 (registry+https://github.com/rust-lang/crates.io-index)" = "2134e210e2a024b5684f90e1556d5f71a1ce7f8b12e9ac9924c67fb36f63b336" "checksum isatty 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f2a233726c7bb76995cec749d59582e5664823b7245d4970354408f1d79a7a2" diff --git a/Cargo.toml b/Cargo.toml index 03d67a0e3f094..27ff076184e40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,9 +40,14 @@ members = [ "substrate/serializer", "substrate/state-machine", "substrate/test-runtime", + "demo/runtime", + "demo/primitives", + "demo/executor", + "demo/cli", ] exclude = [ "polkadot/runtime/wasm", + "demo/runtime/wasm", "substrate/executor/wasm", "substrate/pwasm-alloc", "substrate/pwasm-libc", diff --git a/build.sh b/build.sh index 4e5e035dc468f..d6545f4f4fb8f 100755 --- a/build.sh +++ b/build.sh @@ -3,3 +3,4 @@ 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/Cargo.toml b/demo/cli/Cargo.toml new file mode 100644 index 0000000000000..f2910d99af194 --- /dev/null +++ b/demo/cli/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "demo-cli" +version = "0.1.0" +authors = ["Parity Technologies "] +description = "Substrate Demo node implementation in Rust." + +[dependencies] +clap = { version = "2.27", features = ["yaml"] } +env_logger = "0.4" +error-chain = "0.11" +log = "0.3" +hex-literal = "0.1" +triehash = "0.1" +ed25519 = { path = "../../substrate/ed25519" } +substrate-client = { path = "../../substrate/client" } +substrate-codec = { path = "../../substrate/codec" } +substrate-runtime-io = { path = "../../substrate/runtime-io" } +substrate-state-machine = { path = "../../substrate/state-machine" } +substrate-executor = { path = "../../substrate/executor" } +substrate-primitives = { path = "../../substrate/primitives" } +substrate-rpc-servers = { path = "../../substrate/rpc-servers" } +demo-primitives = { path = "../primitives" } +demo-executor = { path = "../executor" } +demo-runtime = { path = "../runtime" } diff --git a/demo/cli/src/cli.yml b/demo/cli/src/cli.yml new file mode 100644 index 0000000000000..263af9d9ef300 --- /dev/null +++ b/demo/cli/src/cli.yml @@ -0,0 +1,12 @@ +name: substrate-demo +author: "Parity Team " +about: Substrate Demo Node Rust Implementation +args: + - log: + short: l + value_name: LOG_PATTERN + help: Sets a custom logging + takes_value: true +subcommands: + - validator: + about: Run validator node diff --git a/demo/cli/src/error.rs b/demo/cli/src/error.rs new file mode 100644 index 0000000000000..84fa2f1092987 --- /dev/null +++ b/demo/cli/src/error.rs @@ -0,0 +1,29 @@ +// 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 . + +//! Initialization errors. + +use client; + +error_chain! { + foreign_links { + Io(::std::io::Error) #[doc="IO error"]; + Cli(::clap::Error) #[doc="CLI error"]; + } + links { + Client(client::error::Error, client::error::ErrorKind) #[doc="Client error"]; + } +} diff --git a/demo/cli/src/lib.rs b/demo/cli/src/lib.rs new file mode 100644 index 0000000000000..8c0a3711abcb1 --- /dev/null +++ b/demo/cli/src/lib.rs @@ -0,0 +1,134 @@ +// 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 . + +//! Substrate Demo CLI library. + +#![warn(missing_docs)] + +extern crate env_logger; +extern crate ed25519; +extern crate triehash; +extern crate substrate_codec as codec; +extern crate substrate_state_machine as state_machine; +extern crate substrate_client as client; +extern crate substrate_primitives as primitives; +extern crate substrate_rpc_servers as rpc; +extern crate demo_primitives; +extern crate demo_executor; +extern crate demo_runtime; + +#[macro_use] +extern crate hex_literal; +#[macro_use] +extern crate clap; +#[macro_use] +extern crate error_chain; +#[macro_use] +extern crate log; + +pub mod error; + +use codec::Slicable; +use demo_runtime::genesismap::{additional_storage_with_genesis, GenesisConfig}; +use client::genesis; + +/// Parse command line arguments and start the node. +/// +/// IANA unassigned port ranges that we could use: +/// 6717-6766 Unassigned +/// 8504-8553 Unassigned +/// 9556-9591 Unassigned +/// 9803-9874 Unassigned +/// 9926-9949 Unassigned +pub fn run(args: I) -> error::Result<()> where + I: IntoIterator, + T: Into + Clone, +{ + let yaml = load_yaml!("./cli.yml"); + let matches = clap::App::from_yaml(yaml).version(crate_version!()).get_matches_from_safe(args)?; + + // TODO [ToDr] Split parameters parsing from actual execution. + let log_pattern = matches.value_of("log").unwrap_or(""); + init_logger(log_pattern); + + // Create client + let executor = demo_executor::Executor::new(); + let mut storage = Default::default(); + 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. + }; + let prepare_genesis = || { + storage = genesis_config.genesis_map(); + let block = genesis::construct_genesis_block(&storage); + storage.extend(additional_storage_with_genesis(&block)); + (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 = client::new_in_mem(executor, prepare_genesis)?; + + let address = "127.0.0.1:9933".parse().unwrap(); + let handler = rpc::rpc_handler(client); + let server = rpc::start_http(&address, handler)?; + + if let Some(_) = matches.subcommand_matches("validator") { + info!("Starting validator."); + server.wait(); + return Ok(()); + } + + println!("No command given.\n"); + let _ = clap::App::from_yaml(yaml).print_long_help(); + + Ok(()) +} + +fn init_logger(pattern: &str) { + let mut builder = env_logger::LogBuilder::new(); + // Disable info logging by default for some modules: + builder.filter(Some("hyper"), log::LogLevelFilter::Warn); + // Enable info for others. + builder.filter(None, log::LogLevelFilter::Info); + + if let Ok(lvl) = std::env::var("RUST_LOG") { + builder.parse(&lvl); + } + + builder.parse(pattern); + + + builder.init().expect("Logger initialized only once."); +} diff --git a/demo/executor/Cargo.toml b/demo/executor/Cargo.toml new file mode 100644 index 0000000000000..ad2d53f9921b3 --- /dev/null +++ b/demo/executor/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "demo-executor" +version = "0.1.0" +authors = ["Parity Technologies "] +description = "Substrate Demo node implementation in Rust." + +[dependencies] +hex-literal = "0.1" +triehash = { version = "0.1" } +ed25519 = { path = "../../substrate/ed25519" } +substrate-codec = { path = "../../substrate/codec" } +substrate-runtime-io = { path = "../../substrate/runtime-io" } +substrate-runtime-support = { path = "../../substrate/runtime-support" } +substrate-state-machine = { path = "../../substrate/state-machine" } +substrate-executor = { path = "../../substrate/executor" } +substrate-primitives = { path = "../../substrate/primitives" } +demo-primitives = { path = "../primitives" } +demo-runtime = { path = "../runtime" } + +[dev-dependencies] +substrate-keyring = { path = "../../substrate/keyring" } diff --git a/demo/executor/src/lib.rs b/demo/executor/src/lib.rs new file mode 100644 index 0000000000000..1740897add160 --- /dev/null +++ b/demo/executor/src/lib.rs @@ -0,0 +1,248 @@ +// 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 . + +//! A `CodeExecutor` specialisation which uses natively compiled runtime when the wasm to be +//! executed is equivalent to the natively compiled code. + +extern crate demo_runtime; +#[macro_use] extern crate substrate_executor; +extern crate substrate_codec as codec; +extern crate substrate_state_machine as state_machine; +extern crate substrate_runtime_io as runtime_io; +extern crate substrate_primitives as primitives; +extern crate demo_primitives; +extern crate ed25519; +extern crate triehash; + +#[cfg(test)] extern crate substrate_keyring as keyring; +#[cfg(test)] extern crate substrate_runtime_support as runtime_support; +#[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")); + +#[cfg(test)] +mod tests { + use runtime_io; + use super::Executor; + use substrate_executor::WasmExecutor; + use codec::{KeyedVec, Slicable, Joiner}; + use keyring::Keyring::{self, Alice, Bob}; + use runtime_support::Hashable; + use demo_runtime::runtime::staking::{self, balance, BALANCE_OF}; + use state_machine::{CodeExecutor, TestExternalities}; + use primitives::twox_128; + use demo_primitives::{Hash, Header, BlockNumber, Block, Digest, Transaction, + UncheckedTransaction, Function}; + use ed25519::{Public, Pair}; + + const BLOATY_CODE: &[u8] = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm"); + const COMPACT_CODE: &[u8] = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm"); + + // TODO: move into own crate. + macro_rules! map { + ($( $name:expr => $value:expr ),*) => ( + vec![ $( ( $name, $value ) ),* ].into_iter().collect() + ) + } + + fn tx() -> UncheckedTransaction { + let transaction = Transaction { + signed: Alice.into(), + nonce: 0, + function: Function::StakingTransfer(Bob.into(), 69), + }; + let signature = Keyring::from_raw_public(transaction.signed).unwrap() + .sign(&transaction.encode()); + + UncheckedTransaction { transaction, signature } + } + + #[test] + fn panic_execution_with_foreign_code_gives_error() { + let mut t: TestExternalities = map![ + twox_128(&Alice.to_raw_public().to_keyed_vec(BALANCE_OF)).to_vec() => vec![68u8, 0, 0, 0, 0, 0, 0, 0] + ]; + + let r = Executor::new().call(&mut t, BLOATY_CODE, "execute_transaction", &vec![].and(&Header::from_block_number(1u64)).and(&tx())); + assert!(r.is_err()); + } + + #[test] + fn panic_execution_with_native_equivalent_code_gives_error() { + let mut t: TestExternalities = map![ + twox_128(&Alice.to_raw_public().to_keyed_vec(BALANCE_OF)).to_vec() => vec![68u8, 0, 0, 0, 0, 0, 0, 0] + ]; + + let r = Executor::new().call(&mut t, COMPACT_CODE, "execute_transaction", &vec![].and(&Header::from_block_number(1u64)).and(&tx())); + assert!(r.is_err()); + } + + #[test] + fn successful_execution_with_native_equivalent_code_gives_ok() { + let mut t: TestExternalities = map![ + twox_128(&Alice.to_raw_public().to_keyed_vec(BALANCE_OF)).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0] + ]; + + let r = Executor::new().call(&mut t, COMPACT_CODE, "execute_transaction", &vec![].and(&Header::from_block_number(1u64)).and(&tx())); + assert!(r.is_ok()); + + runtime_io::with_externalities(&mut t, || { + assert_eq!(balance(&Alice), 42); + assert_eq!(balance(&Bob), 69); + }); + } + + #[test] + fn successful_execution_with_foreign_code_gives_ok() { + let mut t: TestExternalities = map![ + twox_128(&Alice.to_raw_public().to_keyed_vec(BALANCE_OF)).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0] + ]; + + let r = Executor::new().call(&mut t, BLOATY_CODE, "execute_transaction", &vec![].and(&Header::from_block_number(1u64)).and(&tx())); + assert!(r.is_ok()); + + runtime_io::with_externalities(&mut t, || { + assert_eq!(balance(&Alice), 42); + assert_eq!(balance(&Bob), 69); + }); + } + + fn new_test_ext() -> TestExternalities { + staking::testing::externalities(2, 2, 0) + } + + fn construct_block(number: BlockNumber, parent_hash: Hash, state_root: Hash, txs: 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()); + + UncheckedTransaction { transaction, signature } + }).collect::>(); + + let transaction_root = ordered_trie_root(transactions.iter().map(Slicable::encode)).0.into(); + + let header = Header { + parent_hash, + number, + state_root, + transaction_root, + digest: Digest { logs: vec![], }, + }; + let hash = header.blake2_256(); + + (Block { header, transactions }.encode(), hash.into()) + } + + fn block1() -> (Vec, Hash) { + construct_block( + 1, + [69u8; 32].into(), + hex!("970ae19447bef129c88ee80c72797fa9dfeda4ca1a26d10102b669d776eb0ccf").into(), + vec![Transaction { + signed: Alice.into(), + nonce: 0, + function: Function::StakingTransfer(Bob.into(), 69), + }] + ) + } + + fn block2() -> (Vec, Hash) { + construct_block( + 2, + block1().1, + hex!("347ece6ef0d193bd7c2bfbda17706b82eb24c0965f415784a44b138f0df034cd").into(), + vec![ + Transaction { + signed: Bob.into(), + nonce: 0, + function: Function::StakingTransfer(Alice.into(), 5), + }, + Transaction { + signed: Alice.into(), + nonce: 1, + function: Function::StakingTransfer(Bob.into(), 15), + } + ] + ) + } + + #[test] + fn full_native_block_import_works() { + let mut t = new_test_ext(); + + Executor::new().call(&mut t, COMPACT_CODE, "execute_block", &block1().0).unwrap(); + + runtime_io::with_externalities(&mut t, || { + assert_eq!(balance(&Alice), 42); + assert_eq!(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), 32); + assert_eq!(balance(&Bob), 79); + }); + } + + #[test] + fn full_wasm_block_import_works() { + let mut t = new_test_ext(); + + WasmExecutor.call(&mut t, COMPACT_CODE, "execute_block", &block1().0).unwrap(); + + runtime_io::with_externalities(&mut t, || { + assert_eq!(balance(&Alice), 42); + assert_eq!(balance(&Bob), 69); + }); + + WasmExecutor.call(&mut t, COMPACT_CODE, "execute_block", &block2().0).unwrap(); + + runtime_io::with_externalities(&mut t, || { + assert_eq!(balance(&Alice), 32); + assert_eq!(balance(&Bob), 79); + }); + } + + #[test] + fn panic_execution_gives_error() { + let mut t: TestExternalities = map![ + twox_128(&Alice.to_raw_public().to_keyed_vec(BALANCE_OF)).to_vec() => vec![68u8, 0, 0, 0, 0, 0, 0, 0] + ]; + + let foreign_code = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm"); + let r = WasmExecutor.call(&mut t, &foreign_code[..], "execute_transaction", &vec![].and(&Header::from_block_number(1u64)).and(&tx())); + assert!(r.is_err()); + } + + #[test] + fn successful_execution_gives_ok() { + let mut t: TestExternalities = map![ + twox_128(&Alice.to_raw_public().to_keyed_vec(BALANCE_OF)).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0] + ]; + + let foreign_code = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm"); + let r = WasmExecutor.call(&mut t, &foreign_code[..], "execute_transaction", &vec![].and(&Header::from_block_number(1u64)).and(&tx())); + assert!(r.is_ok()); + + runtime_io::with_externalities(&mut t, || { + assert_eq!(balance(&Alice), 42); + assert_eq!(balance(&Bob), 69); + }); + } +} diff --git a/demo/primitives/Cargo.toml b/demo/primitives/Cargo.toml new file mode 100644 index 0000000000000..a8365cf41fb51 --- /dev/null +++ b/demo/primitives/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "demo-primitives" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +serde = { version = "1.0", default_features = false } +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 } + +[dev-dependencies] +substrate-serializer = { path = "../../substrate/serializer" } +pretty_assertions = "0.4" + +[features] +default = ["std"] +std = [ + "substrate-codec/std", + "substrate-primitives/std", + "substrate-runtime-std/std", + "serde_derive", + "serde/std", +] diff --git a/demo/primitives/src/block.rs b/demo/primitives/src/block.rs new file mode 100644 index 0000000000000..4183498fc7413 --- /dev/null +++ b/demo/primitives/src/block.rs @@ -0,0 +1,187 @@ +// 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}; +use transaction::UncheckedTransaction; + +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; + +/// Hash used to refer to a transaction hash. +pub type TransactionHash = 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) + } +} + +/// 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) = try_opt!(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 + } +} + +/// 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 new file mode 100644 index 0000000000000..e68d6d8727208 --- /dev/null +++ b/demo/primitives/src/lib.rs @@ -0,0 +1,69 @@ +// 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 . + +//! Low-level types used throughout the Substrate Demo code. + +#![warn(missing_docs)] + +#![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; + +extern crate substrate_runtime_std as rstd; +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 mod transaction; + +pub use self::block::{Header, Block, Log, Digest}; +pub use self::block::Number as BlockNumber; +pub use self::transaction::{Transaction, UncheckedTransaction, Function, Proposal, VoteThreshold}; + +/// 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; + +/// 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; + +/// 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; diff --git a/demo/primitives/src/transaction.rs b/demo/primitives/src/transaction.rs new file mode 100644 index 0000000000000..8f0027459a6b4 --- /dev/null +++ b/demo/primitives/src/transaction.rs @@ -0,0 +1,495 @@ +// 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 codec::{Input, Slicable, NonTrivialSlicable}; +use {AccountId, SessionKey}; + +#[cfg(feature = "std")] +use std::fmt; + +use block::Number as BlockNumber; + +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +#[repr(u8)] +enum InternalFunctionId { + SystemSetCode = 0x00, + + SessionSetLength = 0x10, + SessionForceNewSession = 0x11, + + StakingSetSessionsPerEra = 0x20, + StakingSetBondingDuration = 0x21, + StakingSetValidatorCount = 0x22, + StakingForceNewEra = 0x23, + + DemocracyCancelReferendum = 0x30, + DemocracyStartReferendum = 0x31, + + CouncilSetDesiredSeats = 0x40, + CouncilRemoveMember = 0x41, + CouncilSetPresentationDuration = 0x42, + CouncilSetTermDuration = 0x43, + + CouncilVoteSetCooloffPeriod = 0x50, + CouncilVoteSetVotingPeriod = 0x51, +} + +impl InternalFunctionId { + /// Derive `Some` value from a `u8`, or `None` if it's invalid. + fn from_u8(value: u8) -> Option { + let functions = [ + InternalFunctionId::SystemSetCode, + InternalFunctionId::SessionSetLength, + InternalFunctionId::SessionForceNewSession, + InternalFunctionId::StakingSetSessionsPerEra, + InternalFunctionId::StakingSetBondingDuration, + InternalFunctionId::StakingSetValidatorCount, + InternalFunctionId::StakingForceNewEra, + InternalFunctionId::DemocracyCancelReferendum, + InternalFunctionId::DemocracyStartReferendum, + InternalFunctionId::CouncilSetDesiredSeats, + InternalFunctionId::CouncilRemoveMember, + InternalFunctionId::CouncilSetPresentationDuration, + InternalFunctionId::CouncilSetTermDuration, + InternalFunctionId::CouncilVoteSetCooloffPeriod, + InternalFunctionId::CouncilVoteSetVotingPeriod, + ]; + functions.iter().map(|&f| f).find(|&f| value == f as u8) + } +} + +/// A means of determining whether a referendum has gone through or not. +#[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 {} + +/// Internal functions that can be dispatched to. +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +#[allow(missing_docs)] +pub enum Proposal { + SystemSetCode(Vec), + SessionSetLength(BlockNumber), + SessionForceNewSession, + StakingSetSessionsPerEra(BlockNumber), + StakingSetBondingDuration(BlockNumber), + StakingSetValidatorCount(u32), + StakingForceNewEra, + DemocracyStartReferendum(Box, VoteThreshold), + DemocracyCancelReferendum(u32), + CouncilSetDesiredSeats(u32), + CouncilRemoveMember(AccountId), + CouncilSetPresentationDuration(BlockNumber), + CouncilSetTermDuration(BlockNumber), + CouncilVoteSetCooloffPeriod(BlockNumber), + CouncilVoteSetVotingPeriod(BlockNumber), +} + +impl Slicable for Proposal { + fn decode(input: &mut I) -> Option { + let id = u8::decode(input).and_then(InternalFunctionId::from_u8)?; + let function = match id { + InternalFunctionId::SystemSetCode => + Proposal::SystemSetCode(Slicable::decode(input)?), + InternalFunctionId::SessionSetLength => + Proposal::SessionSetLength(Slicable::decode(input)?), + InternalFunctionId::SessionForceNewSession => + Proposal::SessionForceNewSession, + InternalFunctionId::StakingSetSessionsPerEra => + Proposal::StakingSetSessionsPerEra(Slicable::decode(input)?), + InternalFunctionId::StakingSetBondingDuration => + Proposal::StakingSetBondingDuration(Slicable::decode(input)?), + InternalFunctionId::StakingSetValidatorCount => + Proposal::StakingSetValidatorCount(Slicable::decode(input)?), + InternalFunctionId::StakingForceNewEra => + Proposal::StakingForceNewEra, + InternalFunctionId::DemocracyStartReferendum => { + let a = Slicable::decode(input)?; + let b = Slicable::decode(input)?; + Proposal::DemocracyStartReferendum(Box::new(a), b) + } + InternalFunctionId::DemocracyCancelReferendum => + Proposal::DemocracyCancelReferendum(Slicable::decode(input)?), + InternalFunctionId::CouncilSetDesiredSeats => + Proposal::CouncilSetDesiredSeats(Slicable::decode(input)?), + InternalFunctionId::CouncilRemoveMember => + Proposal::CouncilRemoveMember(Slicable::decode(input)?), + InternalFunctionId::CouncilSetPresentationDuration => + Proposal::CouncilSetPresentationDuration(Slicable::decode(input)?), + InternalFunctionId::CouncilSetTermDuration => + Proposal::CouncilSetTermDuration(Slicable::decode(input)?), + InternalFunctionId::CouncilVoteSetCooloffPeriod => + Proposal::CouncilVoteSetCooloffPeriod(Slicable::decode(input)?), + InternalFunctionId::CouncilVoteSetVotingPeriod => + Proposal::CouncilVoteSetVotingPeriod(Slicable::decode(input)?), + }; + + Some(function) + } + + fn encode(&self) -> Vec { + let mut v = Vec::new(); + match *self { + Proposal::SystemSetCode(ref data) => { + (InternalFunctionId::SystemSetCode as u8).using_encoded(|s| v.extend(s)); + data.using_encoded(|s| v.extend(s)); + } + Proposal::SessionSetLength(ref data) => { + (InternalFunctionId::SessionSetLength as u8).using_encoded(|s| v.extend(s)); + data.using_encoded(|s| v.extend(s)); + } + Proposal::SessionForceNewSession => { + (InternalFunctionId::SessionForceNewSession as u8).using_encoded(|s| v.extend(s)); + } + Proposal::StakingSetSessionsPerEra(ref data) => { + (InternalFunctionId::StakingSetSessionsPerEra as u8).using_encoded(|s| v.extend(s)); + data.using_encoded(|s| v.extend(s)); + } + Proposal::StakingSetBondingDuration(ref data) => { + (InternalFunctionId::StakingSetBondingDuration as u8).using_encoded(|s| v.extend(s)); + data.using_encoded(|s| v.extend(s)); + } + Proposal::StakingSetValidatorCount(ref data) => { + (InternalFunctionId::StakingSetValidatorCount as u8).using_encoded(|s| v.extend(s)); + data.using_encoded(|s| v.extend(s)); + } + Proposal::StakingForceNewEra => { + (InternalFunctionId::StakingForceNewEra as u8).using_encoded(|s| v.extend(s)); + } + Proposal::DemocracyCancelReferendum(ref data) => { + (InternalFunctionId::DemocracyCancelReferendum as u8).using_encoded(|s| v.extend(s)); + data.using_encoded(|s| v.extend(s)); + } + _ => { unimplemented!() } + } + + v + } +} + +/// Public functions that can be dispatched to. +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +#[repr(u8)] +enum FunctionId { + TimestampSet = 0x00, + + SessionSetKey = 0x10, + + StakingStake = 0x20, + StakingUnstake = 0x21, + StakingTransfer = 0x22, + + CouncilVotePropose = 0x30, + CouncilVoteVote = 0x31, + CouncilVoteVeto = 0x32, + + CouncilSetApprovals = 0x40, + CouncilReapInactiveVoter = 0x41, + CouncilRetractVoter = 0x42, + CouncilSubmitCandidacy = 0x43, + CouncilPresentWinner = 0x44, + + DemocracyPropose = 0x50, + DemocracySecond = 0x51, + DemocracyVote = 0x52, +} + +impl FunctionId { + /// Derive `Some` value from a `u8`, or `None` if it's invalid. + fn from_u8(value: u8) -> Option { + use self::*; + let functions = [FunctionId::StakingStake, FunctionId::StakingUnstake, + FunctionId::StakingTransfer, FunctionId::SessionSetKey, FunctionId::TimestampSet, + FunctionId::CouncilVotePropose, FunctionId::CouncilVoteVote, FunctionId::CouncilVoteVeto, + FunctionId::CouncilSetApprovals, FunctionId::CouncilReapInactiveVoter, + FunctionId::CouncilRetractVoter, FunctionId::CouncilSubmitCandidacy, + FunctionId::CouncilPresentWinner, FunctionId::DemocracyPropose, + FunctionId::DemocracySecond, FunctionId::DemocracyVote, + ]; + functions.iter().map(|&f| f).find(|&f| value == f as u8) + } +} + +/// Functions on the runtime. +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +#[allow(missing_docs)] +pub enum Function { + TimestampSet(u64), + + SessionSetKey(SessionKey), + + StakingStake, + StakingUnstake, + StakingTransfer(AccountId, u64), + + CouncilVotePropose(Proposal), + CouncilVoteVote([u8; 32], bool), + CouncilVoteVeto([u8; 32]), + + CouncilSetApprovals(Vec, u32), + CouncilReapInactiveVoter(u32, AccountId, u32, u32), + CouncilRetractVoter(u32), + CouncilSubmitCandidacy(u32), + CouncilPresentWinner(AccountId, u64, u32), + + DemocracyPropose(Proposal, u64), + DemocracySecond(u32), + DemocracyVote(u32, bool), +} + +impl Slicable for Function { + fn decode(input: &mut I) -> Option { + let id = u8::decode(input).and_then(FunctionId::from_u8)?; + Some(match id { + FunctionId::TimestampSet => + Function::TimestampSet(Slicable::decode(input)?), + FunctionId::SessionSetKey => + Function::SessionSetKey(Slicable::decode(input)?), + FunctionId::StakingStake => Function::StakingStake, + FunctionId::StakingUnstake => Function::StakingUnstake, + FunctionId::StakingTransfer => { + let to = Slicable::decode(input)?; + let amount = Slicable::decode(input)?; + Function::StakingTransfer(to, amount) + } + FunctionId::CouncilVotePropose => Function::CouncilVotePropose(Slicable::decode(input)?), + FunctionId::CouncilVoteVote => { + let a = Slicable::decode(input)?; + let b = Slicable::decode(input)?; + Function::CouncilVoteVote(a, b) + } + FunctionId::CouncilVoteVeto => Function::CouncilVoteVeto(Slicable::decode(input)?), + FunctionId::CouncilSetApprovals => { + let a = Slicable::decode(input)?; + let b = Slicable::decode(input)?; + Function::CouncilSetApprovals(a, b) + } + FunctionId::CouncilReapInactiveVoter => { + let a = Slicable::decode(input)?; + let b = Slicable::decode(input)?; + let c = Slicable::decode(input)?; + let d = Slicable::decode(input)?; + Function::CouncilReapInactiveVoter(a, b, c, d) + } + FunctionId::CouncilRetractVoter => Function::CouncilRetractVoter(Slicable::decode(input)?), + FunctionId::CouncilSubmitCandidacy => Function::CouncilSubmitCandidacy(Slicable::decode(input)?), + FunctionId::CouncilPresentWinner => { + let a = Slicable::decode(input)?; + let b = Slicable::decode(input)?; + let c = Slicable::decode(input)?; + Function::CouncilPresentWinner(a, b, c) + } + FunctionId::DemocracyPropose => { + let a = Slicable::decode(input)?; + let b = Slicable::decode(input)?; + Function::DemocracyPropose(a, b) + } + FunctionId::DemocracySecond => Function::DemocracySecond(Slicable::decode(input)?), + FunctionId::DemocracyVote => { + let a = Slicable::decode(input)?; + let b = Slicable::decode(input)?; + Function::DemocracyVote(a, b) + } + }) + } + + fn encode(&self) -> Vec { + let mut v = Vec::new(); + match *self { + Function::TimestampSet(ref data) => { + (FunctionId::TimestampSet as u8).using_encoded(|s| v.extend(s)); + data.using_encoded(|s| v.extend(s)); + } + Function::SessionSetKey(ref data) => { + (FunctionId::SessionSetKey as u8).using_encoded(|s| v.extend(s)); + data.using_encoded(|s| v.extend(s)); + } + Function::StakingStake => { + (FunctionId::StakingStake as u8).using_encoded(|s| v.extend(s)); + } + Function::StakingUnstake => { + (FunctionId::StakingUnstake as u8).using_encoded(|s| v.extend(s)); + } + Function::StakingTransfer(ref to, ref amount) => { + (FunctionId::StakingTransfer as u8).using_encoded(|s| v.extend(s)); + to.using_encoded(|s| v.extend(s)); + amount.using_encoded(|s| v.extend(s)); + } + _ => { unimplemented!() } + } + + v + } + + fn using_encoded R>(&self, f: F) -> R { + f(self.encode().as_slice()) + } +} + +/// 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: super::AccountId, + /// The number of transactions have come before from the same signer. + pub nonce: super::TxOrder, + /// The function that should be called. + pub function: Function, +} + +impl Slicable for Transaction { + fn decode(input: &mut I) -> Option { + Some(Transaction { + signed: try_opt!(Slicable::decode(input)), + nonce: try_opt!(Slicable::decode(input)), + function: try_opt!(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: super::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 = try_opt!(Slicable::decode(input)); + + Some(UncheckedTransaction { + transaction: try_opt!(Slicable::decode(input)), + signature: try_opt!(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) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use primitives; + use ::codec::Slicable; + use primitives::hexdisplay::HexDisplay; + + #[test] + fn serialize_unchecked() { + let tx = UncheckedTransaction { + transaction: Transaction { + signed: [1; 32], + nonce: 999u64, + function: Function::TimestampSet(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/Cargo.toml b/demo/runtime/Cargo.toml new file mode 100644 index 0000000000000..bcd3f5dcf00ba --- /dev/null +++ b/demo/runtime/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "demo-runtime" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +rustc-hex = "1.0" +hex-literal = "0.1.0" +log = { version = "0.3", optional = true } +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" } +demo-primitives = { path = "../primitives" } +integer-sqrt = "0.1.0" + +[features] +default = ["std"] +std = [ + "substrate-codec/std", + "substrate-runtime-std/std", + "substrate-runtime-io/std", + "substrate-runtime-support/std", + "substrate-primitives/std", + "demo-primitives/std", + "log" +] diff --git a/demo/runtime/src/api.rs b/demo/runtime/src/api.rs new file mode 100644 index 0000000000000..dd20c1d18014e --- /dev/null +++ b/demo/runtime/src/api.rs @@ -0,0 +1,26 @@ +// 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), + execute_transaction => |(header, utx)| system::internal::execute_transaction(utx, header), + finalise_block => |header| system::internal::finalise_block(header), + validator_count => |()| session::validator_count(), + validators => |()| session::validators(), + authorities => |()| consensus::authorities() +); diff --git a/demo/runtime/src/dispatch.rs b/demo/runtime/src/dispatch.rs new file mode 100644 index 0000000000000..ee5c03d9520fe --- /dev/null +++ b/demo/runtime/src/dispatch.rs @@ -0,0 +1,94 @@ +// 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 demo_primitives::{Function, Proposal, AccountId}; +use runtime::{staking, system, session, democracy, council, council_vote, timestamp}; + +/// Dispatch a proposal. +pub fn proposal(proposal: Proposal) { + match proposal { + Proposal::SystemSetCode(ref a) => + system::privileged::set_code(a), + Proposal::SessionSetLength(a) => + session::privileged::set_length(a), + Proposal::SessionForceNewSession => + session::privileged::force_new_session(), + Proposal::StakingSetSessionsPerEra(a) => + staking::privileged::set_sessions_per_era(a), + Proposal::StakingSetBondingDuration(a) => + staking::privileged::set_bonding_duration(a), + Proposal::StakingSetValidatorCount(a) => + staking::privileged::set_validator_count(a), + Proposal::StakingForceNewEra => + staking::privileged::force_new_era(), + Proposal::DemocracyCancelReferendum(a) => + democracy::privileged::cancel_referendum(a), + Proposal::DemocracyStartReferendum(a, b) => + democracy::privileged::start_referendum(*a, b), + Proposal::CouncilSetDesiredSeats(a) => + council::privileged::set_desired_seats(a), + Proposal::CouncilRemoveMember(a) => + council::privileged::remove_member(&a), + Proposal::CouncilSetPresentationDuration(a) => + council::privileged::set_presentation_duration(a), + Proposal::CouncilSetTermDuration(a) => + council::privileged::set_term_duration(a), + Proposal::CouncilVoteSetCooloffPeriod(a) => + council_vote::privileged::set_cooloff_period(a), + Proposal::CouncilVoteSetVotingPeriod(a) => + council_vote::privileged::set_voting_period(a), + } +} + +/// Dispatch a function. +pub fn function(function: &Function, transactor: &AccountId) { + match *function { + Function::StakingStake => + staking::public::stake(transactor), + Function::StakingUnstake => + staking::public::unstake(transactor), + Function::StakingTransfer(dest, value) => + staking::public::transfer(transactor, &dest, value), + Function::SessionSetKey(session) => + session::public::set_key(transactor, &session), + Function::TimestampSet(t) => + timestamp::public::set(t), + Function::CouncilVotePropose(ref a) => + council_vote::public::propose(transactor, a), + Function::CouncilVoteVote(ref a, b) => + council_vote::public::vote(transactor, a, b), + Function::CouncilVoteVeto(ref a) => + council_vote::public::veto(transactor, a), + Function::CouncilSetApprovals(ref a, b) => + council::public::set_approvals(transactor, a, b), + Function::CouncilReapInactiveVoter(a, ref b, c, d) => + council::public::reap_inactive_voter(transactor, a, b, c, d), + Function::CouncilRetractVoter(a) => + council::public::retract_voter(transactor, a), + Function::CouncilSubmitCandidacy(a) => + council::public::submit_candidacy(transactor, a), + Function::CouncilPresentWinner(ref a, b, c) => + council::public::present_winner(transactor, a, b, c), + Function::DemocracyPropose(ref a, b) => + democracy::public::propose(transactor, a, b), + Function::DemocracySecond(a) => + democracy::public::second(transactor, a), + Function::DemocracyVote(a, b) => + democracy::public::vote(transactor, a, b), + } +} diff --git a/demo/runtime/src/environment.rs b/demo/runtime/src/environment.rs new file mode 100644 index 0000000000000..a5849d91d1a9d --- /dev/null +++ b/demo/runtime/src/environment.rs @@ -0,0 +1,82 @@ +// 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 . + +//! Environment API: Allows certain information to be accessed throughout the runtime. + +use rstd::boxed::Box; +use rstd::mem; +use rstd::cell::RefCell; +use rstd::rc::Rc; + +use demo_primitives::{BlockNumber, Digest, Hash}; + +#[derive(Default)] +/// The information that can be accessed globally. +pub struct Environment { + /// The current block number. + pub block_number: BlockNumber, + /// The current block's parent hash. + pub parent_hash: Hash, + /// The current block digest. + pub digest: Digest, +} + +/// Do something with the environment and return its value. Keep the function short. +pub fn with_env T>(f: F) -> T { + let e = env(); + let mut eb = e.borrow_mut(); + f(&mut *eb) +} + +#[cfg(target_arch = "wasm32")] +fn env() -> Rc> { + // Initialize it to a null value + static mut SINGLETON: *const Rc> = 0 as *const Rc>; + + unsafe { + if SINGLETON == 0 as *const Rc> { + // Make it + let singleton: Rc> = Rc::new(RefCell::new(Default::default())); + + // Put it in the heap so it can outlive this call + SINGLETON = mem::transmute(Box::new(singleton)); + } + + // Now we give out a copy of the data that is safe to use concurrently. + (*SINGLETON).clone() + } +} + +#[cfg(not(target_arch = "wasm32"))] +fn env() -> Rc> { + // Initialize it to a null value + thread_local!{ + static SINGLETON: RefCell<*const Rc>> = RefCell::new(0 as *const Rc>); + } + + SINGLETON.with(|s| unsafe { + if *s.borrow() == 0 as *const Rc> { + // Make it + let singleton: Rc> = Rc::new(RefCell::new(Default::default())); + + // Put it in the heap so it can outlive this call + *s.borrow_mut() = mem::transmute(Box::new(singleton)); + } + + // Now we give out a copy of the data that is safe to use concurrently. + (**s.borrow()).clone() + }) +} diff --git a/demo/runtime/src/genesismap.rs b/demo/runtime/src/genesismap.rs new file mode 100644 index 0000000000000..c3b7da1b7d8c2 --- /dev/null +++ b/demo/runtime/src/genesismap.rs @@ -0,0 +1,132 @@ +// 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; +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::SESSION_LENGTH[..], vec![].and(&self.session_length)), + (&session::VALIDATOR_COUNT[..], vec![].and(&(self.validators.len() as u32))), + + (&staking::INTENTION_COUNT[..], vec![].and(&0u32)), + (&staking::SESSIONS_PER_ERA[..], vec![].and(&self.sessions_per_era)), + (&staking::CURRENT_ERA[..], vec![].and(&0u64)), + + (&democracy::LAUNCH_PERIOD[..], vec![].and(&self.launch_period)), + (&democracy::VOTING_PERIOD[..], vec![].and(&self.voting_period)), + (&democracy::MINIMUM_DEPOSIT[..], vec![].and(&self.minimum_deposit)), + + (&council::CANDIDACY_BOND[..], vec![].and(&self.candidacy_bond)), + (&council::VOTING_BOND[..], vec![].and(&self.voter_bond)), + (&council::PRESENT_SLASH_PER_VOTER[..], vec![].and(&self.present_slash_per_voter)), + (&council::CARRY_COUNT[..], vec![].and(&self.carry_count)), + (&council::PRESENTATION_DURATION[..], vec![].and(&self.presentation_duration)), + (&council::VOTING_PERIOD[..], vec![].and(&self.council_election_voting_period)), + (&council::TERM_DURATION[..], vec![].and(&self.council_term_duration)), + (&council::DESIRED_SEATS[..], vec![].and(&self.desired_seats)), + (&council::INACTIVE_GRACE_PERIOD[..], vec![].and(&self.inactive_grace_period)), + + (&council_vote::COOLOFF_PERIOD[..], vec![].and(&self.cooloff_period)), + (&council_vote::VOTING_PERIOD[..], vec![].and(&self.council_proposal_voting_period)) + ].into_iter() + .map(|(k, v)| (k.into(), v)) + .chain(self.validators.iter() + .enumerate() + .map(|(i, account)| ((i as u32).to_keyed_vec(session::VALIDATOR_AT), vec![].and(account))) + ).chain(self.balances.iter() + .map(|&(account, balance)| (account.to_keyed_vec(staking::BALANCE_OF), vec![].and(&balance))) + ) + .map(|(k, v)| (twox_128(&k[..])[..].to_vec(), v.to_vec())) + .chain(vec![ + (system::CODE[..].into(), 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![ + twox_128(&0u64.to_keyed_vec(system::BLOCK_HASH_AT)).to_vec() => genesis_block.header.blake2_256().encode() + ] +} diff --git a/demo/runtime/src/lib.rs b/demo/runtime/src/lib.rs new file mode 100644 index 0000000000000..a351da4230daa --- /dev/null +++ b/demo/runtime/src/lib.rs @@ -0,0 +1,80 @@ +// 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. This can be compiled with #[no_std], ready for Wasm. + +#![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; +extern crate substrate_runtime_support as runtime_support; +#[cfg(any(feature = "std", test))] extern crate substrate_keyring as keyring; + +#[cfg(feature = "std")] extern crate rustc_hex; + +extern crate substrate_codec as codec; +#[cfg(feature = "std")] #[macro_use] extern crate substrate_primitives as primitives; +extern crate demo_primitives; + +#[cfg(test)] #[macro_use] extern crate hex_literal; + +extern crate integer_sqrt; + +pub mod environment; +pub mod runtime; +pub mod api; +pub mod dispatch; + +#[cfg(feature = "std")] pub mod genesismap; + +/// Type definitions and helpers for transactions. +pub mod transaction { + use rstd::ops; + use demo_primitives::Signature; + pub use demo_primitives::{Transaction, UncheckedTransaction}; + + /// 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 + } + } + + 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) + } + } +} diff --git a/demo/runtime/src/runtime/consensus.rs b/demo/runtime/src/runtime/consensus.rs new file mode 100644 index 0000000000000..8d631d0bb8e9c --- /dev/null +++ b/demo/runtime/src/runtime/consensus.rs @@ -0,0 +1,51 @@ +// 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 new file mode 100644 index 0000000000000..fc93748ac1bcd --- /dev/null +++ b/demo/runtime/src/runtime/council.rs @@ -0,0 +1,1378 @@ +// 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::storage; +use demo_primitives::{Proposal, AccountId, Hash, BlockNumber}; +use runtime::{staking, system, session}; +use runtime::staking::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; + +// parameters +pub const CANDIDACY_BOND: &[u8] = b"cou:cbo"; +pub const VOTING_BOND: &[u8] = b"cou:vbo"; +pub const PRESENT_SLASH_PER_VOTER: &[u8] = b"cou:pss"; +pub const CARRY_COUNT: &[u8] = b"cou:cco"; +pub const PRESENTATION_DURATION: &[u8] = b"cou:pdu"; +pub const INACTIVE_GRACE_PERIOD: &[u8] = b"cou:vgp"; +pub const VOTING_PERIOD: &[u8] = b"cou:per"; +pub const TERM_DURATION: &[u8] = b"cou:trm"; +pub const DESIRED_SEATS: &[u8] = b"cou:sts"; + +// permanent state (always relevant, changes only at the finalisation of voting) +pub const ACTIVE_COUNCIL: &[u8] = b"cou:act"; // Vec<(AccountId, expiry: BlockNumber)> +pub const VOTE_COUNT: &[u8] = b"cou:vco"; // VoteIndex + +// persistent state (always relevant, changes constantly) +pub const APPROVALS_OF: &[u8] = b"cou:apr:"; // Vec +pub const REGISTER_INFO_OF: &[u8] = b"cou:reg:"; // Candidate -> (VoteIndex, u32) +pub const LAST_ACTIVE_OF: &[u8] = b"cou:lac:"; // Voter -> VoteIndex +pub const VOTERS: &[u8] = b"cou:vrs"; // Vec +pub const CANDIDATES: &[u8] = b"cou:can"; // Vec, has holes +pub const CANDIDATE_COUNT: &[u8] = b"cou:cnc"; // u32 + +// temporary state (only relevant during finalisation/presentation) +pub const NEXT_FINALISE: &[u8] = b"cou:nxt"; +pub const SNAPSHOTED_STAKES: &[u8] = b"cou:sss"; // Vec +pub const LEADERBOARD: &[u8] = b"cou:win"; // Vec<(Balance, AccountId)> ORDERED low -> high + +/// How much should be locked up in order to submit one's candidacy. +pub fn candidacy_bond() -> Balance { + storage::get(CANDIDACY_BOND) + .expect("all core parameters of council module must be in place") +} + +/// How much should be locked up in order to be able to submit votes. +pub fn voting_bond() -> Balance { + storage::get(VOTING_BOND) + .expect("all core parameters of council module must be in place") +} + +/// How long to give each top candidate to present themselves after the vote ends. +pub fn presentation_duration() -> BlockNumber { + storage::get(PRESENTATION_DURATION) + .expect("all core parameters of council module must be in place") +} + +/// How often (in blocks) to check for new votes. +pub fn voting_period() -> BlockNumber { + storage::get(VOTING_PERIOD) + .expect("all core parameters of council module must be in place") +} + +/// How many votes need to go by after a voter's last vote before they can be reaped if their +/// approvals are moot. +pub fn inactivity_grace_period() -> VoteIndex { + storage::get(INACTIVE_GRACE_PERIOD) + .expect("all core parameters of council module must be in place") +} + +/// How long each position is active for. +pub fn term_duration() -> BlockNumber { + storage::get(TERM_DURATION) + .expect("all core parameters of council module must be in place") +} + +/// The punishment, per voter, if you provide an invalid presentation. +pub fn present_slash_per_voter() -> Balance { + storage::get(PRESENT_SLASH_PER_VOTER) + .expect("all core parameters of council module must be in place") +} + +/// Number of accounts that should be sitting on the council. +pub fn desired_seats() -> u32 { + storage::get(DESIRED_SEATS) + .expect("all core parameters of council module must be in place") +} + +/// How many runners-up should have their approvals persist until the next vote. +pub fn carry_count() -> u32 { + storage::get(CARRY_COUNT) + .expect("all core parameters of council module must be in place") +} + +/// True if we're currently in a presentation period. +pub fn presentation_active() -> bool { + storage::exists(NEXT_FINALISE) +} + +/// The current council. When there's a vote going on, this should still be used for executive +/// matters. +pub fn active_council() -> Vec<(AccountId, BlockNumber)> { + storage::get_or_default(ACTIVE_COUNCIL) +} + +/// If `who` a candidate at the moment? +pub fn is_a_candidate(who: &AccountId) -> bool { + storage::exists(&who.to_keyed_vec(REGISTER_INFO_OF)) +} + +/// 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) + } +} + +/// The accounts holding the seats that will become free on the next tally. +pub fn next_finalise() -> Option<(BlockNumber, u32, Vec)> { + storage::get(NEXT_FINALISE) +} + +/// The total number of votes that have happened or are in progress. +pub fn vote_index() -> VoteIndex { + storage::get_or_default(VOTE_COUNT) +} + +/// The present candidate list. +pub fn candidates() -> Vec { + storage::get_or_default(CANDIDATES) +} + +/// The present voter list. +pub fn voters() -> Vec { + storage::get_or_default(VOTERS) +} + +/// The vote index and list slot that the candidate `who` was registered or `None` if they are not +/// currently registered. +pub fn candidate_reg_info(who: &AccountId) -> Option<(VoteIndex, u32)> { + storage::get(&who.to_keyed_vec(REGISTER_INFO_OF)) +} + +/// The last cleared vote index that this voter was last active at. +pub fn voter_last_active(voter: &AccountId) -> Option { + storage::get(&voter.to_keyed_vec(LAST_ACTIVE_OF)) +} + +/// The last cleared vote index that this voter was last active at. +pub fn approvals_of(voter: &AccountId) -> Vec { + storage::get_or_default(&voter.to_keyed_vec(APPROVALS_OF)) +} + +/// Get the leaderboard if we;re in the presentation phase. +pub fn leaderboard() -> Option> { + storage::get(LEADERBOARD) +} + +pub mod public { + use super::*; + + /// Set candidate approvals. Approval slots stay valid as long as candidates in those slots + /// are registered. + pub fn set_approvals(signed: &AccountId, votes: &Vec, index: VoteIndex) { + assert!(!presentation_active()); + assert_eq!(index, vote_index()); + if !storage::exists(&signed.to_keyed_vec(LAST_ACTIVE_OF)) { + // not yet a voter - deduct bond. + staking::internal::reserve_balance(signed, voting_bond()); + storage::put(VOTERS, &{ + let mut v: Vec = storage::get_or_default(VOTERS); + v.push(signed.clone()); + v + }); + } + storage::put(&signed.to_keyed_vec(APPROVALS_OF), votes); + storage::put(&signed.to_keyed_vec(LAST_ACTIVE_OF), &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`. + pub fn reap_inactive_voter(signed: &AccountId, signed_index: u32, who: &AccountId, who_index: u32, assumed_vote_index: VoteIndex) { + assert!(!presentation_active(), "cannot reap during presentation period"); + assert!(voter_last_active(signed).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] == *signed, "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 { signed }, + if valid { who_index } else { signed_index }, + voters + ); + if valid { + staking::internal::transfer_reserved_balance(who, signed, voting_bond()); + } else { + staking::internal::slash_reserved(signed, voting_bond()); + } + } + + /// Remove a voter. All votes are cancelled and the voter deposit is returned. + pub fn retract_voter(signed: &AccountId, index: u32) { + assert!(!presentation_active(), "cannot retract when presenting"); + assert!(storage::exists(&signed.to_keyed_vec(LAST_ACTIVE_OF)), "cannot retract non-voter"); + let voters = voters(); + let index = index as usize; + assert!(index < voters.len(), "retraction index invalid"); + assert!(voters[index] == *signed, "retraction index mismatch"); + remove_voter(signed, index, voters); + staking::internal::unreserve_balance(signed, voting_bond()); + } + + /// Submit oneself for candidacy. + /// + /// Account must have enough transferrable funds in it to pay the bond. + pub fn submit_candidacy(signed: &AccountId, slot: u32) { + assert!(!is_a_candidate(signed), "duplicate candidate submission"); + assert!(staking::internal::deduct_unbonded(signed, candidacy_bond()), "candidate has not enough funds"); + + let slot = slot as usize; + let count = storage::get_or_default::(CANDIDATE_COUNT) as usize; + let candidates: Vec = storage::get_or_default(CANDIDATES); + 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(signed.clone()); + } else { + candidates[slot] = signed.clone(); + } + storage::put(CANDIDATES, &candidates); + storage::put(CANDIDATE_COUNT, &(count as u32 + 1)); + storage::put(&signed.to_keyed_vec(REGISTER_INFO_OF), &(vote_index(), slot)); + } + + /// 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 + pub fn present_winner(signed: &AccountId, candidate: &AccountId, total: Balance, index: VoteIndex) { + assert_eq!(index, vote_index(), "index not current"); + let (_, _, expiring): (BlockNumber, u32, Vec) = storage::get(NEXT_FINALISE) + .expect("cannot present outside of presentation period"); + let stakes: Vec = storage::get_or_default(SNAPSHOTED_STAKES); + let voters: Vec = storage::get_or_default(VOTERS); + let bad_presentation_punishment = present_slash_per_voter() * voters.len() as Balance; + assert!(staking::can_slash(signed, 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) = + storage::get(&candidate.to_keyed_vec(REGISTER_INFO_OF)).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); + storage::put(LEADERBOARD, &leaderboard); + } else { + staking::internal::slash(signed, bad_presentation_punishment); + } + } +} + +pub mod privileged { + use super::*; + + /// 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. + pub fn set_desired_seats(count: u32) { + storage::put(DESIRED_SEATS, &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. + pub fn remove_member(who: &AccountId) { + let new_council: Vec<(AccountId, BlockNumber)> = active_council() + .into_iter() + .filter(|i| i.0 != *who) + .collect(); + storage::put(ACTIVE_COUNCIL, &new_council); + } + + /// Set the presentation duration. If there is current a vote being presented for, will + /// invoke `finalise_vote`. + pub fn set_presentation_duration(count: BlockNumber) { + storage::put(PRESENTATION_DURATION, &count); + } + + /// Set the presentation duration. If there is current a vote being presented for, will + /// invoke `finalise_vote`. + pub fn set_term_duration(count: BlockNumber) { + storage::put(TERM_DURATION, &count); + } +} + +pub mod internal { + use super::*; + use demo_primitives::Proposal; + + /// 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) { + storage::put(VOTERS, &{ voters.swap_remove(index); voters }); + storage::kill(&voter.to_keyed_vec(APPROVALS_OF)); + storage::kill(&voter.to_keyed_vec(LAST_ACTIVE_OF)); +} + +/// 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).cloned().collect::>(); + if active_council.len() - expiring.len() < desired_seats { + let empty_seats = desired_seats - (active_council.len() - expiring.len()); + storage::put(NEXT_FINALISE, &(number + presentation_duration(), empty_seats as u32, expiring)); + + let voters: Vec = storage::get_or_default(VOTERS); + let votes: Vec = voters.iter().map(staking::balance).collect(); + storage::put(SNAPSHOTED_STAKES, &votes); + + // initialise leaderboard. + let leaderboard_size = empty_seats + carry_count() as usize; + storage::put(LEADERBOARD, &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() { + storage::kill(SNAPSHOTED_STAKES); + let (_, coming, expiring): (BlockNumber, u32, Vec) = storage::take(NEXT_FINALISE) + .expect("finalise can only be called after a tally is started."); + let leaderboard: Vec<(Balance, AccountId)> = storage::take(LEADERBOARD).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); + storage::put(ACTIVE_COUNCIL, &new_council); + + // clear all except runners-up from candidate list. + let candidates: Vec = storage::get_or_default(CANDIDATES); + 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 + storage::kill(&old.to_keyed_vec(REGISTER_INFO_OF)); + } + } + // discard any superfluous slots. + if let Some(last_index) = new_candidates.iter().rposition(|c| *c != AccountId::default()) { + new_candidates.truncate(last_index + 1); + } + storage::put(CANDIDATES, &(new_candidates)); + storage::put(CANDIDATE_COUNT, &count); + storage::put(VOTE_COUNT, &(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(CANDIDACY_BOND).to_vec() => vec![].and(&9u64), + twox_128(VOTING_BOND).to_vec() => vec![].and(&3u64), + twox_128(PRESENT_SLASH_PER_VOTER).to_vec() => vec![].and(&1u64), + twox_128(CARRY_COUNT).to_vec() => vec![].and(&2u32), + twox_128(PRESENTATION_DURATION).to_vec() => vec![].and(&2u64), + twox_128(VOTING_PERIOD).to_vec() => vec![].and(&4u64), + twox_128(TERM_DURATION).to_vec() => vec![].and(&5u64), + twox_128(DESIRED_SEATS).to_vec() => vec![].and(&2u32), + twox_128(INACTIVE_GRACE_PERIOD).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 environment::with_env; + use demo_primitives::{AccountId, Proposal}; + use runtime::{staking, session, democracy}; + + fn new_test_ext() -> TestExternalities { + testing::externalities() + } + + #[test] + fn basic_environment_works() { + let mut t = new_test_ext(); + with_externalities(&mut t, || { + with_env(|e| e.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(), || { + with_env(|e| e.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); + + public::submit_candidacy(&Alice, 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); + + public::submit_candidacy(&Bob, 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).to_vec(), vec![].and(&vec![AccountId::default(), AccountId::default(), Alice.to_raw_public()])); + t.insert(twox_128(CANDIDATE_COUNT).to_vec(), vec![].and(&1u32)); + t.insert(twox_128(&Alice.to_raw_public().to_keyed_vec(REGISTER_INFO_OF)).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, || { + with_env(|e| e.block_number = 1); + assert_eq!(candidates(), vec![AccountId::default(), AccountId::default(), Alice.to_raw_public()]); + + public::submit_candidacy(&Bob, 1); + assert_eq!(candidates(), vec![AccountId::default(), Bob.into(), Alice.to_raw_public()]); + + public::submit_candidacy(&Charlie, 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, || { + with_env(|e| e.block_number = 1); + assert_eq!(candidates(), vec![AccountId::default(), AccountId::default(), Alice.into()]); + + public::submit_candidacy(&Bob, 0); + assert_eq!(candidates(), vec![Bob.into(), AccountId::default(), Alice.into()]); + + public::submit_candidacy(&Charlie, 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, || { + with_env(|e| e.block_number = 1); + public::submit_candidacy(&Dave, 3); + }); + } + + #[test] + #[should_panic(expected = "invalid candidate slot")] + fn bad_candidate_slot_submission_should_panic() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + assert_eq!(candidates(), Vec::::new()); + public::submit_candidacy(&Alice, 1); + }); + } + + #[test] + #[should_panic(expected = "invalid candidate slot")] + fn non_free_candidate_slot_submission_should_panic() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + assert_eq!(candidates(), Vec::::new()); + public::submit_candidacy(&Alice, 0); + public::submit_candidacy(&Bob, 0); + }); + } + + #[test] + #[should_panic(expected = "duplicate candidate submission")] + fn dupe_candidate_submission_should_panic() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + assert_eq!(candidates(), Vec::::new()); + public::submit_candidacy(&Alice, 0); + public::submit_candidacy(&Alice, 1); + }); + } + + #[test] + #[should_panic(expected = "candidate has not enough funds")] + fn poor_candidate_submission_should_panic() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + assert_eq!(candidates(), Vec::::new()); + public::submit_candidacy(&One, 0); + }); + } + + #[test] + fn voting_should_work() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + + public::submit_candidacy(&Eve, 0); + + public::set_approvals(&Alice, &vec![true], 0); + public::set_approvals(&Dave, &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()]); + + public::submit_candidacy(&Bob, 1); + public::submit_candidacy(&Charlie, 2); + + public::set_approvals(&Bob, &vec![false, true, true], 0); + public::set_approvals(&Charlie, &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(), || { + with_env(|e| e.block_number = 1); + + public::submit_candidacy(&Eve, 0); + public::set_approvals(&Dave, &vec![true], 0); + + assert_eq!(approvals_of(&Dave), vec![true]); + + public::submit_candidacy(&Bob, 1); + public::submit_candidacy(&Charlie, 2); + public::set_approvals(&Dave, &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(), || { + with_env(|e| e.block_number = 1); + + public::submit_candidacy(&Eve, 0); + public::submit_candidacy(&Bob, 1); + public::submit_candidacy(&Charlie, 2); + + public::set_approvals(&Alice, &vec![true], 0); + public::set_approvals(&Bob, &vec![false, true, true], 0); + public::set_approvals(&Charlie, &vec![false, true, true], 0); + public::set_approvals(&Dave, &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]); + + public::retract_voter(&Alice, 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]); + + public::retract_voter(&Bob, 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]); + + public::retract_voter(&Charlie, 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(), || { + with_env(|e| e.block_number = 1); + public::submit_candidacy(&Charlie, 0); + public::set_approvals(&Alice, &vec![true], 0); + public::set_approvals(&Bob, &vec![true], 0); + public::retract_voter(&Alice, 1); + }); + } + + #[test] + #[should_panic(expected = "retraction index invalid")] + fn overflow_retraction_index_should_panic() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + public::submit_candidacy(&Charlie, 0); + public::set_approvals(&Alice, &vec![true], 0); + public::retract_voter(&Alice, 1); + }); + } + + #[test] + #[should_panic(expected = "cannot retract non-voter")] + fn non_voter_retraction_should_panic() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + public::submit_candidacy(&Charlie, 0); + public::set_approvals(&Alice, &vec![true], 0); + public::retract_voter(&Bob, 0); + }); + } + + #[test] + fn simple_tally_should_work() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 4); + assert!(!presentation_active()); + + public::submit_candidacy(&Bob, 0); + public::submit_candidacy(&Eve, 1); + public::set_approvals(&Bob, &vec![true, false], 0); + public::set_approvals(&Eve, &vec![false, true], 0); + internal::end_block(); + + with_env(|e| e.block_number = 6); + assert!(presentation_active()); + public::present_winner(&Dave, &Bob, 11, 0); + public::present_winner(&Dave, &Eve, 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)); + + with_env(|e| e.block_number = 4); + public::submit_candidacy(&Bob, 0); + public::submit_candidacy(&Eve, 1); + public::set_approvals(&Bob, &vec![true, false], 0); + public::set_approvals(&Eve, &vec![false, true], 0); + internal::end_block(); + + with_env(|e| e.block_number = 6); + public::present_winner(&Dave, &Bob, 11, 0); + public::present_winner(&Dave, &Eve, 41, 0); + public::present_winner(&Dave, &Eve, 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(), || { + with_env(|e| e.block_number = 4); + public::submit_candidacy(&Bob, 0); + public::set_approvals(&Bob, &vec![true], 0); + internal::end_block(); + + with_env(|e| e.block_number = 6); + public::present_winner(&Dave, &Bob, 11, 0); + internal::end_block(); + + with_env(|e| e.block_number = 8); + public::submit_candidacy(&Eve, 0); + public::set_approvals(&Eve, &vec![true], 1); + internal::end_block(); + + with_env(|e| e.block_number = 10); + public::present_winner(&Dave, &Eve, 41, 1); + internal::end_block(); + + public::reap_inactive_voter( + &Eve, voters().iter().position(|&i| i == *Eve).unwrap() as u32, + &Bob, 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(), || { + with_env(|e| e.block_number = 4); + public::submit_candidacy(&Bob, 0); + public::set_approvals(&Bob, &vec![true], 0); + internal::end_block(); + + with_env(|e| e.block_number = 6); + public::present_winner(&Dave, &Bob, 11, 0); + internal::end_block(); + + with_env(|e| e.block_number = 8); + public::submit_candidacy(&Bob, 0); + public::set_approvals(&Bob, &vec![true], 1); + internal::end_block(); + + with_env(|e| e.block_number = 10); + public::present_winner(&Dave, &Bob, 11, 1); + }); + } + + #[test] + fn retracting_inactive_voter_with_other_candidates_in_slots_should_work() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 4); + public::submit_candidacy(&Bob, 0); + public::set_approvals(&Bob, &vec![true], 0); + internal::end_block(); + + with_env(|e| e.block_number = 6); + public::present_winner(&Dave, &Bob, 11, 0); + internal::end_block(); + + with_env(|e| e.block_number = 8); + public::submit_candidacy(&Eve, 0); + public::set_approvals(&Eve, &vec![true], 1); + internal::end_block(); + + with_env(|e| e.block_number = 10); + public::present_winner(&Dave, &Eve, 41, 1); + internal::end_block(); + + with_env(|e| e.block_number = 11); + public::submit_candidacy(&Alice, 0); + + public::reap_inactive_voter( + &Eve, voters().iter().position(|&i| i == *Eve).unwrap() as u32, + &Bob, 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(), || { + with_env(|e| e.block_number = 4); + public::submit_candidacy(&Bob, 0); + public::set_approvals(&Bob, &vec![true], 0); + internal::end_block(); + + with_env(|e| e.block_number = 6); + public::present_winner(&Dave, &Bob, 8, 0); + internal::end_block(); + + with_env(|e| e.block_number = 8); + public::submit_candidacy(&Eve, 0); + public::set_approvals(&Eve, &vec![true], 1); + internal::end_block(); + + with_env(|e| e.block_number = 10); + public::present_winner(&Dave, &Eve, 38, 1); + internal::end_block(); + + public::reap_inactive_voter( + &Bob, 42, + &Bob, 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(), || { + with_env(|e| e.block_number = 4); + public::submit_candidacy(&Bob, 0); + public::set_approvals(&Bob, &vec![true], 0); + internal::end_block(); + + with_env(|e| e.block_number = 6); + public::present_winner(&Dave, &Bob, 8, 0); + internal::end_block(); + + with_env(|e| e.block_number = 8); + public::submit_candidacy(&Eve, 0); + public::set_approvals(&Eve, &vec![true], 1); + internal::end_block(); + + with_env(|e| e.block_number = 10); + public::present_winner(&Dave, &Eve, 38, 1); + internal::end_block(); + + public::reap_inactive_voter( + &Bob, voters().iter().position(|&i| i == *Bob).unwrap() as u32, + &Bob, 42, + 2 + ); + }); + } + + #[test] + fn attempting_to_retract_active_voter_should_slash_reporter() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 4); + public::submit_candidacy(&Bob, 0); + public::submit_candidacy(&Charlie, 1); + public::submit_candidacy(&Dave, 2); + public::submit_candidacy(&Eve, 3); + public::set_approvals(&Bob, &vec![true, false, false, false], 0); + public::set_approvals(&Charlie, &vec![false, true, false, false], 0); + public::set_approvals(&Dave, &vec![false, false, true, false], 0); + public::set_approvals(&Eve, &vec![false, false, false, true], 0); + internal::end_block(); + + with_env(|e| e.block_number = 6); + public::present_winner(&Dave, &Bob, 11, 0); + public::present_winner(&Dave, &Charlie, 21, 0); + public::present_winner(&Dave, &Dave, 31, 0); + public::present_winner(&Dave, &Eve, 41, 0); + internal::end_block(); + + with_env(|e| e.block_number = 8); + privileged::set_desired_seats(3); + internal::end_block(); + + with_env(|e| e.block_number = 10); + public::present_winner(&Dave, &Bob, 11, 1); + public::present_winner(&Dave, &Charlie, 21, 1); + internal::end_block(); + + public::reap_inactive_voter( + &Dave, voters().iter().position(|&i| i == *Dave).unwrap() as u32, + &Bob, 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(), || { + with_env(|e| e.block_number = 4); + public::submit_candidacy(&Bob, 0); + public::set_approvals(&Bob, &vec![true], 0); + internal::end_block(); + + with_env(|e| e.block_number = 6); + public::present_winner(&Dave, &Bob, 11, 0); + internal::end_block(); + + with_env(|e| e.block_number = 8); + public::submit_candidacy(&Eve, 0); + public::set_approvals(&Eve, &vec![true], 1); + internal::end_block(); + + with_env(|e| e.block_number = 10); + public::present_winner(&Dave, &Eve, 41, 1); + internal::end_block(); + + public::reap_inactive_voter( + &Dave, 0, + &Bob, 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(), || { + with_env(|e| e.block_number = 4); + public::submit_candidacy(&Alice, 0); + public::set_approvals(&Ferdie, &vec![true], 0); + public::submit_candidacy(&Bob, 1); + public::set_approvals(&Bob, &vec![false, true], 0); + public::submit_candidacy(&Charlie, 2); + public::set_approvals(&Charlie, &vec![false, false, true], 0); + public::submit_candidacy(&Dave, 3); + public::set_approvals(&Dave, &vec![false, false, false, true], 0); + public::submit_candidacy(&Eve, 4); + public::set_approvals(&Eve, &vec![false, false, false, false, true], 0); + internal::end_block(); + + with_env(|e| e.block_number = 6); + public::present_winner(&Dave, &Alice, 60, 0); + public::present_winner(&Dave, &Charlie, 21, 0); + public::present_winner(&Dave, &Dave, 31, 0); + public::present_winner(&Dave, &Eve, 41, 0); + public::present_winner(&Dave, &Bob, 11, 0); + }); + } + + #[test] + fn presenting_loser_first_should_not_matter() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 4); + public::submit_candidacy(&Alice, 0); + public::set_approvals(&Ferdie, &vec![true], 0); + public::submit_candidacy(&Bob, 1); + public::set_approvals(&Bob, &vec![false, true], 0); + public::submit_candidacy(&Charlie, 2); + public::set_approvals(&Charlie, &vec![false, false, true], 0); + public::submit_candidacy(&Dave, 3); + public::set_approvals(&Dave, &vec![false, false, false, true], 0); + public::submit_candidacy(&Eve, 4); + public::set_approvals(&Eve, &vec![false, false, false, false, true], 0); + internal::end_block(); + + with_env(|e| e.block_number = 6); + public::present_winner(&Dave, &Bob, 11, 0); + public::present_winner(&Dave, &Alice, 60, 0); + public::present_winner(&Dave, &Charlie, 21, 0); + public::present_winner(&Dave, &Dave, 31, 0); + public::present_winner(&Dave, &Eve, 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(), || { + with_env(|e| e.block_number = 4); + assert!(!presentation_active()); + public::present_winner(&Eve, &Eve, 1, 0); + }); + } + + #[test] + #[should_panic(expected = "index not current")] + fn present_panics_with_invalid_vote_index() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 4); + public::submit_candidacy(&Bob, 0); + public::submit_candidacy(&Eve, 1); + public::set_approvals(&Bob, &vec![true, false], 0); + public::set_approvals(&Eve, &vec![false, true], 0); + internal::end_block(); + + with_env(|e| e.block_number = 6); + public::present_winner(&Dave, &Bob, 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(), || { + with_env(|e| e.block_number = 4); + assert!(!presentation_active()); + + public::submit_candidacy(&Alice, 0); + public::submit_candidacy(&Eve, 1); + public::set_approvals(&Bob, &vec![true, false], 0); + public::set_approvals(&Eve, &vec![false, true], 0); + internal::end_block(); + + with_env(|e| e.block_number = 6); + assert_eq!(staking::balance(&Alice), 1); + public::present_winner(&Alice, &Alice, 30, 0); + }); + } + + #[test] + fn invalid_present_tally_should_slash() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 4); + assert!(!presentation_active()); + assert_eq!(staking::balance(&Dave), 40); + + public::submit_candidacy(&Bob, 0); + public::submit_candidacy(&Eve, 1); + public::set_approvals(&Bob, &vec![true, false], 0); + public::set_approvals(&Eve, &vec![false, true], 0); + internal::end_block(); + + with_env(|e| e.block_number = 6); + public::present_winner(&Dave, &Bob, 80, 0); + + assert_eq!(staking::balance(&Dave), 38); + }); + } + + #[test] + fn runners_up_should_be_kept() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 4); + assert!(!presentation_active()); + + public::submit_candidacy(&Alice, 0); + public::set_approvals(&Ferdie, &vec![true], 0); + public::submit_candidacy(&Bob, 1); + public::set_approvals(&Bob, &vec![false, true], 0); + public::submit_candidacy(&Charlie, 2); + public::set_approvals(&Charlie, &vec![false, false, true], 0); + public::submit_candidacy(&Dave, 3); + public::set_approvals(&Dave, &vec![false, false, false, true], 0); + public::submit_candidacy(&Eve, 4); + public::set_approvals(&Eve, &vec![false, false, false, false, true], 0); + + internal::end_block(); + + with_env(|e| e.block_number = 6); + assert!(presentation_active()); + public::present_winner(&Dave, &Alice, 60, 0); + assert_eq!(leaderboard(), Some(vec![ + (0, AccountId::default()), + (0, AccountId::default()), + (0, AccountId::default()), + (60, Alice.to_raw_public()) + ])); + public::present_winner(&Dave, &Charlie, 21, 0); + public::present_winner(&Dave, &Dave, 31, 0); + public::present_winner(&Dave, &Eve, 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(), || { + with_env(|e| e.block_number = 4); + public::submit_candidacy(&Alice, 0); + public::set_approvals(&Ferdie, &vec![true], 0); + public::submit_candidacy(&Bob, 1); + public::set_approvals(&Bob, &vec![false, true], 0); + public::submit_candidacy(&Charlie, 2); + public::set_approvals(&Charlie, &vec![false, false, true], 0); + public::submit_candidacy(&Dave, 3); + public::set_approvals(&Dave, &vec![false, false, false, true], 0); + public::submit_candidacy(&Eve, 4); + public::set_approvals(&Eve, &vec![false, false, false, false, true], 0); + internal::end_block(); + + with_env(|e| e.block_number = 6); + public::present_winner(&Dave, &Alice, 60, 0); + public::present_winner(&Dave, &Charlie, 21, 0); + public::present_winner(&Dave, &Dave, 31, 0); + public::present_winner(&Dave, &Eve, 41, 0); + internal::end_block(); + + with_env(|e| e.block_number = 8); + public::set_approvals(&Ferdie, &vec![false, false, true, false], 1); + privileged::set_desired_seats(3); + internal::end_block(); + + with_env(|e| e.block_number = 10); + public::present_winner(&Dave, &Charlie, 81, 1); + public::present_winner(&Dave, &Dave, 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 new file mode 100644 index 0000000000000..9887d14cae293 --- /dev/null +++ b/demo/runtime/src/runtime/council_vote.rs @@ -0,0 +1,485 @@ +// 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 codec::{KeyedVec, Slicable, Input, NonTrivialSlicable}; +use runtime_support::Hashable; +use runtime_support::storage; +use demo_primitives::{Proposal, AccountId, Hash, BlockNumber}; +use runtime::{system, democracy, council}; +use runtime::staking::Balance; + +type ProposalHash = [u8; 32]; + +pub const COOLOFF_PERIOD: &[u8] = b"cov:cooloff"; // BlockNumber +pub const VOTING_PERIOD: &[u8] = b"cov:period"; // BlockNumber + +pub const PROPOSALS: &[u8] = b"cov:prs"; // Vec<(expiry: BlockNumber, ProposalHash)> ordered by expiry. +pub const PROPOSAL_OF: &[u8] = b"cov:pro"; // ProposalHash -> Proposal +pub const PROPOSAL_VOTERS: &[u8] = b"cov:voters:"; // ProposalHash -> Vec +pub const COUNCIL_VOTE_OF: &[u8] = b"cov:vote:"; // (ProposalHash, AccountId) -> bool +pub const VETOED_PROPOSAL: &[u8] = b"cov:veto:"; // ProposalHash -> (BlockNumber, sorted_vetoers: Vec) + +pub fn cooloff_period() -> BlockNumber { + storage::get(COOLOFF_PERIOD).expect("all parameters must be defined") +} + +pub fn voting_period() -> BlockNumber { + storage::get(VOTING_PERIOD).expect("all parameters must be defined") +} + +pub fn proposals() -> Vec<(BlockNumber, ProposalHash)> { + storage::get_or_default(PROPOSALS) +} + +pub fn is_vetoed(proposal: &ProposalHash) -> bool { + storage::get(&proposal.to_keyed_vec(VETOED_PROPOSAL)) + .map(|(expiry, _): (BlockNumber, Vec)| system::block_number() < expiry) + .unwrap_or(false) +} + +pub fn veto_of(proposal: &ProposalHash) -> Option<(BlockNumber, Vec)> { + storage::get(&proposal.to_keyed_vec(VETOED_PROPOSAL)) +} + +fn set_veto_of(proposal: &ProposalHash, expiry: BlockNumber, vetoers: Vec) { + storage::put(&proposal.to_keyed_vec(VETOED_PROPOSAL), &(expiry, vetoers)) +} + +fn kill_veto_of(proposal: &ProposalHash) { + storage::kill(&proposal.to_keyed_vec(VETOED_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 vote_of(who: &AccountId, proposal: &ProposalHash) -> Option { + storage::get(&(*proposal, *who).to_keyed_vec(COUNCIL_VOTE_OF)) +} + +pub fn proposal_voters(proposal: &ProposalHash) -> Vec { + storage::get_or_default(&proposal.to_keyed_vec(PROPOSAL_VOTERS)) +} + +pub fn tally(proposal_hash: &ProposalHash) -> (u32, u32, u32) { + generic_tally(proposal_hash, |w: &AccountId, p: &ProposalHash| storage::get(&(*p, *w).to_keyed_vec(COUNCIL_VOTE_OF))) +} + +fn take_tally(proposal_hash: &ProposalHash) -> (u32, u32, u32) { + generic_tally(proposal_hash, |w: &AccountId, p: &ProposalHash| storage::get(&(*p, *w).to_keyed_vec(COUNCIL_VOTE_OF))) +} + +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)>) { + storage::put(PROPOSALS, 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 = storage::take(&hash.to_keyed_vec(PROPOSAL_OF)).expect("all queued proposal hashes must have associated proposals"); + Some((proposal, hash)) + } + _ => None, + } +} + +pub mod public { + use super::*; + + pub fn propose(signed: &AccountId, proposal: &Proposal) { + let expiry = system::block_number() + voting_period(); + assert!(will_still_be_councillor_at(signed, 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); + + storage::put(&proposal_hash.to_keyed_vec(PROPOSAL_OF), proposal); + storage::put(&proposal_hash.to_keyed_vec(PROPOSAL_VOTERS), &vec![*signed]); + storage::put(&(proposal_hash, *signed).to_keyed_vec(COUNCIL_VOTE_OF), &true); + } + + pub fn vote(signed: &AccountId, proposal: &ProposalHash, approve: bool) { + if vote_of(signed, proposal).is_none() { + let mut voters = proposal_voters(proposal); + voters.push(*signed); + storage::put(&proposal.to_keyed_vec(PROPOSAL_VOTERS), &voters); + } + storage::put(&(*proposal, *signed).to_keyed_vec(COUNCIL_VOTE_OF), &approve); + } + + pub fn veto(signed: &AccountId, proposal_hash: &ProposalHash) { + assert!(is_councillor(signed), "only councillors may veto council proposals"); + assert!(storage::exists(&proposal_hash.to_keyed_vec(PROPOSAL_VOTERS)), "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(signed) + .expect_err("a councillor may not veto a proposal twice"); + existing_vetoers.insert(insert_position, *signed); + set_veto_of(&proposal_hash, system::block_number() + cooloff_period(), existing_vetoers); + + set_proposals(&proposals().into_iter().filter(|&(_, h)| h != *proposal_hash).collect::>()); + storage::kill(&proposal_hash.to_keyed_vec(PROPOSAL_VOTERS)); + storage::kill(&proposal_hash.to_keyed_vec(PROPOSAL_OF)); + for (c, _) in council::active_council() { + storage::kill(&(*proposal_hash, c).to_keyed_vec(COUNCIL_VOTE_OF)); + } + } +} + +pub mod privileged { + use super::*; + + pub fn set_cooloff_period(blocks: BlockNumber) { + storage::put(COOLOFF_PERIOD, &blocks); + } + + pub fn set_voting_period(blocks: BlockNumber) { + storage::put(VOTING_PERIOD, &blocks); + } +} + +pub mod internal { + use super::*; + use runtime::democracy::privileged::start_referendum; + use demo_primitives::VoteThreshold; + + 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::DemocracyCancelReferendum(ref_index) = &proposal { + if let (_, 0, 0) = tally { + democracy::privileged::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::ACTIVE_COUNCIL).to_vec() => vec![].and(&vec![ + (Alice.to_raw_public(), expiry), + (Bob.into(), expiry), + (Charlie.into(), expiry) + ]), + twox_128(COOLOFF_PERIOD).to_vec() => vec![].and(&2u64), + twox_128(VOTING_PERIOD).to_vec() => vec![].and(&1u64), + twox_128(democracy::VOTING_PERIOD).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 environment::with_env; + use demo_primitives::{AccountId, Proposal, VoteThreshold}; + use runtime::{staking, council, democracy}; + + fn new_test_ext() -> TestExternalities { + testing::externalities() + } + + #[test] + fn basic_environment_works() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.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)); + }); + } + + #[test] + fn referendum_cancellation_should_work_when_unanimous() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + let proposal = Proposal::StakingSetBondingDuration(42); + democracy::privileged::start_referendum(proposal.clone(), VoteThreshold::SuperMajorityApprove); + assert_eq!(democracy::active_referendums(), vec![(0, 4, proposal, VoteThreshold::SuperMajorityApprove)]); + + let cancellation = Proposal::DemocracyCancelReferendum(0); + let hash = cancellation.blake2_256(); + public::propose(&Alice, &cancellation); + public::vote(&Bob, &hash, true); + public::vote(&Charlie, &hash, true); + assert_eq!(proposals(), vec![(2, hash)]); + internal::end_block(1); + + with_env(|e| e.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(), || { + with_env(|e| e.block_number = 1); + let proposal = Proposal::StakingSetBondingDuration(42); + democracy::privileged::start_referendum(proposal.clone(), VoteThreshold::SuperMajorityApprove); + + let cancellation = Proposal::DemocracyCancelReferendum(0); + let hash = cancellation.blake2_256(); + public::propose(&Alice, &cancellation); + public::vote(&Bob, &hash, true); + public::vote(&Charlie, &hash, false); + internal::end_block(1); + + with_env(|e| e.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(), || { + with_env(|e| e.block_number = 1); + let proposal = Proposal::StakingSetBondingDuration(42); + democracy::privileged::start_referendum(proposal.clone(), VoteThreshold::SuperMajorityApprove); + + let cancellation = Proposal::DemocracyCancelReferendum(0); + let hash = cancellation.blake2_256(); + public::propose(&Alice, &cancellation); + public::vote(&Bob, &hash, true); + internal::end_block(1); + + with_env(|e| e.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(), || { + with_env(|e| e.block_number = 1); + let proposal = Proposal::StakingSetBondingDuration(42); + let hash = proposal.blake2_256(); + public::propose(&Alice, &proposal); + public::veto(&Bob, &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(), || { + with_env(|e| e.block_number = 1); + let proposal = Proposal::StakingSetBondingDuration(42); + let hash = proposal.blake2_256(); + public::propose(&Alice, &proposal); + public::veto(&Bob, &hash); + + with_env(|e| e.block_number = 3); + public::propose(&Alice, &proposal); + public::veto(&Bob, &hash); + }); + } + + #[test] + #[should_panic] + fn retry_in_cooloff_should_panic() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + let proposal = Proposal::StakingSetBondingDuration(42); + let hash = proposal.blake2_256(); + public::propose(&Alice, &proposal); + public::veto(&Bob, &hash); + + with_env(|e| e.block_number = 2); + public::propose(&Alice, &proposal); + }); + } + + #[test] + fn retry_after_cooloff_should_work() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + let proposal = Proposal::StakingSetBondingDuration(42); + let hash = proposal.blake2_256(); + public::propose(&Alice, &proposal); + public::veto(&Bob, &hash); + + with_env(|e| e.block_number = 3); + public::propose(&Alice, &proposal); + public::vote(&Bob, &hash, false); + public::vote(&Charlie, &hash, true); + internal::end_block(3); + + with_env(|e| e.block_number = 4); + internal::end_block(4); + assert_eq!(proposals().len(), 0); + assert_eq!(democracy::active_referendums(), vec![(0, 7, Proposal::StakingSetBondingDuration(42), VoteThreshold::SimpleMajority)]); + }); + } + + #[test] + fn alternative_double_veto_should_work() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + let proposal = Proposal::StakingSetBondingDuration(42); + let hash = proposal.blake2_256(); + public::propose(&Alice, &proposal); + public::veto(&Bob, &hash); + + with_env(|e| e.block_number = 3); + public::propose(&Alice, &proposal); + public::veto(&Charlie, &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(), || { + with_env(|e| e.block_number = 1); + let proposal = Proposal::StakingSetBondingDuration(42); + let hash = proposal.blake2_256(); + public::propose(&Alice, &proposal); + assert_eq!(proposals().len(), 1); + assert_eq!(proposal_voters(&hash), vec![Alice.to_raw_public()]); + assert_eq!(vote_of(&Alice, &hash), Some(true)); + assert_eq!(tally(&hash), (1, 0, 2)); + }); + } + + #[test] + fn unvoted_proposal_should_expire_without_action() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + public::propose(&Alice, &Proposal::StakingSetBondingDuration(42)); + assert_eq!(tally(&Proposal::StakingSetBondingDuration(42).blake2_256()), (1, 0, 2)); + internal::end_block(1); + + with_env(|e| e.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(), || { + with_env(|e| e.block_number = 1); + public::propose(&Alice, &Proposal::StakingSetBondingDuration(42)); + public::vote(&Bob, &Proposal::StakingSetBondingDuration(42).blake2_256(), true); + public::vote(&Charlie, &Proposal::StakingSetBondingDuration(42).blake2_256(), true); + assert_eq!(tally(&Proposal::StakingSetBondingDuration(42).blake2_256()), (3, 0, 0)); + internal::end_block(1); + + with_env(|e| e.block_number = 2); + internal::end_block(2); + assert_eq!(proposals().len(), 0); + assert_eq!(democracy::active_referendums(), vec![(0, 5, Proposal::StakingSetBondingDuration(42), VoteThreshold::SuperMajorityAgainst)]); + }); + } + + #[test] + fn majority_proposal_should_expire_with_unbiased_referendum() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + public::propose(&Alice, &Proposal::StakingSetBondingDuration(42)); + public::vote(&Bob, &Proposal::StakingSetBondingDuration(42).blake2_256(), true); + public::vote(&Charlie, &Proposal::StakingSetBondingDuration(42).blake2_256(), false); + assert_eq!(tally(&Proposal::StakingSetBondingDuration(42).blake2_256()), (2, 1, 0)); + internal::end_block(1); + + with_env(|e| e.block_number = 2); + internal::end_block(2); + assert_eq!(proposals().len(), 0); + assert_eq!(democracy::active_referendums(), vec![(0, 5, Proposal::StakingSetBondingDuration(42), VoteThreshold::SimpleMajority)]); + }); + } + + #[test] + #[should_panic] + fn propose_by_public_should_panic() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + public::propose(&Dave, &Proposal::StakingSetBondingDuration(42)); + }); + } +} diff --git a/demo/runtime/src/runtime/democracy.rs b/demo/runtime/src/runtime/democracy.rs new file mode 100644 index 0000000000000..227362818862f --- /dev/null +++ b/demo/runtime/src/runtime/democracy.rs @@ -0,0 +1,579 @@ +// 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::storage; +use demo_primitives::{Proposal, AccountId, Hash, BlockNumber, VoteThreshold}; +use runtime::{staking, system, session}; +use runtime::staking::Balance; + +pub type PropIndex = u32; +pub type ReferendumIndex = u32; + +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, + } + } +} + +// public proposals +pub const PUBLIC_PROP_COUNT: &[u8] = b"dem:ppc"; // PropIndex +pub const PUBLIC_PROPS: &[u8] = b"dem:pub"; // Vec<(PropIndex, Proposal)> +pub const DEPOSIT_OF: &[u8] = b"dem:dep:"; // PropIndex -> (Balance, Vec) +pub const LAUNCH_PERIOD: &[u8] = b"dem:lau"; // BlockNumber +pub const MINIMUM_DEPOSIT: &[u8] = b"dem:min"; // Balance + +// referenda +pub const VOTING_PERIOD: &[u8] = b"dem:per"; // BlockNumber +pub const REFERENDUM_COUNT: &[u8] = b"dem:rco"; // ReferendumIndex +pub const NEXT_TALLY: &[u8] = b"dem:nxt"; // ReferendumIndex +pub const REFERENDUM_INFO_OF: &[u8] = b"dem:pro:"; // ReferendumIndex -> (BlockNumber, Proposal, VoteThreshold) +pub const VOTERS_FOR: &[u8] = b"dem:vtr:"; // ReferendumIndex -> Vec +pub const VOTE_OF: &[u8] = b"dem:vot:"; // (ReferendumIndex, AccountId) -> bool + +/// The minimum amount to be used as a deposit for a public referendum proposal. +pub fn minimum_deposit() -> Balance { + storage::get(MINIMUM_DEPOSIT) + .expect("all core parameters of council module must be in place") +} + +/// How often (in blocks) to check for new votes. +pub fn voting_period() -> BlockNumber { + storage::get(VOTING_PERIOD) + .expect("all core parameters of council module must be in place") +} + +/// How often (in blocks) new public referenda are launched. +pub fn launch_period() -> BlockNumber { + storage::get(LAUNCH_PERIOD) + .expect("all core parameters of council module must be in place") +} + +/// The public proposals. Unsorted. +pub fn public_props() -> Vec<(PropIndex, Proposal, AccountId)> { + storage::get_or_default(PUBLIC_PROPS) +} + +/// Those who have locked a deposit. +pub fn deposit_lockers(proposal: PropIndex) -> Option<(Balance, Vec)> { + storage::get(&proposal.to_keyed_vec(DEPOSIT_OF)) +} + +/// 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 { + storage::exists(&ref_index.to_keyed_vec(REFERENDUM_INFO_OF)) +} + +/// Get the voters for the current proposal. +pub fn voters_for(ref_index: ReferendumIndex) -> Vec { + storage::get_or_default(&ref_index.to_keyed_vec(VOTERS_FOR)) +} + +/// Get the vote, if Some, of `who`. +pub fn vote_of(who: &AccountId, ref_index: ReferendumIndex) -> Option { + storage::get(&(*who, ref_index).to_keyed_vec(VOTE_OF)) +} + +/// Get the info concerning the next referendum. +pub fn referendum_info(ref_index: ReferendumIndex) -> Option<(BlockNumber, Proposal, VoteThreshold)> { + storage::get(&ref_index.to_keyed_vec(REFERENDUM_INFO_OF)) +} + +/// Get all referendums currently active. +pub fn active_referendums() -> Vec<(ReferendumIndex, BlockNumber, Proposal, VoteThreshold)> { + let next: ReferendumIndex = storage::get_or_default(NEXT_TALLY); + let last: ReferendumIndex = storage::get_or_default(REFERENDUM_COUNT); + (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: ReferendumIndex = storage::get_or_default(NEXT_TALLY); + let last: ReferendumIndex = storage::get_or_default(REFERENDUM_COUNT); + (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(a, ref_index).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)) +} + +/// Get the next free referendum index, aka the number of referendums started so far. +pub fn next_free_ref_index() -> ReferendumIndex { + storage::get_or_default(REFERENDUM_COUNT) +} + +pub mod public { + use super::*; + + /// Propose a sensitive action to be taken. + pub fn propose(signed: &AccountId, proposal: &Proposal, value: Balance) { + assert!(value >= minimum_deposit()); + assert!(staking::internal::deduct_unbonded(signed, value)); + + let index: PropIndex = storage::get_or_default(PUBLIC_PROP_COUNT); + storage::put(PUBLIC_PROP_COUNT, &(index + 1)); + storage::put(&index.to_keyed_vec(DEPOSIT_OF), &(value, vec![*signed])); + + let mut props = public_props(); + props.push((index, proposal.clone(), *signed)); + storage::put(PUBLIC_PROPS, &props); + } + + /// Propose a sensitive action to be taken. + pub fn second(signed: &AccountId, proposal: PropIndex) { + let key = proposal.to_keyed_vec(DEPOSIT_OF); + let mut deposit: (Balance, Vec) = + storage::get(&key).expect("can only second an existing proposal"); + assert!(staking::internal::deduct_unbonded(signed, deposit.0)); + + deposit.1.push(*signed); + storage::put(&key, &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.. + pub fn vote(signed: &AccountId, ref_index: ReferendumIndex, approve_proposal: bool) { + if !is_active_referendum(ref_index) { + panic!("vote given for invalid referendum.") + } + if staking::balance(signed) == 0 { + panic!("transactor must have balance to signal approval."); + } + let key = (*signed, ref_index).to_keyed_vec(VOTE_OF); + if !storage::exists(&key) { + let mut voters = voters_for(ref_index); + voters.push(signed.clone()); + storage::put(&ref_index.to_keyed_vec(VOTERS_FOR), &voters); + } + storage::put(&key, &approve_proposal); + } +} + +pub mod privileged { + use super::*; + + /// 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); + } +} + +pub mod internal { + use super::*; + use demo_primitives::Proposal; + use dispatch; + + /// 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) = + storage::take(&prop_index.to_keyed_vec(DEPOSIT_OF)) + .expect("depositors always exist for current proposals"); + // refund depositors + for d in &depositors { + staking::internal::refund(d, deposit); + } + storage::put(PUBLIC_PROPS, &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) { + dispatch::proposal(proposal); + } + storage::put(NEXT_TALLY, &(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"); + } + + storage::put(REFERENDUM_COUNT, &(ref_index + 1)); + storage::put(&ref_index.to_keyed_vec(REFERENDUM_INFO_OF), &(end, proposal, vote_threshold)); + ref_index +} + +/// Remove all info on a referendum. +fn clear_referendum(ref_index: ReferendumIndex) { + storage::kill(&ref_index.to_keyed_vec(REFERENDUM_INFO_OF)); + storage::kill(&ref_index.to_keyed_vec(VOTERS_FOR)); + for v in voters_for(ref_index) { + storage::kill(&(v, ref_index).to_keyed_vec(VOTE_OF)); + } +} + +#[cfg(test)] +pub mod testing { + use super::*; + use runtime_io::{twox_128, TestExternalities}; + use codec::Joiner; + use keyring::Keyring::*; + use runtime::{session, staking}; + + pub fn externalities() -> TestExternalities { + map![ + twox_128(session::SESSION_LENGTH).to_vec() => vec![].and(&1u64), + twox_128(session::VALIDATOR_COUNT).to_vec() => vec![].and(&3u32), + twox_128(&0u32.to_keyed_vec(session::VALIDATOR_AT)).to_vec() => Alice.to_raw_public_vec(), + twox_128(&1u32.to_keyed_vec(session::VALIDATOR_AT)).to_vec() => Bob.to_raw_public_vec(), + twox_128(&2u32.to_keyed_vec(session::VALIDATOR_AT)).to_vec() => Charlie.to_raw_public_vec(), + twox_128(staking::INTENTION_COUNT).to_vec() => vec![].and(&3u32), + twox_128(&0u32.to_keyed_vec(staking::INTENTION_AT)).to_vec() => Alice.to_raw_public_vec(), + twox_128(&1u32.to_keyed_vec(staking::INTENTION_AT)).to_vec() => Bob.to_raw_public_vec(), + twox_128(&2u32.to_keyed_vec(staking::INTENTION_AT)).to_vec() => Charlie.to_raw_public_vec(), + twox_128(&Alice.to_raw_public().to_keyed_vec(staking::BALANCE_OF)).to_vec() => vec![].and(&10u64), + twox_128(&Bob.to_raw_public().to_keyed_vec(staking::BALANCE_OF)).to_vec() => vec![].and(&20u64), + twox_128(&Charlie.to_raw_public().to_keyed_vec(staking::BALANCE_OF)).to_vec() => vec![].and(&30u64), + twox_128(&Dave.to_raw_public().to_keyed_vec(staking::BALANCE_OF)).to_vec() => vec![].and(&40u64), + twox_128(&Eve.to_raw_public().to_keyed_vec(staking::BALANCE_OF)).to_vec() => vec![].and(&50u64), + twox_128(&Ferdie.to_raw_public().to_keyed_vec(staking::BALANCE_OF)).to_vec() => vec![].and(&60u64), + twox_128(&One.to_raw_public().to_keyed_vec(staking::BALANCE_OF)).to_vec() => vec![].and(&1u64), + twox_128(staking::TOTAL_STAKE).to_vec() => vec![].and(&210u64), + twox_128(staking::SESSIONS_PER_ERA).to_vec() => vec![].and(&1u64), + twox_128(staking::VALIDATOR_COUNT).to_vec() => vec![].and(&3u64), + twox_128(staking::CURRENT_ERA).to_vec() => vec![].and(&1u64), + + twox_128(LAUNCH_PERIOD).to_vec() => vec![].and(&1u64), + twox_128(VOTING_PERIOD).to_vec() => vec![].and(&1u64), + twox_128(MINIMUM_DEPOSIT).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 environment::with_env; + use demo_primitives::{AccountId, Proposal}; + 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 + + #[test] + fn locked_for_should_work() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + public::propose(&Alice, &Proposal::StakingSetSessionsPerEra(2), 2u64); + public::propose(&Alice, &Proposal::StakingSetSessionsPerEra(4), 4u64); + public::propose(&Alice, &Proposal::StakingSetSessionsPerEra(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(), || { + with_env(|e| e.block_number = 1); + public::propose(&Alice, &Proposal::StakingSetSessionsPerEra(2), 1u64); + democracy::internal::end_block(system::block_number()); + + with_env(|e| e.block_number = 2); + let r = 0; + public::vote(&Alice, r, true); + + assert_eq!(next_free_ref_index(), 1); + assert_eq!(voters_for(r), vec![Alice.to_raw_public()]); + assert_eq!(vote_of(&Alice, r), 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(), || { + with_env(|e| e.block_number = 1); + public::propose(&Alice, &Proposal::StakingSetSessionsPerEra(2), 5u64); + public::second(&Bob, 0); + public::second(&Eve, 0); + public::second(&Eve, 0); + public::second(&Eve, 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(), || { + with_env(|e| e.block_number = 1); + public::propose(&Alice, &Proposal::StakingSetSessionsPerEra(2), 5u64); + public::second(&Bob, 0); + public::second(&Eve, 0); + public::second(&Eve, 0); + public::second(&Eve, 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(), || { + with_env(|e| e.block_number = 1); + public::propose(&Alice, &Proposal::StakingSetSessionsPerEra(2), 0u64); + }); + } + + #[test] + #[should_panic] + fn poor_proposer_should_panic() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + public::propose(&Alice, &Proposal::StakingSetSessionsPerEra(2), 11u64); + }); + } + + #[test] + #[should_panic] + fn poor_seconder_should_panic() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + public::propose(&Bob, &Proposal::StakingSetSessionsPerEra(2), 11u64); + public::second(&Alice, 0); + }); + } + + #[test] + fn runners_up_should_come_after() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 0); + public::propose(&Alice, &Proposal::StakingSetBondingDuration(2), 2u64); + public::propose(&Alice, &Proposal::StakingSetBondingDuration(4), 4u64); + public::propose(&Alice, &Proposal::StakingSetBondingDuration(3), 3u64); + democracy::internal::end_block(system::block_number()); + + with_env(|e| e.block_number = 1); + public::vote(&Alice, 0, true); + democracy::internal::end_block(system::block_number()); + staking::internal::check_new_era(); + assert_eq!(staking::bonding_duration(), 4u64); + + with_env(|e| e.block_number = 2); + public::vote(&Alice, 1, true); + democracy::internal::end_block(system::block_number()); + staking::internal::check_new_era(); + assert_eq!(staking::bonding_duration(), 3u64); + + with_env(|e| e.block_number = 3); + public::vote(&Alice, 2, true); + democracy::internal::end_block(system::block_number()); + staking::internal::check_new_era(); + assert_eq!(staking::bonding_duration(), 2u64); + }); + } + + #[test] + fn simple_passing_should_work() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + let r = inject_referendum(1, Proposal::StakingSetSessionsPerEra(2), VoteThreshold::SuperMajorityApprove); + public::vote(&Alice, r, true); + + assert_eq!(voters_for(r), vec![Alice.to_raw_public()]); + assert_eq!(vote_of(&Alice, r), 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(), || { + with_env(|e| e.block_number = 1); + let r = inject_referendum(1, Proposal::StakingSetSessionsPerEra(2), VoteThreshold::SuperMajorityApprove); + public::vote(&Alice, r, true); + privileged::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(), || { + with_env(|e| e.block_number = 1); + let r = inject_referendum(1, Proposal::StakingSetSessionsPerEra(2), VoteThreshold::SuperMajorityApprove); + public::vote(&Alice, r, false); + + assert_eq!(voters_for(r), vec![Alice.to_raw_public()]); + assert_eq!(vote_of(&Alice, r), 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(), || { + with_env(|e| e.block_number = 1); + let r = inject_referendum(1, Proposal::StakingSetSessionsPerEra(2), VoteThreshold::SuperMajorityApprove); + public::vote(&Alice, r, true); + public::vote(&Bob, r, false); + public::vote(&Charlie, r, false); + public::vote(&Dave, r, true); + public::vote(&Eve, r, false); + public::vote(&Ferdie, 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(), || { + with_env(|e| e.block_number = 1); + let r = inject_referendum(1, Proposal::StakingSetSessionsPerEra(2), VoteThreshold::SuperMajorityApprove); + public::vote(&Eve, r, false); + public::vote(&Ferdie, 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); + + with_env(|e| e.block_number = 1); + let r = inject_referendum(1, Proposal::StakingSetSessionsPerEra(2), VoteThreshold::SuperMajorityApprove); + public::vote(&Dave, r, true); + public::vote(&Eve, r, false); + public::vote(&Ferdie, 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 new file mode 100644 index 0000000000000..bba91af86d7a8 --- /dev/null +++ b/demo/runtime/src/runtime/mod.rs @@ -0,0 +1,34 @@ +// 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 new file mode 100644 index 0000000000000..c5c5387dd9b57 --- /dev/null +++ b/demo/runtime/src/runtime/session.rs @@ -0,0 +1,276 @@ +// 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, StorageVec}; +use demo_primitives::{AccountId, SessionKey, BlockNumber}; +use runtime::{system, staking, consensus}; + +pub const SESSION_LENGTH: &[u8] = b"ses:len"; +pub const CURRENT_INDEX: &[u8] = b"ses:ind"; +pub const LAST_LENGTH_CHANGE: &[u8] = b"ses:llc"; +pub const NEXT_KEY_FOR: &[u8] = b"ses:nxt:"; +pub const NEXT_SESSION_LENGTH: &[u8] = b"ses:nln"; +pub const VALIDATOR_AT: &[u8] = b"ses:val:"; +pub const VALIDATOR_COUNT: &[u8] = b"ses:val:len"; + +struct ValidatorStorageVec {} +impl StorageVec for ValidatorStorageVec { + type Item = AccountId; + const PREFIX: &'static[u8] = VALIDATOR_AT; +} + +/// Get the current set of validators. +pub fn validators() -> Vec { + ValidatorStorageVec::items() +} + +/// The number of blocks in each session. +pub fn length() -> BlockNumber { + storage::get_or(SESSION_LENGTH, 0) +} + +/// The number of validators currently. +pub fn validator_count() -> u32 { + ValidatorStorageVec::count() as u32 +} + +/// The current era index. +pub fn current_index() -> BlockNumber { + storage::get_or(CURRENT_INDEX, 0) +} + +/// The block number at which the era length last changed. +pub fn last_length_change() -> BlockNumber { + storage::get_or(LAST_LENGTH_CHANGE, 0) +} + +pub mod public { + use super::*; + + /// Sets the session key of `_validator` to `_key`. This doesn't take effect until the next + /// session. + pub fn set_key(validator: &AccountId, key: &SessionKey) { + // set new value for next session + storage::put(&validator.to_keyed_vec(NEXT_KEY_FOR), key); + } +} + +pub mod privileged { + use super::*; + + /// Set a new era length. Won't kick in until the next era change (at current length). + pub fn set_length(new: BlockNumber) { + storage::put(NEXT_SESSION_LENGTH, &new); + } + + /// Forces a new session. + pub fn force_new_session() { + 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]) { + ValidatorStorageVec::set_items(new); + 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() - last_length_change()) % length() == 0 { + rotate_session(); + } + } +} + +/// Move onto next session: register the new authority set. +fn rotate_session() { + // Increment current session index. + storage::put(CURRENT_INDEX, &(current_index() + 1)); + + // Enact era length change. + if let Some(next_len) = storage::get::(NEXT_SESSION_LENGTH) { + storage::put(SESSION_LENGTH, &next_len); + storage::put(LAST_LENGTH_CHANGE, &system::block_number()); + storage::kill(NEXT_SESSION_LENGTH); + } + + // Update any changes in session keys. + validators().iter().enumerate().for_each(|(i, v)| { + let k = v.to_keyed_vec(NEXT_KEY_FOR); + if let Some(n) = storage::take(&k) { + 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 one = Keyring::One.to_raw_public(); + let two = Keyring::Two.to_raw_public(); + let three = [3u8; 32]; + + let extras: TestExternalities = map![ + twox_128(SESSION_LENGTH).to_vec() => vec![].and(&session_length), + twox_128(VALIDATOR_COUNT).to_vec() => vec![].and(&3u32), + twox_128(&0u32.to_keyed_vec(VALIDATOR_AT)).to_vec() => one.to_vec(), + twox_128(&1u32.to_keyed_vec(VALIDATOR_AT)).to_vec() => two.to_vec(), + twox_128(&2u32.to_keyed_vec(VALIDATOR_AT)).to_vec() => three.to_vec() + ]; + system::testing::externalities().into_iter().chain(extras.into_iter()).collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use super::public::*; + use super::privileged::*; + use super::internal::*; + use runtime_io::{with_externalities, twox_128, TestExternalities}; + use codec::{KeyedVec, Joiner}; + use keyring::Keyring; + use environment::with_env; + use demo_primitives::AccountId; + use runtime::{consensus, session}; + + fn simple_setup() -> TestExternalities { + map![ + twox_128(SESSION_LENGTH).to_vec() => vec![].and(&2u64), + // the validators (10, 20, ...) + twox_128(b"ses:val:len").to_vec() => vec![].and(&2u32), + twox_128(&0u32.to_keyed_vec(ValidatorStorageVec::PREFIX)).to_vec() => vec![10; 32], + twox_128(&1u32.to_keyed_vec(ValidatorStorageVec::PREFIX)).to_vec() => vec![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. + with_env(|e| e.block_number = 1); + set_length(3); + check_rotate_session(); + assert_eq!(length(), 2); + assert_eq!(current_index(), 0); + + // Block 2: Length now changed to 3. Index incremented. + with_env(|e| e.block_number = 2); + set_length(3); + check_rotate_session(); + assert_eq!(length(), 3); + assert_eq!(current_index(), 1); + + // Block 3: Length now changed to 3. Index incremented. + with_env(|e| e.block_number = 3); + check_rotate_session(); + assert_eq!(length(), 3); + assert_eq!(current_index(), 1); + + // Block 4: Change to length 2; no visible change. + with_env(|e| e.block_number = 4); + set_length(2); + check_rotate_session(); + assert_eq!(length(), 3); + assert_eq!(current_index(), 1); + + // Block 5: Length now changed to 2. Index incremented. + with_env(|e| e.block_number = 5); + check_rotate_session(); + assert_eq!(length(), 2); + assert_eq!(current_index(), 2); + + // Block 6: No change. + with_env(|e| e.block_number = 6); + check_rotate_session(); + assert_eq!(length(), 2); + assert_eq!(current_index(), 2); + + // Block 7: Next index. + with_env(|e| e.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 + with_env(|e| e.block_number = 1); + check_rotate_session(); + assert_eq!(consensus::authorities(), vec![[11u8; 32], [21u8; 32]]); + + // Block 2: Session rollover, but no change. + with_env(|e| e.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. + with_env(|e| e.block_number = 3); + set_key(&[20; 32], &[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. + with_env(|e| e.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 new file mode 100644 index 0000000000000..6a30a743142a3 --- /dev/null +++ b/demo/runtime/src/runtime/staking.rs @@ -0,0 +1,878 @@ +// 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::cmp; +use rstd::cell::RefCell; +use rstd::collections::btree_map::{BTreeMap, Entry}; +use runtime_io::{print, blake2_256}; +use codec::KeyedVec; +use runtime_support::{storage, StorageVec}; +use demo_primitives::{BlockNumber, AccountId}; +use runtime::{system, session}; + +/// 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; + +pub const BONDING_DURATION: &[u8] = b"sta:loc"; +pub const VALIDATOR_COUNT: &[u8] = b"sta:vac"; +pub const SESSIONS_PER_ERA: &[u8] = b"sta:spe"; +pub const NEXT_SESSIONS_PER_ERA: &[u8] = b"sta:nse"; +pub const CURRENT_ERA: &[u8] = b"sta:era"; +pub const LAST_ERA_LENGTH_CHANGE: &[u8] = b"sta:lec"; +pub const TOTAL_STAKE: &[u8] = b"sta:tot"; +pub const INTENTION_AT: &[u8] = b"sta:wil:"; +pub const INTENTION_COUNT: &[u8] = b"sta:wil:len"; + +pub const BALANCE_OF: &[u8] = b"sta:bal:"; +pub const RESERVED_BALANCE_OF: &[u8] = b"sta:lbo:"; +pub const BONDAGE_OF: &[u8] = b"sta:bon:"; +pub const CODE_OF: &[u8] = b"sta:cod:"; +pub const STORAGE_OF: &[u8] = b"sta:sto:"; + +pub struct IntentionStorageVec {} +impl StorageVec for IntentionStorageVec { + type Item = AccountId; + const PREFIX: &'static[u8] = INTENTION_AT; +} + +/// The length of the bonding duration in eras. +pub fn bonding_duration() -> BlockNumber { + storage::get_or_default(BONDING_DURATION) +} + +/// The length of a staking era in sessions. +pub fn validator_count() -> usize { + storage::get_or_default::(VALIDATOR_COUNT) as usize +} + +/// The length of a staking era in blocks. +pub fn era_length() -> BlockNumber { + sessions_per_era() * session::length() +} + +/// The length of a staking era in sessions. +pub fn sessions_per_era() -> BlockNumber { + storage::get_or_default(SESSIONS_PER_ERA) +} + +/// The current era index. +pub fn current_era() -> BlockNumber { + storage::get_or_default(CURRENT_ERA) +} + +/// The block number at which the era length last changed. +pub fn last_era_length_change() -> BlockNumber { + storage::get_or_default(LAST_ERA_LENGTH_CHANGE) +} + +/// The balance of a given account. +pub fn balance(who: &AccountId) -> Balance { + free_balance(who) + reserved_balance(who) +} + +/// The balance of a given account. +pub fn free_balance(who: &AccountId) -> Balance { + storage::get_or_default(&who.to_keyed_vec(BALANCE_OF)) +} + +/// 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 fn reserved_balance(who: &AccountId) -> Balance { + storage::get_or_default(&who.to_keyed_vec(RESERVED_BALANCE_OF)) +} + +/// 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 +} + +/// The block at which the `who`'s funds become entirely liquid. +pub fn bondage(who: &AccountId) -> Bondage { + storage::get_or_default(&who.to_keyed_vec(BONDAGE_OF)) +} + +#[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 bondage(who) { + i if i == Bondage::max_value() => LockStatus::Staked, + i if i <= system::block_number() => LockStatus::Liquid, + i => LockStatus::LockedUntil(i), + } +} + +/// The total amount of stake on the system. +pub fn total_stake() -> Balance { + storage::get_or(TOTAL_STAKE, 0) +} + +// Each identity's stake may be in one of three bondage states, given by an integer: +// - n | n <= current_era(): inactive: free to be transferred. +// - ~0: active: currently representing a validator. +// - n | n > current_era(): deactivating: recently representing a validator and not yet +// ready for transfer. + +pub mod public { + use super::*; + + #[derive(Default)] + 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; + + 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, + } + + struct DirectExt; + impl Externalities for DirectExt { + fn get_storage(&self, account: &AccountId, location: &[u8]) -> Option> { + let mut v = account.to_keyed_vec(STORAGE_OF); + v.extend(location); + storage::get_raw(&v) + } + fn get_code(&self, account: &AccountId) -> Vec { + storage::get_raw(&account.to_keyed_vec(CODE_OF)).unwrap_or_default() + } + fn get_balance(&self, account: &AccountId) -> Balance { + storage::get_or_default::(&account.to_keyed_vec(BALANCE_OF)) + } + } + + 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) + } + } + + fn commit_state(s: State) { + for (address, changed) in s.into_iter() { + if let Some(balance) = changed.balance { + storage::put(&address.to_keyed_vec(BALANCE_OF), &balance); + } + if let Some(code) = changed.code { + storage::put(&address.to_keyed_vec(CODE_OF), &code); + } + let storage_key = address.to_keyed_vec(STORAGE_OF); + for (k, v) in changed.storage.into_iter() { + let mut key = storage_key.clone(); + key.extend(k); + if let Some(value) = v { + storage::put_raw(&key, &value); + } else { + storage::kill(&key); + } + } + } + } + + 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); + } + } + } + } + + /// Create a smart-contract account. + pub fn create(transactor: &AccountId, code: &[u8], value: Balance) { + // commit anything that made it this far to storage + if let Some(commit) = effect_create(transactor, code, value, DirectExt) { + commit_state(commit); + } + } + + 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) + } + + /// Transfer some unlocked staking balance to another staker. + /// TODO: probably want to state gas-limit and gas-price. + pub fn transfer(transactor: &AccountId, dest: &AccountId, value: Balance) { + // commit anything that made it this far to storage + if let Some(commit) = effect_transfer(transactor, dest, value, DirectExt) { + commit_state(commit); + } + } + + 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!(bondage(transactor) <= bondage(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 + } + } + + /// Declare the desire to stake for the transactor. + /// + /// Effects will be felt at the beginning of the next era. + pub fn stake(transactor: &AccountId) { + let mut intentions = IntentionStorageVec::items(); + // can't be in the list twice. + assert!(intentions.iter().find(|t| *t == transactor).is_none(), "Cannot stake if already staked."); + intentions.push(transactor.clone()); + IntentionStorageVec::set_items(&intentions); + storage::put(&transactor.to_keyed_vec(BONDAGE_OF), &u64::max_value()); + } + + /// Retract the desire to stake for the transactor. + /// + /// Effects will be felt at the beginning of the next era. + pub fn unstake(transactor: &AccountId) { + let mut intentions = IntentionStorageVec::items(); + if let Some(position) = intentions.iter().position(|t| t == transactor) { + intentions.swap_remove(position); + } else { + panic!("Cannot unstake if not already staked."); + } + IntentionStorageVec::set_items(&intentions); + storage::put(&transactor.to_keyed_vec(BONDAGE_OF), &(current_era() + bonding_duration())); + } +} + +pub mod privileged { + use super::*; + + /// Set the number of sessions in an era. + pub fn set_sessions_per_era(new: BlockNumber) { + storage::put(NEXT_SESSIONS_PER_ERA, &new); + } + + /// The length of the bonding duration in eras. + pub fn set_bonding_duration(new: BlockNumber) { + storage::put(BONDING_DURATION, &new); + } + + /// The length of a staking era in sessions. + pub fn set_validator_count(new: u32) { + storage::put(VALIDATOR_COUNT, &new); + } + + /// Force there to be a new era. This also forces a new session immediately after. + pub fn force_new_era() { + new_era(); + session::privileged::force_new_session(); + } +} + +pub mod internal { + use super::*; + + /// Set the balance of an account. + /// Needless to say, this is super low-level and accordingly dangerous. Ensure any modules that + /// use it are auditted to the hilt. + pub fn set_free_balance(who: &AccountId, value: Balance) { + storage::put(&who.to_keyed_vec(BALANCE_OF), &value); + } + + /// 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() - last_era_length_change()) % 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 = free_balance(who); + if b >= value { + set_free_balance(who, b - value); + return true; + } + } + false + } + + /// Refund some balance. + pub fn refund(who: &AccountId, value: Balance) { + set_free_balance(who, free_balance(who) + value) + } + + /// Will slash any balance, but prefer free over reserved. + pub fn slash(who: &AccountId, value: Balance) -> bool { + let free_balance = free_balance(who); + let free_slash = cmp::min(free_balance, value); + set_free_balance(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 = free_balance(who); + assert!(b >= value); + set_free_balance(who, b - value); + set_reserved_balance(who, reserved_balance(who) + value); + } + + /// Moves `value` from reserved balance to balance. + pub fn unreserve_balance(who: &AccountId, value: Balance) { + let b = reserved_balance(who); + let value = cmp::min(b, value); + set_reserved_balance(who, b - value); + set_free_balance(who, free_balance(who) + value); + } + + /// Moves `value` from reserved balance to balance. + pub fn slash_reserved(who: &AccountId, value: Balance) -> bool { + let b = reserved_balance(who); + let slash = cmp::min(b, value); + set_reserved_balance(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 = reserved_balance(slashed); + let slash = cmp::min(b, value); + set_reserved_balance(slashed, b - slash); + set_free_balance(beneficiary, free_balance(beneficiary) + slash); + slash == value + } +} + +/// Set the reserved portion of `who`'s balance. +fn set_reserved_balance(who: &AccountId, value: Balance) { + storage::put(&who.to_keyed_vec(RESERVED_BALANCE_OF), &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. + storage::put(CURRENT_ERA, &(current_era() + 1)); + + // Enact era length change. + let next_spe: u64 = storage::get_or_default(NEXT_SESSIONS_PER_ERA); + if next_spe > 0 && next_spe != sessions_per_era() { + storage::put(SESSIONS_PER_ERA, &next_spe); + storage::put(LAST_ERA_LENGTH_CHANGE, &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 + // validator_count() of them. + let mut intentions = IntentionStorageVec::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(validator_count()) + .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; + + pub fn externalities(session_length: u64, sessions_per_era: u64, current_era: u64) -> TestExternalities { + let extras: TestExternalities = map![ + twox_128(INTENTION_COUNT).to_vec() => vec![].and(&3u32), + twox_128(&0u32.to_keyed_vec(INTENTION_AT)).to_vec() => Alice.to_raw_public_vec(), + twox_128(&1u32.to_keyed_vec(INTENTION_AT)).to_vec() => Bob.to_raw_public_vec(), + twox_128(&2u32.to_keyed_vec(INTENTION_AT)).to_vec() => Charlie.to_raw_public_vec(), + twox_128(SESSIONS_PER_ERA).to_vec() => vec![].and(&sessions_per_era), + twox_128(VALIDATOR_COUNT).to_vec() => vec![].and(&3u64), + twox_128(CURRENT_ERA).to_vec() => vec![].and(¤t_era), + twox_128(&Alice.to_raw_public().to_keyed_vec(BALANCE_OF)).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::public::*; + use super::privileged::*; + + use runtime_io::{with_externalities, twox_128, TestExternalities}; + use codec::{KeyedVec, Joiner}; + use keyring::Keyring::*; + use environment::with_env; + use demo_primitives::AccountId; + use runtime::{staking, session}; + + #[test] + fn staking_should_work() { + let mut t: TestExternalities = map![ + twox_128(session::SESSION_LENGTH).to_vec() => vec![].and(&1u64), + twox_128(session::VALIDATOR_COUNT).to_vec() => vec![].and(&2u32), + twox_128(&0u32.to_keyed_vec(session::VALIDATOR_AT)).to_vec() => vec![10; 32], + twox_128(&1u32.to_keyed_vec(session::VALIDATOR_AT)).to_vec() => vec![20; 32], + twox_128(SESSIONS_PER_ERA).to_vec() => vec![].and(&2u64), + twox_128(VALIDATOR_COUNT).to_vec() => vec![].and(&2u32), + twox_128(BONDING_DURATION).to_vec() => vec![].and(&3u64), + twox_128(TOTAL_STAKE).to_vec() => vec![].and(&100u64), + twox_128(&Alice.to_raw_public().to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&10u64), + twox_128(&Bob.to_raw_public().to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&20u64), + twox_128(&Charlie.to_raw_public().to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&30u64), + twox_128(&Dave.to_raw_public().to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&40u64) + ]; + + with_externalities(&mut t, || { + assert_eq!(era_length(), 2u64); + assert_eq!(validator_count(), 2usize); + assert_eq!(bonding_duration(), 3u64); + assert_eq!(session::validators(), vec![[10u8; 32], [20u8; 32]]); + + // Block 1: Add three validators. No obvious change. + with_env(|e| e.block_number = 1); + stake(&Alice); + stake(&Bob); + stake(&Dave); + check_new_era(); + assert_eq!(session::validators(), vec![[10u8; 32], [20u8; 32]]); + + // Block 2: New validator set now. + with_env(|e| e.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. + with_env(|e| e.block_number = 3); + stake(&Charlie); + unstake(&Dave); + check_new_era(); + + // Block 4: New era - validators change. + with_env(|e| e.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. + with_env(|e| e.block_number = 5); + transfer(&Dave, &Alice, 40); + check_new_era(); + + // Block 6: Lowest now validator. + with_env(|e| e.block_number = 6); + check_new_era(); + assert_eq!(session::validators(), vec![Alice.to_raw_public(), Charlie.into()]); + + // Block 7: Unstake three. No change yet. + with_env(|e| e.block_number = 7); + unstake(&Charlie); + check_new_era(); + assert_eq!(session::validators(), vec![Alice.to_raw_public(), Charlie.into()]); + + // Block 8: Back to one and two. + with_env(|e| e.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::SESSION_LENGTH).to_vec() => vec![].and(&1u64), + twox_128(SESSIONS_PER_ERA).to_vec() => vec![].and(&2u64) + ]; + with_externalities(&mut t, || { + assert_eq!(era_length(), 2u64); + assert_eq!(sessions_per_era(), 2u64); + assert_eq!(last_era_length_change(), 0u64); + assert_eq!(current_era(), 0u64); + + // Block 1: No change. + with_env(|e| e.block_number = 1); + check_new_era(); + assert_eq!(sessions_per_era(), 2u64); + assert_eq!(last_era_length_change(), 0u64); + assert_eq!(current_era(), 0u64); + + // Block 2: Simple era change. + with_env(|e| e.block_number = 2); + check_new_era(); + assert_eq!(sessions_per_era(), 2u64); + assert_eq!(last_era_length_change(), 0u64); + assert_eq!(current_era(), 1u64); + + // Block 3: Schedule an era length change; no visible changes. + with_env(|e| e.block_number = 3); + set_sessions_per_era(3); + check_new_era(); + assert_eq!(sessions_per_era(), 2u64); + assert_eq!(last_era_length_change(), 0u64); + assert_eq!(current_era(), 1u64); + + // Block 4: Era change kicks in. + with_env(|e| e.block_number = 4); + check_new_era(); + assert_eq!(sessions_per_era(), 3u64); + assert_eq!(last_era_length_change(), 4u64); + assert_eq!(current_era(), 2u64); + + // Block 5: No change. + with_env(|e| e.block_number = 5); + check_new_era(); + assert_eq!(sessions_per_era(), 3u64); + assert_eq!(last_era_length_change(), 4u64); + assert_eq!(current_era(), 2u64); + + // Block 6: No change. + with_env(|e| e.block_number = 6); + check_new_era(); + assert_eq!(sessions_per_era(), 3u64); + assert_eq!(last_era_length_change(), 4u64); + assert_eq!(current_era(), 2u64); + + // Block 7: Era increment. + with_env(|e| e.block_number = 7); + check_new_era(); + assert_eq!(sessions_per_era(), 3u64); + assert_eq!(last_era_length_change(), 4u64); + assert_eq!(current_era(), 3u64); + }); + } + + #[test] + fn staking_balance_works() { + with_externalities(&mut TestExternalities::default(), || { + set_free_balance(&Alice, 42); + assert_eq!(free_balance(&Alice), 42); + assert_eq!(reserved_balance(&Alice), 0); + assert_eq!(balance(&Alice), 42); + assert_eq!(free_balance(&Bob), 0); + assert_eq!(reserved_balance(&Bob), 0); + assert_eq!(balance(&Bob), 0); + }); + } + + #[test] + fn staking_balance_transfer_works() { + with_externalities(&mut TestExternalities::default(), || { + set_free_balance(&Alice, 111); + transfer(&Alice, &Bob, 69); + assert_eq!(balance(&Alice), 42); + assert_eq!(balance(&Bob), 69); + }); + } + + #[test] + #[should_panic] + fn staking_balance_transfer_when_bonded_panics() { + with_externalities(&mut TestExternalities::default(), || { + set_free_balance(&Alice, 111); + stake(&Alice); + transfer(&Alice, &Bob, 69); + }); + } + + #[test] + fn reserving_balance_should_work() { + with_externalities(&mut TestExternalities::default(), || { + set_free_balance(&Alice, 111); + + assert_eq!(balance(&Alice), 111); + assert_eq!(free_balance(&Alice), 111); + assert_eq!(reserved_balance(&Alice), 0); + + reserve_balance(&Alice, 69); + + assert_eq!(balance(&Alice), 111); + assert_eq!(free_balance(&Alice), 42); + assert_eq!(reserved_balance(&Alice), 69); + }); + } + + #[test] + #[should_panic] + fn staking_balance_transfer_when_reserved_panics() { + with_externalities(&mut TestExternalities::default(), || { + set_free_balance(&Alice, 111); + reserve_balance(&Alice, 69); + transfer(&Alice, &Bob, 69); + }); + } + + #[test] + fn deducting_balance_should_work() { + with_externalities(&mut TestExternalities::default(), || { + set_free_balance(&Alice, 111); + assert!(deduct_unbonded(&Alice, 69)); + assert_eq!(free_balance(&Alice), 42); + }); + } + + #[test] + fn deducting_balance_should_fail_when_bonded() { + let mut t: TestExternalities = map![ + twox_128(&Alice.to_raw_public().to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&111u64), + twox_128(&Alice.to_raw_public().to_keyed_vec(BONDAGE_OF)).to_vec() => vec![].and(&2u64) + ]; + with_externalities(&mut t, || { + with_env(|e| e.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 TestExternalities::default(), || { + set_free_balance(&Alice, 42); + refund(&Alice, 69); + assert_eq!(free_balance(&Alice), 111); + }); + } + + #[test] + fn slashing_balance_should_work() { + with_externalities(&mut TestExternalities::default(), || { + set_free_balance(&Alice, 111); + reserve_balance(&Alice, 69); + assert!(slash(&Alice, 69)); + assert_eq!(free_balance(&Alice), 0); + assert_eq!(reserved_balance(&Alice), 42); + }); + } + + #[test] + fn slashing_incomplete_balance_should_work() { + with_externalities(&mut TestExternalities::default(), || { + set_free_balance(&Alice, 42); + reserve_balance(&Alice, 21); + assert!(!slash(&Alice, 69)); + assert_eq!(free_balance(&Alice), 0); + assert_eq!(reserved_balance(&Alice), 0); + }); + } + + #[test] + fn unreserving_balance_should_work() { + with_externalities(&mut TestExternalities::default(), || { + set_free_balance(&Alice, 111); + reserve_balance(&Alice, 111); + unreserve_balance(&Alice, 42); + assert_eq!(reserved_balance(&Alice), 69); + assert_eq!(free_balance(&Alice), 42); + }); + } + + #[test] + fn slashing_reserved_balance_should_work() { + with_externalities(&mut TestExternalities::default(), || { + set_free_balance(&Alice, 111); + reserve_balance(&Alice, 111); + assert!(slash_reserved(&Alice, 42)); + assert_eq!(reserved_balance(&Alice), 69); + assert_eq!(free_balance(&Alice), 0); + }); + } + + #[test] + fn slashing_incomplete_reserved_balance_should_work() { + with_externalities(&mut TestExternalities::default(), || { + set_free_balance(&Alice, 111); + reserve_balance(&Alice, 42); + assert!(!slash_reserved(&Alice, 69)); + assert_eq!(free_balance(&Alice), 69); + assert_eq!(reserved_balance(&Alice), 0); + }); + } + + #[test] + fn transferring_reserved_balance_should_work() { + with_externalities(&mut TestExternalities::default(), || { + set_free_balance(&Alice, 111); + reserve_balance(&Alice, 111); + assert!(transfer_reserved_balance(&Alice, &Bob, 42)); + assert_eq!(reserved_balance(&Alice), 69); + assert_eq!(free_balance(&Alice), 0); + assert_eq!(reserved_balance(&Bob), 0); + assert_eq!(free_balance(&Bob), 42); + }); + } + + #[test] + fn transferring_incomplete_reserved_balance_should_work() { + with_externalities(&mut TestExternalities::default(), || { + set_free_balance(&Alice, 111); + reserve_balance(&Alice, 42); + assert!(!transfer_reserved_balance(&Alice, &Bob, 69)); + assert_eq!(reserved_balance(&Alice), 0); + assert_eq!(free_balance(&Alice), 69); + assert_eq!(reserved_balance(&Bob), 0); + assert_eq!(free_balance(&Bob), 42); + }); + } +} diff --git a/demo/runtime/src/runtime/system.rs b/demo/runtime/src/runtime/system.rs new file mode 100644 index 0000000000000..4add4dd146403 --- /dev/null +++ b/demo/runtime/src/runtime/system.rs @@ -0,0 +1,325 @@ +// 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}; +use environment::with_env; +use demo_primitives::{AccountId, Hash, TxOrder, BlockNumber, Block, Header, + UncheckedTransaction, Function, Log}; +use runtime::{staking, session}; +use dispatch; + +pub const NONCE_OF: &[u8] = b"sys:non:"; +pub const BLOCK_HASH_AT: &[u8] = b"sys:old:"; +pub const CODE: &[u8] = b"sys:cod"; + +/// The current block number being processed. Set by `execute_block`. +pub fn block_number() -> BlockNumber { + with_env(|e| e.block_number) +} + +/// Get the block hash of a given block (uses storage). +pub fn block_hash(number: BlockNumber) -> Hash { + storage::get_or_default(&number.to_keyed_vec(BLOCK_HASH_AT)) +} + +pub mod privileged { + use super::*; + + /// Set the new code. + pub fn set_code(new: &[u8]) { + storage::unhashed::put_raw(b":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) { + with_env(|e| e.digest.logs.push(log)); + } + + /// Actually execute all transitioning for `block`. + pub fn execute_block(mut block: Block) { + // populate environment from header. + with_env(|e| { + e.block_number = block.header.number; + e.parent_hash = block.header.parent_hash; + }); + + // 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); + } + + /// 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, mut header: Header) -> Header { + // populate environment from header. + with_env(|e| { + e.block_number = header.number; + e.parent_hash = header.parent_hash; + mem::swap(&mut header.digest, &mut e.digest); + }); + + super::execute_transaction(utx); + + with_env(|e| { + mem::swap(&mut header.digest, &mut e.digest); + }); + header + } + + /// Finalise the block - it is up the caller to ensure that all header fields are valid + /// except state-root. + pub fn finalise_block(mut header: Header) -> Header { + // populate environment from header. + with_env(|e| { + e.block_number = header.number; + e.parent_hash = header.parent_hash; + mem::swap(&mut header.digest, &mut e.digest); + }); + + staking::internal::check_new_era(); + session::internal::check_rotate_session(); + + header.state_root = storage_root().into(); + with_env(|e| { + mem::swap(&mut header.digest, &mut e.digest); + }); + + 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 nonce_key = tx.signed.to_keyed_vec(NONCE_OF); + let expected_nonce: TxOrder = storage::get_or(&nonce_key, 0); + assert!(tx.nonce == expected_nonce, "All transactions should have the correct nonce"); + + // increment nonce in storage + storage::put(&nonce_key, &(expected_nonce + 1)); + + // decode parameters and dispatch + dispatch::function(&tx.function, &tx.signed); +} + +fn initial_checks(block: &Block) { + let ref header = block.header; + + // check parent_hash is correct. + assert!( + header.number > 0 && block_hash(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 + with_env(|e| { + assert!(header.digest == e.digest); + }); + + // 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 post_finalise(header: &Header) { + // store the header hash in storage; we can't do it before otherwise there would be a + // cyclic dependency. + storage::put(&header.number.to_keyed_vec(BLOCK_HASH_AT), &header.blake2_256()); +} + +#[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(&0u64.to_keyed_vec(BLOCK_HASH_AT)).to_vec() => [69u8; 32].encode() + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use super::internal::*; + + use runtime_io::{with_externalities, twox_128, TestExternalities}; + use codec::{Joiner, KeyedVec, Slicable}; + use keyring::Keyring::*; + use environment::with_env; + use primitives::hexdisplay::HexDisplay; + use demo_primitives::{Header, Digest, UncheckedTransaction, Transaction, Function}; + use runtime::staking; + + #[test] + fn staking_balance_transfer_dispatch_works() { + let mut t: TestExternalities = map![ + twox_128(&One.to_raw_public().to_keyed_vec(staking::BALANCE_OF)).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0] + ]; + + let tx = UncheckedTransaction { + transaction: Transaction { + signed: One.into(), + nonce: 0, + function: Function::StakingTransfer(Two.into(), 69), + }, + signature: hex!("5f9832c5a4a39e2dd4a3a0c5b400e9836beb362cb8f7d845a8291a2ae6fe366612e080e4acd0b5a75c3d0b6ee69614a68fb63698c1e76bf1f2dcd8fa617ddf05").into(), + }; + + with_externalities(&mut t, || { + internal::execute_transaction(tx, Header::from_block_number(1)); + assert_eq!(staking::balance(&One), 42); + 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!("f4f6408fe3ce1d78d30bb7ed625b32f91e45b8b566023df309cfd93c6f4af9a4").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 new file mode 100644 index 0000000000000..52b6cbe2d7d18 --- /dev/null +++ b/demo/runtime/src/runtime/timestamp.rs @@ -0,0 +1,60 @@ +// 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; + +pub type Timestamp = u64; + +pub const CURRENT_TIMESTAMP: &[u8] = b"tim:val"; + +/// Get the current time. +pub fn get() -> Timestamp { + storage::get_or_default(CURRENT_TIMESTAMP) +} + +pub mod public { + use super::*; + + /// Set the current time. + pub fn set(now: Timestamp) { + storage::put(CURRENT_TIMESTAMP, &now); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use super::public::*; + + use runtime_io::{with_externalities, twox_128, TestExternalities}; + use runtime::timestamp; + use codec::{Joiner, KeyedVec}; + + #[test] + fn timestamp_works() { + let mut t: TestExternalities = map![ + twox_128(CURRENT_TIMESTAMP).to_vec() => vec![].and(&42u64) + ]; + + with_externalities(&mut t, || { + assert_eq!(get(), 42); + set(69); + assert_eq!(get(), 69); + }); + } +} diff --git a/demo/runtime/wasm/Cargo.lock b/demo/runtime/wasm/Cargo.lock new file mode 100644 index 0000000000000..a415dd58eb92f --- /dev/null +++ b/demo/runtime/wasm/Cargo.lock @@ -0,0 +1,900 @@ +[[package]] +name = "aho-corasick" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ansi_term" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "arrayvec" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "odds 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "arrayvec" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bigint" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "heapsize 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byteorder" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cc" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "coco" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "crunchy" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "demo-primitives" +version = "0.1.0" +dependencies = [ + "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-std 0.1.0", +] + +[[package]] +name = "demo-runtime" +version = "0.1.0" +dependencies = [ + "demo-primitives 0.1.0", + "integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)", + "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 = "ed25519" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-primitives 0.1.0", + "untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "either" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "elastic-array" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "heapsize 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "env_logger" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "environmental" +version = "0.1.0" + +[[package]] +name = "ethcore-bigint" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bigint 4.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "heapsize 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "plain_hasher 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ethcore-bytes" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ethcore-logger" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayvec 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "isatty 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fixed-hash" +version = "0.1.3" +source = "git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm#8dc457899afdaf968ff7f16140b03d1e37b01d71" +dependencies = [ + "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "gcc" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hashdb" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "elastic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "heapsize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hex-literal" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hex-literal-impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hex-literal-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.0" +source = "git+https://github.com/paritytech/integer-sqrt-rs.git#f4cf61482096dc98c1273f46a10849d182b4c23c" + +[[package]] +name = "isatty" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "keccak-hash" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tiny-keccak 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "log" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memorydb" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bigint 4.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "elastic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hashdb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "heapsize 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "keccak-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rlp 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nodrop" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num_cpus" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "odds" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "owning_ref" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot_core" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "patricia-trie" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "elastic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-bytes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-logger 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hashdb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "keccak-hash 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)", + "memorydb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "rlp 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "plain_hasher" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro-hack" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack-impl 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro-hack-impl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pwasm-alloc" +version = "0.1.0" +dependencies = [ + "pwasm-libc 0.1.0", + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pwasm-libc" +version = "0.1.0" + +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rayon-core 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon-core" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ring" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rlp" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "elastic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-hex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc-hex" +version = "2.0.0" +source = "git+https://github.com/rphmeier/rustc-hex.git#ee2ec40b9062ac7769ccb9dc891d6dc2cc9009d7" + +[[package]] +name = "rustc_version" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "scopeguard" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "semver" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_derive" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive_internals" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "smallvec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "stable_deref_trait" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "substrate-codec" +version = "0.1.0" +dependencies = [ + "substrate-runtime-std 0.1.0", +] + +[[package]] +name = "substrate-primitives" +version = "0.1.0" +dependencies = [ + "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "fixed-hash 0.1.3 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)", + "rustc-hex 2.0.0 (git+https://github.com/rphmeier/rustc-hex.git)", + "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-runtime-std 0.1.0", + "twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uint 0.1.2 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)", +] + +[[package]] +name = "substrate-runtime-io" +version = "0.1.0" +dependencies = [ + "ed25519 0.1.0", + "environmental 0.1.0", + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-primitives 0.1.0", + "substrate-runtime-std 0.1.0", + "substrate-state-machine 0.1.0", + "triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "substrate-runtime-std" +version = "0.1.0" +dependencies = [ + "pwasm-alloc 0.1.0", + "pwasm-libc 0.1.0", + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +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)", + "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-state-machine" +version = "0.1.0" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hashdb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memorydb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "patricia-trie 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-primitives 0.1.0", + "triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tiny-keccak" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "triehash" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ethcore-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "keccak-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rlp 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "twox-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "uint" +version = "0.1.2" +source = "git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm#8dc457899afdaf968ff7f16140b03d1e37b01d71" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "untrusted" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "utf8-ranges" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" +"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" +"checksum arrayvec 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)" = "06f59fe10306bb78facd90d28c2038ad23ffaaefa85bac43c8a434cde383334f" +"checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" +"checksum bigint 4.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5442186ef6560f30f1ee4b9c1e4c87a35a6879d3644550cc248ec2b955eb5fcd" +"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" +"checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +"checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" +"checksum cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "deaf9ec656256bb25b404c51ef50097207b9cbb29c933d31f92cae5a8a0ffee0" +"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" +"checksum coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c06169f5beb7e31c7c67ebf5540b8b472d23e3eade3b2ec7d1f5b504a85f91bd" +"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" +"checksum crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" +"checksum either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "740178ddf48b1a9e878e6d6509a1442a2d42fd2928aae8e7a6f8a36fb01981b3" +"checksum elastic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "258ff6a9a94f648d0379dbd79110e057edbb53eb85cc237e33eadf8e5a30df85" +"checksum env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b" +"checksum ethcore-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcb5af77e74a8f70e9c3337e069c37bc82178ef1b459c02091f73c4ad5281eb5" +"checksum ethcore-bytes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3977c772cd6c5c22e1c7cfa208e4c3b746bd6c3a6c8eeec0999a6b2103015ad5" +"checksum ethcore-logger 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1fd5813e49546030be7d134e775088d56b8ff4ab60617b90e93d4f0513da4c5b" +"checksum fixed-hash 0.1.3 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)" = "" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb" +"checksum hashdb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d97be07c358c5b461268b4ce60304024c5fa5acfd4bd8cd743639f0252003cf5" +"checksum heapsize 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1679e6ea370dee694f91f1dc469bf94cf8f52051d147aec3e1f9497c6fc22461" +"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 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" +"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" +"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" +"checksum libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1e5d97d6708edaa407429faa671b942dc0f2727222fb6b6539bf1db936e4b121" +"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" +"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_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" +"checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" +"checksum parking_lot_core 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "9f35048d735bb93dd115a0030498785971aab3234d311fbe273d020084d26bd8" +"checksum patricia-trie 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f1e2f638d79aba5c4a71a4f373df6e3cd702250a53b7f0ed4da1e2a7be9737ae" +"checksum plain_hasher 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83ae80873992f511142c07d0ec6c44de5636628fdb7e204abd655932ea79d995" +"checksum proc-macro-hack 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ba8d4f9257b85eb6cdf13f055cea3190520aab1409ca2ab43493ea4820c25f0" +"checksum proc-macro-hack-impl 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5cb6f960ad471404618e9817c0e5d10b1ae74cfdf01fab89ea0641fe7fb2892" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" +"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" +"checksum rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b614fe08b6665cb9a231d07ac1364b0ef3cb3698f1239ee0c4c3a88a524f54c8" +"checksum rayon-core 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e64b609139d83da75902f88fd6c01820046840a18471e4dfcd5ac7c0f46bea53" +"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "744554e01ccbd98fff8c457c3b092cd67af62a555a43bfe97ae8a0451f7799fa" +"checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e" +"checksum ring 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6f7d28b30a72c01b458428e0ae988d4149c20d902346902be881e3edc4bb325c" +"checksum rlp 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "babe6fce20c0ca9b1582998734c4569082d0ad08e43772a1c6c40aef4f106ef9" +"checksum rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0ceb8ce7a5e520de349e1fa172baeba4a9e8d5ef06c47471863530bc4972ee1e" +"checksum rustc-hex 2.0.0 (git+https://github.com/rphmeier/rustc-hex.git)" = "" +"checksum rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9743a7670d88d5d52950408ecdb7c71d8986251ab604d4689dd2ca25c9bca69" +"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" +"checksum semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "db99f3919e20faa51bb2996057f5031d8685019b5a06139b1ce761da671b8526" +"checksum serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ba7591cfe93755e89eeecdbcc668885624829b020050e6aec99c2a03bd3fd0" +"checksum serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e03f1c9530c3fb0a0a5c9b826bdd9246a5921ae995d75f512ac917fc4dd55b5" +"checksum smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44db0ecb22921ef790d17ae13a3f6d15784183ff5f2a01aa32098c7498d2b4b9" +"checksum stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15132e0e364248108c5e2c02e3ab539be8d6f5d52a01ca9bbf27ed657316f02b" +"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" +"checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098" +"checksum tiny-keccak 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e9241752647ca572f12c9b520a5d360d9099360c527770647e694001646a1d0" +"checksum triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9291c7f0fae44858b5e087dd462afb382354120003778f1695b44aab98c7abd7" +"checksum twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "475352206e7a290c5fccc27624a163e8d0d115f7bb60ca18a64fc9ce056d7435" +"checksum uint 0.1.2 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)" = "" +"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +"checksum untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f392d7819dbe58833e26872f5f6f0d68b7bbbe90fc3667e98731c4a15ad9a7ae" +"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/demo/runtime/wasm/Cargo.toml b/demo/runtime/wasm/Cargo.toml new file mode 100644 index 0000000000000..436c482015cba --- /dev/null +++ b/demo/runtime/wasm/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "demo-runtime" +version = "0.1.0" +authors = ["Parity Technologies "] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +substrate-codec = { path = "../../../substrate/codec", 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 } +demo-primitives = { path = "../../primitives", default-features = false } +integer-sqrt = { git = "https://github.com/paritytech/integer-sqrt-rs.git", branch = "master" } + +[features] +default = [] +std = [ + "substrate-codec/std", + "substrate-runtime-io/std", + "substrate-runtime-std/std", + "substrate-runtime-support/std", + "substrate-primitives/std", + "demo-primitives/std", +] + +[profile.release] +panic = "abort" + +[workspace] +members = [] diff --git a/demo/runtime/wasm/build.sh b/demo/runtime/wasm/build.sh new file mode 100755 index 0000000000000..53ca2a0018ce5 --- /dev/null +++ b/demo/runtime/wasm/build.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -e + +cargo +nightly build --target=wasm32-unknown-unknown --release +for i in demo_runtime +do + wasm-gc target/wasm32-unknown-unknown/release/$i.wasm target/wasm32-unknown-unknown/release/$i.compact.wasm +done diff --git a/demo/runtime/wasm/genesis.wasm b/demo/runtime/wasm/genesis.wasm new file mode 100644 index 0000000000000..bc0cb89235c57 Binary files /dev/null and b/demo/runtime/wasm/genesis.wasm differ diff --git a/demo/runtime/wasm/init.sh b/demo/runtime/wasm/init.sh new file mode 100755 index 0000000000000..02a0059a87584 --- /dev/null +++ b/demo/runtime/wasm/init.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +rustup update nightly +rustup target add wasm32-unknown-unknown --toolchain nightly +rustup update stable +cargo install --git https://github.com/alexcrichton/wasm-gc diff --git a/demo/runtime/wasm/src b/demo/runtime/wasm/src new file mode 120000 index 0000000000000..5cd551cf2693e --- /dev/null +++ b/demo/runtime/wasm/src @@ -0,0 +1 @@ +../src \ No newline at end of file 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 new file mode 100644 index 0000000000000..71363e96d9632 Binary files /dev/null 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 new file mode 100644 index 0000000000000..77c937bf318bb Binary files /dev/null and b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm differ diff --git a/demo/src/main.rs b/demo/src/main.rs new file mode 100644 index 0000000000000..82e48dfc70cf8 --- /dev/null +++ b/demo/src/main.rs @@ -0,0 +1,30 @@ +// 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 . + +//! Substrate Demo CLI + +#![warn(missing_docs)] + +extern crate demo_cli as cli; + +#[macro_use] +extern crate error_chain; + +quick_main!(run); + +fn run() -> cli::error::Result<()> { + cli::run(::std::env::args()) +} diff --git a/polkadot/api/src/lib.rs b/polkadot/api/src/lib.rs index 2eeb0cd06d0d3..049e59e197648 100644 --- a/polkadot/api/src/lib.rs +++ b/polkadot/api/src/lib.rs @@ -17,7 +17,7 @@ //! Strongly typed API for Polkadot based around the locally-compiled native //! runtime. -extern crate polkadot_executor as polkadot_executor; +extern crate polkadot_executor; extern crate polkadot_runtime; extern crate polkadot_primitives as primitives; extern crate substrate_client as client; diff --git a/polkadot/cli/src/cli.yml b/polkadot/cli/src/cli.yml index a23ba5c2356da..2e98e2c68efa2 100644 --- a/polkadot/cli/src/cli.yml +++ b/polkadot/cli/src/cli.yml @@ -1,5 +1,5 @@ name: polkadot -author: "Parity Team " +author: "Parity Team " about: Polkadot Node Rust Implementation args: - log: diff --git a/polkadot/runtime/src/runtime/staking.rs b/polkadot/runtime/src/runtime/staking.rs index f321c714a5f3f..cca0a92718420 100644 --- a/polkadot/runtime/src/runtime/staking.rs +++ b/polkadot/runtime/src/runtime/staking.rs @@ -40,6 +40,7 @@ const SESSIONS_PER_ERA: &[u8] = b"sta:spe"; const NEXT_SESSIONS_PER_ERA: &[u8] = b"sta:nse"; const CURRENT_ERA: &[u8] = b"sta:era"; const LAST_ERA_LENGTH_CHANGE: &[u8] = b"sta:lec"; +const TOTAL_STAKE: &[u8] = b"sta:tot"; const BALANCE_OF: &[u8] = b"sta:bal:"; const BONDAGE_OF: &[u8] = b"sta:bon:"; @@ -295,6 +296,7 @@ mod tests { twox_128(SESSIONS_PER_ERA).to_vec() => vec![].and(&2u64), twox_128(VALIDATOR_COUNT).to_vec() => vec![].and(&2u32), twox_128(BONDING_DURATION).to_vec() => vec![].and(&3u64), + twox_128(TOTAL_STAKE).to_vec() => vec![].and(&100u64), twox_128(&one.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&10u64), twox_128(&two.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&20u64), twox_128(&three.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&30u64), 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 7b23e9f44add9..e33dc4212228e 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 index 99fcb7cdfc5a9..b29f06b3b6d28 100644 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/substrate/codec/src/keyedvec.rs b/substrate/codec/src/keyedvec.rs index 7a27ece90a274..d57d1dd111972 100644 --- a/substrate/codec/src/keyedvec.rs +++ b/substrate/codec/src/keyedvec.rs @@ -25,33 +25,12 @@ pub trait KeyedVec { fn to_keyed_vec(&self, prepend_key: &[u8]) -> Vec; } -macro_rules! impl_non_endians { - ( $( $t:ty ),* ) => { $( - impl KeyedVec for $t { - fn to_keyed_vec(&self, prepend_key: &[u8]) -> Vec { - let mut r = prepend_key.to_vec(); - r.extend(&self[..]); - r - } - } - )* } +impl KeyedVec for T { + fn to_keyed_vec(&self, prepend_key: &[u8]) -> Vec { + self.using_encoded(|slice| { + let mut r = prepend_key.to_vec(); + r.extend(slice); + r + }) + } } - -macro_rules! impl_endians { - ( $( $t:ty ),* ) => { $( - impl KeyedVec for $t { - fn to_keyed_vec(&self, prepend_key: &[u8]) -> Vec { - self.using_encoded(|slice| { - let mut r = prepend_key.to_vec(); - r.extend(slice); - r - }) - } - } - )* } -} - -impl_endians!(u8, i8, u16, u32, u64, usize, i16, i32, i64, isize); -impl_non_endians!([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]); diff --git a/substrate/codec/src/slicable.rs b/substrate/codec/src/slicable.rs index b2aeb08035bfa..eb2b01db52fc8 100644 --- a/substrate/codec/src/slicable.rs +++ b/substrate/codec/src/slicable.rs @@ -89,6 +89,26 @@ impl Slicable for T { } } +impl Slicable for Option { + fn decode(input: &mut I) -> Option { + u8::decode(input).and_then(|v| match v { + 0 => Some(Some(false)), + 1 => Some(Some(true)), + 2 => Some(None), + _ => None, + }) + } + + fn using_encoded R>(&self, f: F) -> R { + match *self { + Some(false) => 0u8, + Some(true) => 1u8, + None => 2u8, + }.using_encoded(f) + } +} +impl NonTrivialSlicable for Option {} + impl Slicable for Vec { fn decode(input: &mut I) -> Option { u32::decode(input).and_then(move |len| { @@ -112,6 +132,43 @@ 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),*) => { $( diff --git a/substrate/ed25519/src/lib.rs b/substrate/ed25519/src/lib.rs index 4d8117186bc61..f8a2ec0ce62a0 100644 --- a/substrate/ed25519/src/lib.rs +++ b/substrate/ed25519/src/lib.rs @@ -37,8 +37,8 @@ pub struct LocalizedSignature { } /// Verify a message without type checking the parameters' types for the right size. -pub fn verify(sig: &[u8], message: &[u8], public: &[u8]) -> bool { - let public_key = untrusted::Input::from(public); +pub fn verify>(sig: &[u8], message: &[u8], public: P) -> bool { + let public_key = untrusted::Input::from(public.as_ref()); let msg = untrusted::Input::from(message); let sig = untrusted::Input::from(sig); @@ -104,6 +104,18 @@ impl Into<[u8; 32]> for Public { } } +impl AsRef for Public { + fn as_ref(&self) -> &Public { + &self + } +} + +impl AsRef for Pair { + fn as_ref(&self) -> &Pair { + &self + } +} + impl Pair { /// Generate new secure (random) key pair. pub fn new() -> Pair { @@ -144,8 +156,8 @@ impl Pair { } /// Verify a signature on a message. -pub fn verify_strong(sig: &Signature, message: &[u8], pubkey: &Public) -> bool { - let public_key = untrusted::Input::from(&pubkey.0[..]); +pub fn verify_strong>(sig: &Signature, message: &[u8], pubkey: P) -> bool { + let public_key = untrusted::Input::from(&pubkey.as_ref().0[..]); let msg = untrusted::Input::from(message); let sig = untrusted::Input::from(&sig.0[..]); @@ -157,19 +169,19 @@ pub fn verify_strong(sig: &Signature, message: &[u8], pubkey: &Public) -> bool { pub trait Verifiable { /// Verify something that acts like a signature. - fn verify(&self, message: &[u8], pubkey: &Public) -> bool; + fn verify>(&self, message: &[u8], pubkey: P) -> bool; } impl Verifiable for Signature { /// Verify something that acts like a signature. - fn verify(&self, message: &[u8], pubkey: &Public) -> bool { + fn verify>(&self, message: &[u8], pubkey: P) -> bool { verify_strong(&self, message, pubkey) } } impl Verifiable for LocalizedSignature { - fn verify(&self, message: &[u8], pubkey: &Public) -> bool { - pubkey == &self.signer && self.signature.verify(message, pubkey) + fn verify>(&self, message: &[u8], pubkey: P) -> bool { + pubkey.as_ref() == &self.signer && self.signature.verify(message, pubkey) } } 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 8e3669ced5d9d..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 index c55856204bcac..ab23e9b4f7a2a 100644 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/keyring/Cargo.toml b/substrate/keyring/Cargo.toml index f59caf2caf208..69bd55a6f3e24 100644 --- a/substrate/keyring/Cargo.toml +++ b/substrate/keyring/Cargo.toml @@ -6,3 +6,4 @@ authors = ["Parity Technologies "] [dependencies] ed25519 = { path = "../ed25519" } hex-literal = { version = "0.1.0" } +lazy_static = { version = "1.0" } diff --git a/substrate/keyring/src/lib.rs b/substrate/keyring/src/lib.rs index 2e49695be8b4b..9749ef13449cf 100644 --- a/substrate/keyring/src/lib.rs +++ b/substrate/keyring/src/lib.rs @@ -17,12 +17,15 @@ //! Support code for the runtime. #[macro_use] extern crate hex_literal; +#[macro_use] extern crate lazy_static; pub extern crate ed25519; +use std::collections::HashMap; +use std::ops::Deref; use ed25519::{Pair, Public, Signature}; /// Set of test accounts. -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum Keyring { Alice, Bob, @@ -65,6 +68,19 @@ impl Keyring { pub fn sign(self, msg: &[u8]) -> Signature { Pair::from(self).sign(msg) } + + pub fn pair(self) -> Pair { + match self { + Keyring::Alice => Pair::from_seed(b"Alice "), + Keyring::Bob => Pair::from_seed(b"Bob "), + Keyring::Charlie => Pair::from_seed(b"Charlie "), + Keyring::Dave => Pair::from_seed(b"Dave "), + Keyring::Eve => Pair::from_seed(b"Eve "), + Keyring::Ferdie => Pair::from_seed(b"Ferdie "), + Keyring::One => Pair::from_seed(b"12345678901234567890123456789012"), + Keyring::Two => Pair::from_seed(&hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")), + } + } } impl From for &'static str { @@ -82,32 +98,65 @@ impl From for &'static str { } } -impl From for Pair { +lazy_static! { + static ref PRIVATE_KEYS: HashMap = { + [ + Keyring::Alice, + Keyring::Bob, + Keyring::Charlie, + Keyring::Dave, + Keyring::Eve, + Keyring::Ferdie, + Keyring::One, + Keyring::Two, + ].iter().map(|&i| (i, i.pair())).collect() + }; + + static ref PUBLIC_KEYS: HashMap = { + PRIVATE_KEYS.iter().map(|(&name, pair)| (name, pair.public())).collect() + }; +} + +impl From for Public { fn from(k: Keyring) -> Self { - match k { - Keyring::Alice => Pair::from_seed(b"Alice "), - Keyring::Bob => Pair::from_seed(b"Bob "), - Keyring::Charlie => Pair::from_seed(b"Charlie "), - Keyring::Dave => Pair::from_seed(b"Dave "), - Keyring::Eve => Pair::from_seed(b"Eve "), - Keyring::Ferdie => Pair::from_seed(b"Ferdie "), - Keyring::One => Pair::from_seed(b"12345678901234567890123456789012"), - Keyring::Two => Pair::from_seed(&hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")), - } + (*PUBLIC_KEYS).get(&k).unwrap().clone() } } -impl From for Public { +impl From for Pair { fn from(k: Keyring) -> Self { - let pair: Pair = k.into(); - pair.public() + k.pair() } } impl From for [u8; 32] { fn from(k: Keyring) -> Self { - let pair: Pair = k.into(); - *pair.public().as_array_ref() + *(*PUBLIC_KEYS).get(&k).unwrap().as_array_ref() + } +} + +impl From for &'static [u8; 32] { + fn from(k: Keyring) -> Self { + (*PUBLIC_KEYS).get(&k).unwrap().as_array_ref() + } +} + +impl AsRef<[u8; 32]> for Keyring { + fn as_ref(&self) -> &[u8; 32] { + (*PUBLIC_KEYS).get(self).unwrap().as_array_ref() + } +} + +impl AsRef for Keyring { + fn as_ref(&self) -> &Public { + (*PUBLIC_KEYS).get(self).unwrap() + } +} + +impl Deref for Keyring { + type Target = [u8; 32]; + fn deref(&self) -> &[u8; 32] { + (*PUBLIC_KEYS).get(self).unwrap().as_array_ref() } } @@ -118,8 +167,8 @@ mod tests { #[test] fn should_work() { - assert!(Keyring::Alice.sign(b"I am Alice!").verify(b"I am Alice!", &Keyring::Alice.into())); - assert!(!Keyring::Alice.sign(b"I am Alice!").verify(b"I am Bob!", &Keyring::Alice.into())); - assert!(!Keyring::Alice.sign(b"I am Alice!").verify(b"I am Alice!", &Keyring::Bob.into())); + assert!(Keyring::Alice.sign(b"I am Alice!").verify(b"I am Alice!", Keyring::Alice)); + assert!(!Keyring::Alice.sign(b"I am Alice!").verify(b"I am Bob!", Keyring::Alice)); + assert!(!Keyring::Alice.sign(b"I am Alice!").verify(b"I am Alice!", Keyring::Bob)); } } diff --git a/substrate/primitives/src/block.rs b/substrate/primitives/src/block.rs index f41f0ec8480f2..0fd55bca403fc 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}; +use codec::{Input, Slicable, NonTrivialSlicable}; /// Used to refer to a block number. pub type Number = u64; @@ -47,7 +47,9 @@ impl Slicable for Transaction { } } -impl ::codec::NonTrivialSlicable for Transaction { } +impl NonTrivialSlicable for Transaction { } + + /// Execution log (event) #[derive(PartialEq, Eq, Clone)] @@ -64,7 +66,9 @@ impl Slicable for Log { } } -impl ::codec::NonTrivialSlicable for Log { } +impl NonTrivialSlicable for Log { } + + /// The digest of a block, useful for light-clients. #[derive(Clone, Default, PartialEq, Eq)] @@ -84,40 +88,49 @@ impl Slicable for Digest { } } -/// The body of a block is just a bunch of transactions. -pub type Body = Vec; +impl NonTrivialSlicable for Digest { } -/// A Substrate relay chain block. -#[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, -} +/// Generic types to be specialised later. +pub mod generic { + use super::{Header, Slicable, Input, NonTrivialSlicable, Vec}; -impl Slicable for Block { - fn decode(input: &mut I) -> Option { - Some(Block { - header: try_opt!(Slicable::decode(input)), - transactions: try_opt!(Slicable::decode(input)), - }) + /// A Block - this is generic for later specialisation in particular runtimes. + #[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: Vec, } - fn encode(&self) -> Vec { - let mut v = Vec::new(); - - v.extend(self.header.encode()); - v.extend(self.transactions.encode()); + 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)), + }) + } - v + fn encode(&self) -> Vec { + let mut v: Vec = Vec::new(); + v.extend(self.header.encode()); + v.extend(self.transactions.encode()); + v + } } + + impl NonTrivialSlicable for Block where Vec: Slicable { } } -/// A relay chain block header. -/// -/// https://github.com/w3f/polkadot-spec/blob/master/spec.md#header +/// The body of a block is just a bunch of transactions. +pub type Body = Vec; +/// The header and body of a concrete, but unspecialised, block. Used by substrate to represent a +/// block some fields of which the runtime alone knows how to interpret (e.g. the transactions). +pub type Block = generic::Block; + +/// A substrate chain block header. +// TODO: split out into light-client-specific fields and runtime-specific fields. #[derive(PartialEq, Eq, Clone)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] @@ -131,6 +144,9 @@ pub struct Header { pub state_root: Hash, /// The root of the trie that represents this block's transactions, indexed by a 32-byte integer. pub transaction_root: Hash, + // TODO... +// /// The root of the trie that represents the receipts from this block's transactions +// pub receipts_root: Hash, /// The digest of activity on the block. pub digest: Digest, } diff --git a/substrate/runtime-io/with_std.rs b/substrate/runtime-io/with_std.rs index 5d785119c561e..8c1bad8d7fc52 100644 --- a/substrate/runtime-io/with_std.rs +++ b/substrate/runtime-io/with_std.rs @@ -86,7 +86,7 @@ pub fn enumerated_trie_root(serialised_values: &[&[u8]]) -> [u8; 32] { } /// Verify a ed25519 signature. -pub fn ed25519_verify(sig: &[u8; 64], msg: &[u8], pubkey: &[u8; 32]) -> bool { +pub fn ed25519_verify>(sig: &[u8; 64], msg: &[u8], pubkey: P) -> bool { ed25519::verify(sig, msg, pubkey) } diff --git a/substrate/runtime-io/without_std.rs b/substrate/runtime-io/without_std.rs index ad31020cc4962..64d69957cf61c 100644 --- a/substrate/runtime-io/without_std.rs +++ b/substrate/runtime-io/without_std.rs @@ -161,9 +161,9 @@ pub fn twox_128(data: &[u8]) -> [u8; 16] { } /// Verify a ed25519 signature. -pub fn ed25519_verify(sig: &[u8; 64], msg: &[u8], pubkey: &[u8; 32]) -> bool { +pub fn ed25519_verify>(sig: &[u8; 64], msg: &[u8], pubkey: P) -> bool { unsafe { - ext_ed25519_verify(msg.as_ptr(), msg.len() as u32, sig.as_ptr(), pubkey.as_ptr()) == 0 + ext_ed25519_verify(msg.as_ptr(), msg.len() as u32, sig.as_ptr(), pubkey.as_ref().as_ptr()) == 0 } } diff --git a/substrate/runtime-std/with_std.rs b/substrate/runtime-std/with_std.rs index 9473cb836f132..2cfd266f48271 100644 --- a/substrate/runtime-std/with_std.rs +++ b/substrate/runtime-std/with_std.rs @@ -26,3 +26,6 @@ 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 6afda817126b0..5fc2d7526b3ff 100644 --- a/substrate/runtime-std/without_std.rs +++ b/substrate/runtime-std/without_std.rs @@ -25,6 +25,9 @@ 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::cmp; diff --git a/substrate/runtime-support/src/storage.rs b/substrate/runtime-support/src/storage.rs index 54237200fb911..cdb52468cf5b6 100644 --- a/substrate/runtime-support/src/storage.rs +++ b/substrate/runtime-support/src/storage.rs @@ -322,7 +322,6 @@ pub mod unhashed { #[cfg(test)] mod tests { use super::*; - use primitives::hexdisplay; use runtime_io::{twox_128, TestExternalities, with_externalities}; #[test] diff --git a/substrate/test-runtime/src/block.rs b/substrate/test-runtime/src/block.rs deleted file mode 100644 index f85bbd882ec46..0000000000000 --- a/substrate/test-runtime/src/block.rs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate 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 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. If not, see . - -//! A toy unchecked transaction complete with signature. - -use rstd::prelude::*; -use codec::{Input, Slicable, Joiner}; -use super::{Header, UncheckedTransaction}; - -#[derive(PartialEq, Eq, Clone)] -#[cfg_attr(feature = "std", derive(Debug))] -/// A coupling between a header and a list of transactions. -pub struct Block { - /// The block header. - pub header: Header, - /// The list of transactions in the block. - pub transactions: Vec, -} - -impl Slicable for Block { - fn decode(input: &mut I) -> Option { - Some(Block { - header: Slicable::decode(input)?, - transactions: Slicable::decode(input)?, - }) - } - - fn encode(&self) -> Vec { - Vec::new() - .and(&self.header) - .and(&self.transactions) - } -} - -impl ::codec::NonTrivialSlicable for Block {} diff --git a/substrate/test-runtime/src/lib.rs b/substrate/test-runtime/src/lib.rs index 52fedf788f56b..fb09970820225 100644 --- a/substrate/test-runtime/src/lib.rs +++ b/substrate/test-runtime/src/lib.rs @@ -31,19 +31,20 @@ extern crate substrate_codec as codec; pub mod system; mod transaction; mod unchecked_transaction; -mod block; use rstd::prelude::*; use codec::Slicable; use primitives::AuthorityId; use primitives::hash::H512; +use primitives::block::generic; pub use primitives::hash::H256; pub use primitives::block::{Header, Number as BlockNumber, Digest}; pub use transaction::Transaction; pub use unchecked_transaction::UncheckedTransaction; -pub use block::Block; +/// A test block. +pub type Block = generic::Block; /// An identifier for an account on this system. pub type AccountId = AuthorityId; /// Signature for our transactions. 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 d6f866d14b313..e97060c601271 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 index 7abe7a7ac0e2a..96bd1a82b292b 100644 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