From 97acfa76d1577ad1277188f58e4fd13bc0c724f6 Mon Sep 17 00:00:00 2001 From: Gavin Wood <gavin@parity.io> Date: Sat, 11 Jul 2020 17:04:10 +0200 Subject: [PATCH] Introduce polling pallet (#1391) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial draft * Initial build * Integrate into runtime * Fix warnings. * Initial tests * Fixes and tests * Update runtime/polkadot/src/poll.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Expose End constant * Docs * Update runtime/polkadot/src/lib.rs Co-authored-by: kaichao <kaichaosuna@gmail.com> * Update runtime/polkadot/src/poll.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Clean up filters * Warnings Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> Co-authored-by: kaichao <kaichaosuna@gmail.com> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- polkadot/Cargo.lock | 1 + polkadot/runtime/polkadot/Cargo.toml | 3 +- polkadot/runtime/polkadot/src/lib.rs | 24 ++- polkadot/runtime/polkadot/src/poll.rs | 233 ++++++++++++++++++++++++++ 4 files changed, 253 insertions(+), 8 deletions(-) create mode 100644 polkadot/runtime/polkadot/src/poll.rs diff --git a/polkadot/Cargo.lock b/polkadot/Cargo.lock index 8583a3b1db5..9f8d5c94f08 100644 --- a/polkadot/Cargo.lock +++ b/polkadot/Cargo.lock @@ -4716,6 +4716,7 @@ dependencies = [ "sp-consensus-babe", "sp-core", "sp-inherents", + "sp-io", "sp-keyring", "sp-offchain", "sp-runtime", diff --git a/polkadot/runtime/polkadot/Cargo.toml b/polkadot/runtime/polkadot/Cargo.toml index bad45c6aa12..7f1763ff298 100644 --- a/polkadot/runtime/polkadot/Cargo.toml +++ b/polkadot/runtime/polkadot/Cargo.toml @@ -20,7 +20,8 @@ babe-primitives = { package = "sp-consensus-babe", git = "https://github.com/par sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } inherents = { package = "sp-inherents", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } offchain-primitives = { package = "sp-offchain", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } -sp-std = { package = "sp-std", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-staking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } diff --git a/polkadot/runtime/polkadot/src/lib.rs b/polkadot/runtime/polkadot/src/lib.rs index 258a7b4781b..610a37f07ac 100644 --- a/polkadot/runtime/polkadot/src/lib.rs +++ b/polkadot/runtime/polkadot/src/lib.rs @@ -79,6 +79,7 @@ pub use parachains::Call as ParachainsCall; /// Constant values used within the runtime. pub mod constants; +pub mod poll; use constants::{time::*, currency::*, fee::*}; use frame_support::traits::InstanceFilter; @@ -114,12 +115,7 @@ pub struct BaseFilter; impl Filter<Call> for BaseFilter { fn filter(call: &Call) -> bool { match call { - Call::Parachains(parachains::Call::set_heads(..)) - | Call::Democracy(democracy::Call::vote(..)) - | Call::Democracy(democracy::Call::remove_vote(..)) - | Call::Democracy(democracy::Call::delegate(..)) - | Call::Democracy(democracy::Call::undelegate(..)) - => true, + Call::Parachains(parachains::Call::set_heads(..)) => true, // Governance stuff Call::Democracy(_) | Call::Council(_) | Call::TechnicalCommittee(_) | @@ -138,7 +134,7 @@ impl Filter<Call> for BaseFilter { Call::Session(_) | Call::FinalityTracker(_) | Call::Grandpa(_) | Call::ImOnline(_) | Call::AuthorityDiscovery(_) | Call::Utility(_) | Call::Claims(_) | Call::Vesting(_) | Call::Sudo(_) | - Call::Identity(_) | Call::Proxy(_) | Call::Multisig(_) => + Call::Identity(_) | Call::Proxy(_) | Call::Multisig(_) | Call::Poll(_) => true, } } @@ -893,6 +889,7 @@ impl InstanceFilter<Call> for ProxyType { ProxyType::Governance => matches!(c, Call::Democracy(..) | Call::Council(..) | Call::TechnicalCommittee(..) | Call::ElectionsPhragmen(..) | Call::Treasury(..) | Call::Utility(..) + | Call::Poll(..) ), ProxyType::Staking => matches!(c, Call::Staking(..) | Call::Utility(utility::Call::batch(..)) | Call::Utility(..) @@ -941,6 +938,16 @@ impl frame_support::traits::OnRuntimeUpgrade for CustomOnRuntimeUpgrade { } } +parameter_types! { + pub const PollEnd: BlockNumber = 100_000; +} + +impl poll::Trait for Runtime { + type Event = Event; + type Currency = Balances; + type End = PollEnd; +} + construct_runtime! { pub enum Runtime where Block = Block, @@ -1006,6 +1013,9 @@ construct_runtime! { // Multisig dispatch. Late addition. Multisig: multisig::{Module, Call, Storage, Event<T>}, + + // Poll module. + Poll: poll::{Module, Call, Storage, Event<T>}, } } diff --git a/polkadot/runtime/polkadot/src/poll.rs b/polkadot/runtime/polkadot/src/poll.rs new file mode 100644 index 00000000000..c2cd5518178 --- /dev/null +++ b/polkadot/runtime/polkadot/src/poll.rs @@ -0,0 +1,233 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>. + +//! # Simple polling module +//! +//! Note: This implementation assumes that all accounts are locked, and thus that no account balance +//! may ever reduce. + +use frame_support::{ + decl_module, decl_storage, decl_event, decl_error, ensure, traits::{Currency, Get}, +}; +use system::{self as frame_system, ensure_signed}; +use sp_runtime::traits::Saturating; + +pub type BalanceOf<T> = + <<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance; + +pub trait Trait: system::Trait { + /// The overarching event type. + type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>; + + /// The currency type used. + type Currency: Currency<Self::AccountId>; + + /// The block number only before which voting is possible. + type End: Get<Self::BlockNumber>; +} + +/// The options someone has approved. +pub type Approvals = [bool; 4]; + +decl_storage! { + trait Store for Module<T: Trait> as Poll { + /// Votes, so far. + pub VoteOf: map hasher(twox_64_concat) T::AccountId => (Approvals, BalanceOf<T>); + + /// The total balances voting for each option. + pub Totals: [BalanceOf<T>; 4]; + } +} + +decl_event! { + pub enum Event<T> where + <T as system::Trait>::AccountId, + Balance = BalanceOf<T>, + { + Voted(AccountId, Balance, Approvals), + } +} + +decl_error! { + pub enum Error for Module<T: Trait> { + /// Vote attempted after the end of voting. + TooLate, + } +} + +decl_module! { + pub struct Module<T: Trait> for enum Call where origin: T::Origin { + type Error = Error<T>; + + fn deposit_event() = default; + + /// The End config param. + const End: T::BlockNumber = T::End::get(); + + /// Cast a vote on the poll. + #[weight = 100_000_000] + fn vote(origin, approvals: Approvals) { + let who = ensure_signed(origin)?; + ensure!(system::Module::<T>::block_number() < T::End::get(), Error::<T>::TooLate); + let balance = T::Currency::total_balance(&who); + Totals::<T>::mutate(|ref mut totals| { + VoteOf::<T>::mutate(&who, |(ref mut who_approvals, ref mut who_balance)| { + for i in 0..approvals.len() { + if who_approvals[i] { + totals[i] = totals[i].saturating_sub(*who_balance); + } + if approvals[i] { + totals[i] = totals[i].saturating_add(balance); + } + } + *who_approvals = approvals; + *who_balance = balance; + }); + }); + Self::deposit_event(RawEvent::Voted(who, balance, approvals)); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use frame_support::{assert_ok, assert_noop, impl_outer_origin, parameter_types, weights::Weight}; + use sp_core::H256; + use sp_runtime::{Perbill, testing::Header, traits::{BlakeTwo256, IdentityLookup}}; + + impl_outer_origin! { + pub enum Origin for Test where system = frame_system {} + } + + // For testing the pallet, we construct most of a mock runtime. This means + // first constructing a configuration type (`Test`) which `impl`s each of the + // configuration traits of pallets we want to use. + #[derive(Clone, Eq, PartialEq)] + pub struct Test; + parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); + } + impl frame_system::Trait for Test { + type BaseCallFilter = (); + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Call = (); + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup<Self::AccountId>; + type Header = Header; + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type DbWeight = (); + type BlockExecutionWeight = (); + type ExtrinsicBaseWeight = (); + type MaximumExtrinsicWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + type ModuleToIndex = (); + type AccountData = balances::AccountData<u64>; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + } + parameter_types! { + pub const ExistentialDeposit: u64 = 1; + } + impl balances::Trait for Test { + type Balance = u64; + type Event = (); + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + } + parameter_types! { + pub const End: u64 = 1; + } + impl Trait for Test { + type Event = (); + type Currency = Balances; + type End = End; + } + type System = system::Module<Test>; + type Balances = balances::Module<Test>; + type Poll = Module<Test>; + + // This function basically just builds a genesis storage key/value store according to + // our desired mockup. + pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = system::GenesisConfig::default().build_storage::<Test>().unwrap(); + // We use default for brevity, but you can configure as desired if needed. + balances::GenesisConfig::<Test> { + balances: vec![ + (1, 10), + (2, 20), + (3, 30), + (4, 40), + ], + }.assimilate_storage(&mut t).unwrap(); + t.into() + } + + #[test] + fn basic_setup_works() { + new_test_ext().execute_with(|| { + assert_eq!(System::block_number(), 0); + }); + } + + #[test] + fn totaling_works() { + new_test_ext().execute_with(|| { + assert_ok!(Poll::vote(Origin::signed(1), [true, true, false, false])); + assert_ok!(Poll::vote(Origin::signed(2), [false, true, true, false])); + assert_ok!(Poll::vote(Origin::signed(3), [false, false, true, true])); + assert_ok!(Poll::vote(Origin::signed(4), [true, false, false, true])); + assert_eq!(Totals::<Test>::get(), [50, 30, 50, 70]); + }); + } + + #[test] + fn revoting_works() { + new_test_ext().execute_with(|| { + assert_ok!(Poll::vote(Origin::signed(1), [true, false, false, false])); + assert_eq!(Totals::<Test>::get(), [10, 0, 0, 0]); + assert_ok!(Poll::vote(Origin::signed(1), [false, true, false, false])); + assert_eq!(Totals::<Test>::get(), [0, 10, 0, 0]); + assert_ok!(Poll::vote(Origin::signed(1), [false, false, true, true])); + assert_eq!(Totals::<Test>::get(), [0, 0, 10, 10]); + }); + } + + #[test] + fn vote_end_works() { + new_test_ext().execute_with(|| { + assert_ok!(Poll::vote(Origin::signed(1), [true, false, false, false])); + assert_eq!(Totals::<Test>::get(), [10, 0, 0, 0]); + system::Module::<Test>::set_block_number(1); + assert_noop!(Poll::vote(Origin::signed(1), [false, true, false, false]), Error::<Test>::TooLate); + }); + } +} -- GitLab