diff --git a/Cargo.lock b/Cargo.lock index fdf624718..94146cd49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,6 +119,7 @@ dependencies = [ "frame-try-runtime", "hex", "hex-literal", + "lazy_static", "libsecp256k1", "log", "module-aggregated-dex", @@ -175,11 +176,13 @@ dependencies = [ "pallet-balances", "pallet-bounties", "pallet-collective", + "pallet-conviction-voting", "pallet-democracy", "pallet-membership", "pallet-multisig", "pallet-preimage", "pallet-proxy", + "pallet-referenda", "pallet-scheduler", "pallet-session", "pallet-staking", @@ -190,6 +193,7 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "pallet-treasury", "pallet-utility", + "pallet-whitelist", "pallet-xcm", "parity-scale-codec", "polkadot-parachain-primitives", @@ -216,6 +220,8 @@ dependencies = [ "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", + "strum 0.24.1", + "strum_macros 0.24.3", "substrate-wasm-builder", ] @@ -5322,6 +5328,7 @@ dependencies = [ "frame-try-runtime", "hex", "hex-literal", + "lazy_static", "libsecp256k1", "log", "module-aggregated-dex", @@ -5379,11 +5386,13 @@ dependencies = [ "pallet-balances", "pallet-bounties", "pallet-collective", + "pallet-conviction-voting", "pallet-democracy", "pallet-membership", "pallet-multisig", "pallet-preimage", "pallet-proxy", + "pallet-referenda", "pallet-scheduler", "pallet-session", "pallet-staking", @@ -5394,6 +5403,7 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "pallet-treasury", "pallet-utility", + "pallet-whitelist", "pallet-xcm", "parity-scale-codec", "polkadot-parachain-primitives", @@ -5420,6 +5430,8 @@ dependencies = [ "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", + "strum 0.24.1", + "strum_macros 0.24.3", "substrate-wasm-builder", ] @@ -6233,6 +6245,7 @@ dependencies = [ "frame-try-runtime", "hex", "hex-literal", + "lazy_static", "libsecp256k1", "log", "module-aggregated-dex", @@ -6292,6 +6305,7 @@ dependencies = [ "pallet-balances", "pallet-bounties", "pallet-collective", + "pallet-conviction-voting", "pallet-democracy", "pallet-elections-phragmen", "pallet-indices", @@ -6300,6 +6314,7 @@ dependencies = [ "pallet-preimage", "pallet-proxy", "pallet-recovery", + "pallet-referenda", "pallet-root-testing", "pallet-scheduler", "pallet-session", @@ -6310,6 +6325,7 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "pallet-treasury", "pallet-utility", + "pallet-whitelist", "pallet-xcm", "parity-scale-codec", "polkadot-parachain-primitives", @@ -6338,6 +6354,8 @@ dependencies = [ "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", + "strum 0.24.1", + "strum_macros 0.24.3", "substrate-wasm-builder", ] diff --git a/Cargo.toml b/Cargo.toml index 0121755a0..11dde34ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -153,6 +153,9 @@ async-trait = { version = "0.1.71" } coins-bip32 = { version = "0.7.0" } coins-bip39 = { version = "0.7.0" } k256 = { version = "0.11.5", default-features = false } +strum = { version = "0.24", default-features = false, features = [ "derive" ] } +strum_macros = { version = "0.24" } +lazy_static = { version = "1.4.0" } # Dependencies are split into 2 groups: wasm and client. # - "wasm" dependencies requires to be no_std compatible, which often requires @@ -276,6 +279,9 @@ pallet-treasury = { version = "24.0.0", default-features = false } pallet-utility = { version = "25.0.0", default-features = false } pallet-vesting = { version = "25.0.0", default-features = false } pallet-xcm = { version = "4.0.0", default-features = false } +pallet-conviction-voting = { version = "25.0.0", default-features = false } +pallet-referenda = { version = "25.0.0", default-features = false } +pallet-whitelist = { version = "24.0.0", default-features = false } parachain-info = { package = "staging-parachain-info", version = "0.4.0", default-features = false } polkadot-core-primitives = { version = "4.0.0", default-features = false } polkadot-parachain-primitives = { version = "3.0.0", default-features = false } diff --git a/runtime/acala/Cargo.toml b/runtime/acala/Cargo.toml index e5b2e3fbf..fd315cf79 100644 --- a/runtime/acala/Cargo.toml +++ b/runtime/acala/Cargo.toml @@ -13,6 +13,9 @@ scale-info = { workspace = true } serde_json = { workspace = true, features = ["alloc"] } hex = { workspace = true } hex-literal = { workspace = true } +strum = { workspace = true } +strum_macros = { workspace = true } +lazy_static = { workspace = true } # substrate frame-executive = { workspace = true } @@ -39,6 +42,9 @@ pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-treasury = { workspace = true } pallet-utility = { workspace = true } pallet-preimage = { workspace = true } +pallet-conviction-voting = { workspace = true } +pallet-referenda = { workspace = true } +pallet-whitelist = { workspace = true } sp-api = { workspace = true } sp-application-crypto = { workspace = true } sp-block-builder = { workspace = true } @@ -150,6 +156,7 @@ std = [ "hex/std", "scale-info/std", "serde_json/std", + "strum/std", "frame-benchmarking/std", "frame-executive/std", @@ -193,6 +200,9 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "substrate-wasm-builder", + "pallet-conviction-voting/std", + "pallet-referenda/std", + "pallet-whitelist/std", "cumulus-pallet-aura-ext/std", "cumulus-pallet-dmp-queue/std", @@ -289,6 +299,9 @@ runtime-benchmarks = [ "pallet-utility/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "sp-staking/runtime-benchmarks", + "pallet-conviction-voting/runtime-benchmarks", + "pallet-referenda/runtime-benchmarks", + "pallet-whitelist/runtime-benchmarks", "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", @@ -351,6 +364,9 @@ try-runtime = [ "pallet-treasury/try-runtime", "pallet-utility/try-runtime", "pallet-bags-list/try-runtime", + "pallet-conviction-voting/try-runtime", + "pallet-referenda/try-runtime", + "pallet-whitelist/try-runtime", "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-dmp-queue/try-runtime", diff --git a/runtime/acala/src/governance/councils.rs b/runtime/acala/src/governance/councils.rs new file mode 100644 index 000000000..31bc170ce --- /dev/null +++ b/runtime/acala/src/governance/councils.rs @@ -0,0 +1,144 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Councils for Gov1 and Gov2 + +use super::*; + +parameter_types! { + pub const GeneralCouncilMotionDuration: BlockNumber = 3 * DAYS; + pub const CouncilDefaultMaxProposals: u32 = 20; + pub const CouncilDefaultMaxMembers: u32 = 30; + pub MaxProposalWeight: Weight = Perbill::from_percent(50) * RuntimeBlockWeights::get().max_block; +} + +impl pallet_collective::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = GeneralCouncilMotionDuration; + type MaxProposals = CouncilDefaultMaxProposals; + type MaxMembers = CouncilDefaultMaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type SetMembersOrigin = EnsureRoot; + type WeightInfo = (); + type MaxProposalWeight = MaxProposalWeight; +} + +impl pallet_membership::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type RemoveOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type SwapOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type ResetOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type PrimeOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type MembershipInitialized = GeneralCouncil; + type MembershipChanged = GeneralCouncil; + type MaxMembers = CouncilDefaultMaxMembers; + type WeightInfo = (); +} + +parameter_types! { + pub const FinancialCouncilMotionDuration: BlockNumber = 3 * DAYS; +} + +impl pallet_collective::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = FinancialCouncilMotionDuration; + type MaxProposals = CouncilDefaultMaxProposals; + type MaxMembers = CouncilDefaultMaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type SetMembersOrigin = EnsureRoot; + type WeightInfo = (); + type MaxProposalWeight = MaxProposalWeight; +} + +impl pallet_membership::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type MembershipInitialized = FinancialCouncil; + type MembershipChanged = FinancialCouncil; + type MaxMembers = CouncilDefaultMaxMembers; + type WeightInfo = (); +} + +parameter_types! { + pub const HomaCouncilMotionDuration: BlockNumber = 3 * DAYS; +} + +impl pallet_collective::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = HomaCouncilMotionDuration; + type MaxProposals = CouncilDefaultMaxProposals; + type MaxMembers = CouncilDefaultMaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type SetMembersOrigin = EnsureRoot; + type WeightInfo = (); + type MaxProposalWeight = MaxProposalWeight; +} + +impl pallet_membership::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type MembershipInitialized = HomaCouncil; + type MembershipChanged = HomaCouncil; + type MaxMembers = CouncilDefaultMaxMembers; + type WeightInfo = (); +} + +parameter_types! { + pub const TechnicalCommitteeMotionDuration: BlockNumber = 3 * DAYS; +} + +impl pallet_collective::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = TechnicalCommitteeMotionDuration; + type MaxProposals = CouncilDefaultMaxProposals; + type MaxMembers = CouncilDefaultMaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type SetMembersOrigin = EnsureRoot; + type WeightInfo = (); + type MaxProposalWeight = MaxProposalWeight; +} + +impl pallet_membership::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type MembershipInitialized = TechnicalCommittee; + type MembershipChanged = TechnicalCommittee; + type MaxMembers = CouncilDefaultMaxMembers; + type WeightInfo = (); +} diff --git a/runtime/acala/src/governance/democracy.rs b/runtime/acala/src/governance/democracy.rs new file mode 100644 index 000000000..4ec5df626 --- /dev/null +++ b/runtime/acala/src/governance/democracy.rs @@ -0,0 +1,74 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Democracy config for Gov1 + +use crate::*; + +parameter_types! { + pub const LaunchPeriod: BlockNumber = 5 * DAYS; + pub const VotingPeriod: BlockNumber = 5 * DAYS; + pub const FastTrackVotingPeriod: BlockNumber = 3 * HOURS; + pub MinimumDeposit: Balance = 1000 * dollar(ACA); + pub const EnactmentPeriod: BlockNumber = 2 * DAYS; + pub const VoteLockingPeriod: BlockNumber = 14 * DAYS; + pub const CooloffPeriod: BlockNumber = 7 * DAYS; +} + +impl pallet_democracy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type EnactmentPeriod = EnactmentPeriod; + type LaunchPeriod = LaunchPeriod; + type VotingPeriod = VotingPeriod; + type VoteLockingPeriod = VoteLockingPeriod; + type MinimumDeposit = MinimumDeposit; + /// A straight majority of the council can decide what their next motion is. + type ExternalOrigin = EnsureRootOrHalfGeneralCouncil; + /// A majority can have the next scheduled referendum be a straight majority-carries vote. + type ExternalMajorityOrigin = EnsureRootOrHalfGeneralCouncil; + /// A unanimous council can have the next scheduled referendum be a straight default-carries + /// (NTB) vote. + type ExternalDefaultOrigin = EnsureRootOrAllGeneralCouncil; + /// Two thirds of the technical committee can have an ExternalMajority/ExternalDefault vote + /// be tabled immediately and with a shorter voting/enactment period. + type FastTrackOrigin = EnsureRootOrTwoThirdsTechnicalCommittee; + type InstantOrigin = EnsureRootOrAllTechnicalCommittee; + type InstantAllowed = ConstBool; + type FastTrackVotingPeriod = FastTrackVotingPeriod; + // To cancel a proposal which has been passed, 2/3 of the council must agree to it. + type CancellationOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type BlacklistOrigin = EnsureRoot; + // To cancel a proposal before it has been passed, the technical committee must be unanimous or + // Root must agree. + type CancelProposalOrigin = EnsureRootOrAllTechnicalCommittee; + // Any single technical committee member may veto a coming council proposal, however they can + // only do it once and it lasts only for the cooloff period. + type VetoOrigin = pallet_collective::EnsureMember; + type CooloffPeriod = CooloffPeriod; + type Slash = Treasury; + type Scheduler = Scheduler; + type PalletsOrigin = OriginCaller; + type MaxVotes = ConstU32<100>; + type WeightInfo = pallet_democracy::weights::SubstrateWeight; + type MaxProposals = ConstU32<100>; + type Preimages = Preimage; + type MaxDeposits = ConstU32<100>; + type MaxBlacklisted = ConstU32<100>; + type SubmitOrigin = EnsureSigned; +} diff --git a/runtime/acala/src/governance/mod.rs b/runtime/acala/src/governance/mod.rs new file mode 100644 index 000000000..9d8000ef0 --- /dev/null +++ b/runtime/acala/src/governance/mod.rs @@ -0,0 +1,30 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Governance configurations + +pub mod councils; +mod democracy; +pub mod referenda; + +use super::*; + +mod origins; +pub use origins::{custom_origins, GeneralAdmin, ReferendumCanceller, ReferendumKiller, WhitelistedCaller}; +mod tracks; +pub use tracks::TracksInfo; diff --git a/runtime/acala/src/governance/origins.rs b/runtime/acala/src/governance/origins.rs new file mode 100644 index 000000000..7f7d7237a --- /dev/null +++ b/runtime/acala/src/governance/origins.rs @@ -0,0 +1,79 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Custom origins for governance interventions. + +pub use custom_origins::*; + +#[frame_support::pallet] +pub mod custom_origins { + use frame_support::pallet_prelude::*; + use strum_macros::EnumString; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[derive(PartialEq, Eq, Clone, MaxEncodedLen, Encode, Decode, TypeInfo, RuntimeDebug, EnumString)] + #[strum(serialize_all = "snake_case")] + #[pallet::origin] + pub enum Origin { + /// Origin able to dispatch a whitelisted call. + WhitelistedCaller, + /// General admin + GeneralAdmin, + /// Origin able to cancel referenda. + ReferendumCanceller, + /// Origin able to kill referenda. + ReferendumKiller, + } + + macro_rules! decl_unit_ensures { + ( $name:ident: $success_type:ty = $success:expr ) => { + pub struct $name; + impl> + From> + EnsureOrigin for $name + { + type Success = $success_type; + fn try_origin(o: O) -> Result { + o.into().and_then(|o| match o { + Origin::$name => Ok($success), + r => Err(O::from(r)), + }) + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(O::from(Origin::$name)) + } + } + }; + ( $name:ident ) => { decl_unit_ensures! { $name : () = () } }; + ( $name:ident: $success_type:ty = $success:expr, $( $rest:tt )* ) => { + decl_unit_ensures! { $name: $success_type = $success } + decl_unit_ensures! { $( $rest )* } + }; + ( $name:ident, $( $rest:tt )* ) => { + decl_unit_ensures! { $name } + decl_unit_ensures! { $( $rest )* } + }; + () => {} + } + decl_unit_ensures!(ReferendumCanceller, ReferendumKiller, WhitelistedCaller, GeneralAdmin); +} diff --git a/runtime/acala/src/governance/referenda.rs b/runtime/acala/src/governance/referenda.rs new file mode 100644 index 000000000..b223a17e7 --- /dev/null +++ b/runtime/acala/src/governance/referenda.rs @@ -0,0 +1,94 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! # Gov2 config +//! Includes runtime configs for these substrate pallets: +//! 1. pallet-conviction-voting +//! 2. pallet-whitelist +//! 3. pallet-referenda + +use super::*; +use frame_support::traits::{EitherOf, MapSuccess}; +use frame_system::EnsureRootWithSuccess; +use sp_runtime::traits::Replace; + +parameter_types! { + pub const VoteLockingPeriod: BlockNumber = DAYS; +} + +impl pallet_conviction_voting::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type Polls = Referenda; + type MaxTurnout = frame_support::traits::TotalIssuanceOf; + // Maximum number of concurrent votes an account may have + type MaxVotes = ConstU32<20>; + // Minimum period of vote locking + type VoteLockingPeriod = VoteLockingPeriod; +} + +// Origin for general admin or root +pub type GeneralAdminOrRoot = EitherOf, origins::GeneralAdmin>; + +impl custom_origins::Config for Runtime {} + +// The purpose of this pallet is to queue calls to be dispatched as by root later => the Dispatch +// origin corresponds to the Gov2 Whitelist track. +impl pallet_whitelist::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type WhitelistOrigin = EitherOf< + EnsureRootWithSuccess>, + MapSuccess< + pallet_collective::EnsureProportionAtLeast, + Replace>, + >, + >; + type DispatchWhitelistedOrigin = EitherOf, WhitelistedCaller>; + type Preimages = Preimage; +} + +pallet_referenda::impl_tracksinfo_get!(TracksInfo, Balance, BlockNumber); + +parameter_types! { + pub const AlarmInterval: BlockNumber = 1; + pub SubmissionDeposit: Balance = 10 * dollar(ACA); + pub const UndecidingTimeout: BlockNumber = 14 * DAYS; +} + +impl pallet_referenda::Config for Runtime { + type WeightInfo = (); + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type Scheduler = Scheduler; + type Currency = Balances; + type SubmitOrigin = frame_system::EnsureSigned; + type CancelOrigin = EitherOf, ReferendumCanceller>; + type KillOrigin = EitherOf, ReferendumKiller>; + type Slash = Treasury; + type Votes = pallet_conviction_voting::VotesOf; + type Tally = pallet_conviction_voting::TallyOf; + type SubmissionDeposit = SubmissionDeposit; + type MaxQueued = ConstU32<100>; + type UndecidingTimeout = UndecidingTimeout; + type AlarmInterval = AlarmInterval; + type Tracks = TracksInfo; + type Preimages = Preimage; +} diff --git a/runtime/acala/src/governance/tracks.rs b/runtime/acala/src/governance/tracks.rs new file mode 100644 index 000000000..48803ee8f --- /dev/null +++ b/runtime/acala/src/governance/tracks.rs @@ -0,0 +1,178 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Track configurations for governance. + +use super::*; +use pallet_referenda::Curve; +use sp_std::str::FromStr; + +const fn percent(x: i32) -> sp_runtime::FixedI64 { + sp_runtime::FixedI64::from_rational(x as u128, 100) +} +const fn permill(x: i32) -> sp_runtime::FixedI64 { + sp_runtime::FixedI64::from_rational(x as u128, 1000) +} + +lazy_static::lazy_static! { +static ref TRACKS_DATA: [(u16, pallet_referenda::TrackInfo); 5] = [ + ( + 0, + pallet_referenda::TrackInfo { + // Name of this track. + name: "root", + // A limit for the number of referenda on this track that can be being decided at once. + // For Root origin this should generally be just one. + max_deciding: 1, + // Amount that must be placed on deposit before a decision can be made. + decision_deposit: 20_000 * dollar(ACA), + // Amount of time this must be submitted for before a decision can be made. + prepare_period: 2 * HOURS, + // Amount of time that a decision may take to be approved prior to cancellation. + decision_period: 14 * DAYS, + // Amount of time that the approval criteria must hold before it can be approved. + confirm_period: DAYS, + // Minimum amount of time that an approved proposal must be in the dispatch queue. + min_enactment_period: DAYS, + // Minimum aye votes as percentage of overall conviction-weighted votes needed for + // approval as a function of time into decision period. + min_approval: Curve::make_reciprocal(4, 14, percent(80), percent(50), percent(100)), + // Minimum pre-conviction aye-votes ("support") as percentage of overall population that + // is needed for approval as a function of time into decision period. + min_support: Curve::make_linear(14, 14, permill(1), percent(40)), + }, + ), + ( + 1, + pallet_referenda::TrackInfo { + name: "whitelisted_caller", + max_deciding: 5, + decision_deposit: 5_000 * dollar(ACA), + prepare_period: 10 * MINUTES, + decision_period: 10 * DAYS, + confirm_period: 10 * MINUTES, + min_enactment_period: 30 * MINUTES, + min_approval: Curve::make_reciprocal(1, 10, percent(90), percent(50), percent(100)), + min_support: Curve::make_reciprocal(1, 10, percent(1), percent(0), percent(5)), + }, + ), + ( + 2, + pallet_referenda::TrackInfo { + name: "general_admin", + max_deciding: 10, + decision_deposit: 1000 * dollar(ACA), + prepare_period: HOURS, + decision_period: 10 * DAYS, + confirm_period: DAYS, + min_enactment_period: DAYS, + min_approval: Curve::make_reciprocal(4, 14, percent(80), percent(50), percent(100)), + min_support: Curve::make_reciprocal(7, 14, percent(10), percent(0), percent(50)), + }, + ), + ( + 3, + pallet_referenda::TrackInfo { + name: "referendum_canceller", + max_deciding: 20, + decision_deposit: 2 * 1000 * dollar(ACA), + prepare_period: HOURS, + decision_period: 10 * DAYS, + confirm_period: 3 * HOURS, + min_enactment_period: 10 * MINUTES, + min_approval: Curve::make_reciprocal(1, 10, percent(96), percent(50), percent(100)), + min_support: Curve::make_reciprocal(1, 10, percent(1), percent(0), percent(10)), + }, + ), + ( + 4, + pallet_referenda::TrackInfo { + name: "referendum_killer", + max_deciding: 20, + decision_deposit: 4 * 1000 * dollar(ACA), + prepare_period: HOURS, + decision_period: 10 * DAYS, + confirm_period: 3 * HOURS, + min_enactment_period: 10 * MINUTES, + min_approval: Curve::make_reciprocal(1, 10, percent(96), percent(50), percent(100)), + min_support: Curve::make_reciprocal(1, 10, percent(1), percent(0), percent(10)), + }, + ), +]; +} + +pub struct TracksInfo; +impl pallet_referenda::TracksInfo for TracksInfo { + type Id = u16; + type RuntimeOrigin = ::PalletsOrigin; + fn tracks() -> &'static [(Self::Id, pallet_referenda::TrackInfo)] { + &TRACKS_DATA[..] + } + fn track_for(id: &Self::RuntimeOrigin) -> Result { + if let Ok(system_origin) = frame_system::RawOrigin::try_from(id.clone()) { + match system_origin { + frame_system::RawOrigin::Root => { + if let Some((track_id, _)) = Self::tracks().iter().find(|(_, track)| track.name == "root") { + Ok(*track_id) + } else { + Err(()) + } + } + _ => Err(()), + } + } else if let Ok(custom_origin) = custom_origins::Origin::try_from(id.clone()) { + if let Some((track_id, _)) = Self::tracks().iter().find(|(_, track)| { + if let Ok(track_custom_origin) = custom_origins::Origin::from_str(track.name) { + track_custom_origin == custom_origin + } else { + false + } + }) { + Ok(*track_id) + } else { + Err(()) + } + } else { + Err(()) + } + } +} + +#[test] +/// To ensure voters are always locked into their vote +fn vote_locking_always_longer_than_enactment_period() { + for (_, track) in (&TRACKS_DATA).iter() { + assert!( + ::VoteLockingPeriod::get() >= track.min_enactment_period, + "Track {} has enactment period {} < vote locking period {}", + track.name, + track.min_enactment_period, + ::VoteLockingPeriod::get(), + ); + } +} + +#[test] +fn all_tracks_have_origins() { + for (_, track) in (&TRACKS_DATA).iter() { + // check name.into() is successful either converts into "root" or custom origin + let track_is_root = track.name == "root"; + let track_has_custom_origin = custom_origins::Origin::from_str(track.name).is_ok(); + assert!(track_is_root || track_has_custom_origin); + } +} diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 11d43d105..7581a3b14 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -68,7 +68,7 @@ use frame_support::{ traits::{ fungible::HoldConsideration, tokens::{PayFromAccount, UnityAssetBalanceConversion}, - ConstBool, ConstU128, ConstU32, Contains, ContainsLengthBound, Currency as PalletCurrency, Currency, + ConstBool, ConstU128, ConstU16, ConstU32, Contains, ContainsLengthBound, Currency as PalletCurrency, Currency, EnsureOrigin, EqualPrivilegeOnly, Get, Imbalance, InstanceFilter, LinearStoragePrice, LockIdentifier, OnUnbalanced, SortedMembers, }, @@ -119,9 +119,11 @@ use xcm::v3::prelude::*; mod authority; mod benchmarking; pub mod constants; +pub mod governance; /// Weights for pallets used in the runtime. mod weights; pub mod xcm_config; +use governance::councils::*; /// This runtime version. #[sp_version::runtime_version] @@ -397,129 +399,6 @@ impl pallet_sudo::Config for Runtime { type WeightInfo = (); } -parameter_types! { - pub const GeneralCouncilMotionDuration: BlockNumber = 3 * DAYS; - pub const CouncilDefaultMaxProposals: u32 = 20; - pub const CouncilDefaultMaxMembers: u32 = 30; - pub MaxProposalWeight: Weight = Perbill::from_percent(50) * RuntimeBlockWeights::get().max_block; -} - -impl pallet_collective::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type Proposal = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type MotionDuration = GeneralCouncilMotionDuration; - type MaxProposals = CouncilDefaultMaxProposals; - type MaxMembers = CouncilDefaultMaxMembers; - type DefaultVote = pallet_collective::PrimeDefaultVote; - type SetMembersOrigin = EnsureRoot; - type WeightInfo = (); - type MaxProposalWeight = MaxProposalWeight; -} - -impl pallet_membership::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type AddOrigin = EnsureRootOrThreeFourthsGeneralCouncil; - type RemoveOrigin = EnsureRootOrThreeFourthsGeneralCouncil; - type SwapOrigin = EnsureRootOrThreeFourthsGeneralCouncil; - type ResetOrigin = EnsureRootOrThreeFourthsGeneralCouncil; - type PrimeOrigin = EnsureRootOrThreeFourthsGeneralCouncil; - type MembershipInitialized = GeneralCouncil; - type MembershipChanged = GeneralCouncil; - type MaxMembers = CouncilDefaultMaxMembers; - type WeightInfo = (); -} - -parameter_types! { - pub const FinancialCouncilMotionDuration: BlockNumber = 3 * DAYS; -} - -impl pallet_collective::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type Proposal = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type MotionDuration = FinancialCouncilMotionDuration; - type MaxProposals = CouncilDefaultMaxProposals; - type MaxMembers = CouncilDefaultMaxMembers; - type DefaultVote = pallet_collective::PrimeDefaultVote; - type SetMembersOrigin = EnsureRoot; - type WeightInfo = (); - type MaxProposalWeight = MaxProposalWeight; -} - -impl pallet_membership::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type MembershipInitialized = FinancialCouncil; - type MembershipChanged = FinancialCouncil; - type MaxMembers = CouncilDefaultMaxMembers; - type WeightInfo = (); -} - -parameter_types! { - pub const HomaCouncilMotionDuration: BlockNumber = 3 * DAYS; -} - -impl pallet_collective::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type Proposal = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type MotionDuration = HomaCouncilMotionDuration; - type MaxProposals = CouncilDefaultMaxProposals; - type MaxMembers = CouncilDefaultMaxMembers; - type DefaultVote = pallet_collective::PrimeDefaultVote; - type SetMembersOrigin = EnsureRoot; - type WeightInfo = (); - type MaxProposalWeight = MaxProposalWeight; -} - -impl pallet_membership::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type MembershipInitialized = HomaCouncil; - type MembershipChanged = HomaCouncil; - type MaxMembers = CouncilDefaultMaxMembers; - type WeightInfo = (); -} - -parameter_types! { - pub const TechnicalCommitteeMotionDuration: BlockNumber = 3 * DAYS; -} - -impl pallet_collective::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type Proposal = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type MotionDuration = TechnicalCommitteeMotionDuration; - type MaxProposals = CouncilDefaultMaxProposals; - type MaxMembers = CouncilDefaultMaxMembers; - type DefaultVote = pallet_collective::PrimeDefaultVote; - type SetMembersOrigin = EnsureRoot; - type WeightInfo = (); - type MaxProposalWeight = MaxProposalWeight; -} - -impl pallet_membership::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type MembershipInitialized = TechnicalCommittee; - type MembershipChanged = TechnicalCommittee; - type MaxMembers = CouncilDefaultMaxMembers; - type WeightInfo = (); -} - impl pallet_membership::Config for Runtime { type RuntimeEvent = RuntimeEvent; type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; @@ -659,59 +538,6 @@ impl pallet_tips::Config for Runtime { type WeightInfo = (); } -parameter_types! { - pub const LaunchPeriod: BlockNumber = 5 * DAYS; - pub const VotingPeriod: BlockNumber = 5 * DAYS; - pub const FastTrackVotingPeriod: BlockNumber = 3 * HOURS; - pub MinimumDeposit: Balance = 1000 * dollar(ACA); - pub const EnactmentPeriod: BlockNumber = 2 * DAYS; - pub const VoteLockingPeriod: BlockNumber = 14 * DAYS; - pub const CooloffPeriod: BlockNumber = 7 * DAYS; -} - -impl pallet_democracy::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type EnactmentPeriod = EnactmentPeriod; - type LaunchPeriod = LaunchPeriod; - type VotingPeriod = VotingPeriod; - type VoteLockingPeriod = VoteLockingPeriod; - type MinimumDeposit = MinimumDeposit; - /// A straight majority of the council can decide what their next motion is. - type ExternalOrigin = EnsureRootOrHalfGeneralCouncil; - /// A majority can have the next scheduled referendum be a straight majority-carries vote. - type ExternalMajorityOrigin = EnsureRootOrHalfGeneralCouncil; - /// A unanimous council can have the next scheduled referendum be a straight default-carries - /// (NTB) vote. - type ExternalDefaultOrigin = EnsureRootOrAllGeneralCouncil; - /// Two thirds of the technical committee can have an ExternalMajority/ExternalDefault vote - /// be tabled immediately and with a shorter voting/enactment period. - type FastTrackOrigin = EnsureRootOrTwoThirdsTechnicalCommittee; - type InstantOrigin = EnsureRootOrAllTechnicalCommittee; - type InstantAllowed = ConstBool; - type FastTrackVotingPeriod = FastTrackVotingPeriod; - // To cancel a proposal which has been passed, 2/3 of the council must agree to it. - type CancellationOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type BlacklistOrigin = EnsureRoot; - // To cancel a proposal before it has been passed, the technical committee must be unanimous or - // Root must agree. - type CancelProposalOrigin = EnsureRootOrAllTechnicalCommittee; - // Any single technical committee member may veto a coming council proposal, however they can - // only do it once and it lasts only for the cooloff period. - type VetoOrigin = pallet_collective::EnsureMember; - type CooloffPeriod = CooloffPeriod; - type Slash = Treasury; - type Scheduler = Scheduler; - type PalletsOrigin = OriginCaller; - type MaxVotes = ConstU32<100>; - type WeightInfo = pallet_democracy::weights::SubstrateWeight; - type MaxProposals = ConstU32<100>; - type Preimages = Preimage; - type MaxDeposits = ConstU32<100>; - type MaxBlacklisted = ConstU32<100>; - type SubmitOrigin = EnsureSigned; -} - impl orml_auction::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; @@ -1804,6 +1630,10 @@ construct_runtime!( TechnicalCommittee: pallet_collective:: = 67, TechnicalCommitteeMembership: pallet_membership:: = 68, Democracy: pallet_democracy = 69, + ConvictionVoting: pallet_conviction_voting = 160, + Referenda: pallet_referenda = 161, + Origins: governance::custom_origins::{Origin} = 162, + Whitelist: pallet_whitelist = 163, // Oracle // diff --git a/runtime/karura/Cargo.toml b/runtime/karura/Cargo.toml index 5b9725758..943c011c2 100644 --- a/runtime/karura/Cargo.toml +++ b/runtime/karura/Cargo.toml @@ -13,6 +13,9 @@ scale-info = { workspace = true } serde_json = { workspace = true, features = ["alloc"] } hex = { workspace = true } hex-literal = { workspace = true } +strum = { workspace = true } +strum_macros = { workspace = true } +lazy_static = { workspace = true } # substrate frame-executive = { workspace = true } @@ -39,6 +42,9 @@ pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-treasury = { workspace = true } pallet-utility = { workspace = true } pallet-preimage = { workspace = true } +pallet-conviction-voting = { workspace = true } +pallet-referenda = { workspace = true } +pallet-whitelist = { workspace = true } sp-api = { workspace = true } sp-application-crypto = { workspace = true } sp-block-builder = { workspace = true } @@ -151,6 +157,7 @@ std = [ "hex/std", "scale-info/std", "serde_json/std", + "strum/std", "frame-benchmarking/std", "frame-executive/std", @@ -194,6 +201,9 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "substrate-wasm-builder", + "pallet-conviction-voting/std", + "pallet-referenda/std", + "pallet-whitelist/std", "cumulus-pallet-aura-ext/std", "cumulus-pallet-dmp-queue/std", @@ -291,6 +301,9 @@ runtime-benchmarks = [ "pallet-utility/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "sp-staking/runtime-benchmarks", + "pallet-conviction-voting/runtime-benchmarks", + "pallet-referenda/runtime-benchmarks", + "pallet-whitelist/runtime-benchmarks", "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", @@ -353,6 +366,9 @@ try-runtime = [ "pallet-treasury/try-runtime", "pallet-utility/try-runtime", "pallet-bags-list/try-runtime", + "pallet-conviction-voting/try-runtime", + "pallet-referenda/try-runtime", + "pallet-whitelist/try-runtime", "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-dmp-queue/try-runtime", diff --git a/runtime/karura/src/governance/councils.rs b/runtime/karura/src/governance/councils.rs new file mode 100644 index 000000000..31bc170ce --- /dev/null +++ b/runtime/karura/src/governance/councils.rs @@ -0,0 +1,144 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Councils for Gov1 and Gov2 + +use super::*; + +parameter_types! { + pub const GeneralCouncilMotionDuration: BlockNumber = 3 * DAYS; + pub const CouncilDefaultMaxProposals: u32 = 20; + pub const CouncilDefaultMaxMembers: u32 = 30; + pub MaxProposalWeight: Weight = Perbill::from_percent(50) * RuntimeBlockWeights::get().max_block; +} + +impl pallet_collective::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = GeneralCouncilMotionDuration; + type MaxProposals = CouncilDefaultMaxProposals; + type MaxMembers = CouncilDefaultMaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type SetMembersOrigin = EnsureRoot; + type WeightInfo = (); + type MaxProposalWeight = MaxProposalWeight; +} + +impl pallet_membership::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type RemoveOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type SwapOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type ResetOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type PrimeOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type MembershipInitialized = GeneralCouncil; + type MembershipChanged = GeneralCouncil; + type MaxMembers = CouncilDefaultMaxMembers; + type WeightInfo = (); +} + +parameter_types! { + pub const FinancialCouncilMotionDuration: BlockNumber = 3 * DAYS; +} + +impl pallet_collective::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = FinancialCouncilMotionDuration; + type MaxProposals = CouncilDefaultMaxProposals; + type MaxMembers = CouncilDefaultMaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type SetMembersOrigin = EnsureRoot; + type WeightInfo = (); + type MaxProposalWeight = MaxProposalWeight; +} + +impl pallet_membership::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type MembershipInitialized = FinancialCouncil; + type MembershipChanged = FinancialCouncil; + type MaxMembers = CouncilDefaultMaxMembers; + type WeightInfo = (); +} + +parameter_types! { + pub const HomaCouncilMotionDuration: BlockNumber = 3 * DAYS; +} + +impl pallet_collective::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = HomaCouncilMotionDuration; + type MaxProposals = CouncilDefaultMaxProposals; + type MaxMembers = CouncilDefaultMaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type SetMembersOrigin = EnsureRoot; + type WeightInfo = (); + type MaxProposalWeight = MaxProposalWeight; +} + +impl pallet_membership::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type MembershipInitialized = HomaCouncil; + type MembershipChanged = HomaCouncil; + type MaxMembers = CouncilDefaultMaxMembers; + type WeightInfo = (); +} + +parameter_types! { + pub const TechnicalCommitteeMotionDuration: BlockNumber = 3 * DAYS; +} + +impl pallet_collective::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = TechnicalCommitteeMotionDuration; + type MaxProposals = CouncilDefaultMaxProposals; + type MaxMembers = CouncilDefaultMaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type SetMembersOrigin = EnsureRoot; + type WeightInfo = (); + type MaxProposalWeight = MaxProposalWeight; +} + +impl pallet_membership::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type MembershipInitialized = TechnicalCommittee; + type MembershipChanged = TechnicalCommittee; + type MaxMembers = CouncilDefaultMaxMembers; + type WeightInfo = (); +} diff --git a/runtime/karura/src/governance/democracy.rs b/runtime/karura/src/governance/democracy.rs new file mode 100644 index 000000000..3a809c556 --- /dev/null +++ b/runtime/karura/src/governance/democracy.rs @@ -0,0 +1,75 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Democracy config for Gov1 + +use crate::*; + +parameter_types! { + pub const LaunchPeriod: BlockNumber = 5 * DAYS; + pub const VotingPeriod: BlockNumber = 5 * DAYS; + pub const FastTrackVotingPeriod: BlockNumber = 3 * HOURS; + pub MinimumDeposit: Balance = 100 * dollar(KAR); + pub const EnactmentPeriod: BlockNumber = 2 * DAYS; + pub const VoteLockingPeriod: BlockNumber = 7 * DAYS; + pub const CooloffPeriod: BlockNumber = 7 * DAYS; +} + +impl pallet_democracy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type EnactmentPeriod = EnactmentPeriod; + type LaunchPeriod = LaunchPeriod; + type VotingPeriod = VotingPeriod; + type VoteLockingPeriod = VoteLockingPeriod; + type MinimumDeposit = MinimumDeposit; + /// A straight majority of the council can decide what their next motion is. + type ExternalOrigin = EnsureRootOrHalfGeneralCouncil; + /// A majority can have the next scheduled referendum be a straight majority-carries vote. + type ExternalMajorityOrigin = EnsureRootOrHalfGeneralCouncil; + /// A unanimous council can have the next scheduled referendum be a straight default-carries + /// (NTB) vote. + type ExternalDefaultOrigin = EnsureRootOrAllGeneralCouncil; + /// Two thirds of the technical committee can have an ExternalMajority/ExternalDefault vote + /// be tabled immediately and with a shorter voting/enactment period. + type FastTrackOrigin = EnsureRootOrTwoThirdsTechnicalCommittee; + type InstantOrigin = EnsureRootOrAllTechnicalCommittee; + type InstantAllowed = ConstBool; + type FastTrackVotingPeriod = FastTrackVotingPeriod; + // To cancel a proposal which has been passed, 2/3 of the council must agree to it. + type CancellationOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type BlacklistOrigin = EnsureRoot; + // To cancel a proposal before it has been passed, the technical committee must be unanimous or + // Root must agree. + type CancelProposalOrigin = EnsureRootOrAllTechnicalCommittee; + // Any single technical committee member may veto a coming council proposal, however they can + // only do it once and it lasts only for the cooloff period. + type VetoOrigin = pallet_collective::EnsureMember; + type CooloffPeriod = CooloffPeriod; + type Slash = Treasury; + type Scheduler = Scheduler; + type PalletsOrigin = OriginCaller; + type MaxVotes = ConstU32<100>; + //TODO: might need to weight for Karura + type WeightInfo = pallet_democracy::weights::SubstrateWeight; + type MaxProposals = ConstU32<100>; + type Preimages = Preimage; + type MaxDeposits = ConstU32<100>; + type MaxBlacklisted = ConstU32<100>; + type SubmitOrigin = EnsureSigned; +} diff --git a/runtime/karura/src/governance/mod.rs b/runtime/karura/src/governance/mod.rs new file mode 100644 index 000000000..9d8000ef0 --- /dev/null +++ b/runtime/karura/src/governance/mod.rs @@ -0,0 +1,30 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Governance configurations + +pub mod councils; +mod democracy; +pub mod referenda; + +use super::*; + +mod origins; +pub use origins::{custom_origins, GeneralAdmin, ReferendumCanceller, ReferendumKiller, WhitelistedCaller}; +mod tracks; +pub use tracks::TracksInfo; diff --git a/runtime/karura/src/governance/origins.rs b/runtime/karura/src/governance/origins.rs new file mode 100644 index 000000000..7f7d7237a --- /dev/null +++ b/runtime/karura/src/governance/origins.rs @@ -0,0 +1,79 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Custom origins for governance interventions. + +pub use custom_origins::*; + +#[frame_support::pallet] +pub mod custom_origins { + use frame_support::pallet_prelude::*; + use strum_macros::EnumString; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[derive(PartialEq, Eq, Clone, MaxEncodedLen, Encode, Decode, TypeInfo, RuntimeDebug, EnumString)] + #[strum(serialize_all = "snake_case")] + #[pallet::origin] + pub enum Origin { + /// Origin able to dispatch a whitelisted call. + WhitelistedCaller, + /// General admin + GeneralAdmin, + /// Origin able to cancel referenda. + ReferendumCanceller, + /// Origin able to kill referenda. + ReferendumKiller, + } + + macro_rules! decl_unit_ensures { + ( $name:ident: $success_type:ty = $success:expr ) => { + pub struct $name; + impl> + From> + EnsureOrigin for $name + { + type Success = $success_type; + fn try_origin(o: O) -> Result { + o.into().and_then(|o| match o { + Origin::$name => Ok($success), + r => Err(O::from(r)), + }) + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(O::from(Origin::$name)) + } + } + }; + ( $name:ident ) => { decl_unit_ensures! { $name : () = () } }; + ( $name:ident: $success_type:ty = $success:expr, $( $rest:tt )* ) => { + decl_unit_ensures! { $name: $success_type = $success } + decl_unit_ensures! { $( $rest )* } + }; + ( $name:ident, $( $rest:tt )* ) => { + decl_unit_ensures! { $name } + decl_unit_ensures! { $( $rest )* } + }; + () => {} + } + decl_unit_ensures!(ReferendumCanceller, ReferendumKiller, WhitelistedCaller, GeneralAdmin); +} diff --git a/runtime/karura/src/governance/referenda.rs b/runtime/karura/src/governance/referenda.rs new file mode 100644 index 000000000..db7cca239 --- /dev/null +++ b/runtime/karura/src/governance/referenda.rs @@ -0,0 +1,94 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! # Gov2 config +//! Includes runtime configs for these substrate pallets: +//! 1. pallet-conviction-voting +//! 2. pallet-whitelist +//! 3. pallet-referenda + +use super::*; +use frame_support::traits::{EitherOf, MapSuccess}; +use frame_system::EnsureRootWithSuccess; +use sp_runtime::traits::Replace; + +parameter_types! { + pub const VoteLockingPeriod: BlockNumber = DAYS; +} + +impl pallet_conviction_voting::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type Polls = Referenda; + type MaxTurnout = frame_support::traits::TotalIssuanceOf; + // Maximum number of concurrent votes an account may have + type MaxVotes = ConstU32<20>; + // Minimum period of vote locking + type VoteLockingPeriod = VoteLockingPeriod; +} + +// Origin for general admin or root +pub type GeneralAdminOrRoot = EitherOf, origins::GeneralAdmin>; + +impl custom_origins::Config for Runtime {} + +// The purpose of this pallet is to queue calls to be dispatched as by root later => the Dispatch +// origin corresponds to the Gov2 Whitelist track. +impl pallet_whitelist::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type WhitelistOrigin = EitherOf< + EnsureRootWithSuccess>, + MapSuccess< + pallet_collective::EnsureProportionAtLeast, + Replace>, + >, + >; + type DispatchWhitelistedOrigin = EitherOf, WhitelistedCaller>; + type Preimages = Preimage; +} + +pallet_referenda::impl_tracksinfo_get!(TracksInfo, Balance, BlockNumber); + +parameter_types! { + pub const AlarmInterval: BlockNumber = 1; + pub SubmissionDeposit: Balance = 10 * dollar(KAR); + pub const UndecidingTimeout: BlockNumber = 14 * DAYS; +} + +impl pallet_referenda::Config for Runtime { + type WeightInfo = (); + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type Scheduler = Scheduler; + type Currency = Balances; + type SubmitOrigin = frame_system::EnsureSigned; + type CancelOrigin = EitherOf, ReferendumCanceller>; + type KillOrigin = EitherOf, ReferendumKiller>; + type Slash = Treasury; + type Votes = pallet_conviction_voting::VotesOf; + type Tally = pallet_conviction_voting::TallyOf; + type SubmissionDeposit = SubmissionDeposit; + type MaxQueued = ConstU32<100>; + type UndecidingTimeout = UndecidingTimeout; + type AlarmInterval = AlarmInterval; + type Tracks = TracksInfo; + type Preimages = Preimage; +} diff --git a/runtime/karura/src/governance/tracks.rs b/runtime/karura/src/governance/tracks.rs new file mode 100644 index 000000000..47d55f9de --- /dev/null +++ b/runtime/karura/src/governance/tracks.rs @@ -0,0 +1,178 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Track configurations for governance. + +use super::*; +use pallet_referenda::Curve; +use sp_std::str::FromStr; + +const fn percent(x: i32) -> sp_runtime::FixedI64 { + sp_runtime::FixedI64::from_rational(x as u128, 100) +} +const fn permill(x: i32) -> sp_runtime::FixedI64 { + sp_runtime::FixedI64::from_rational(x as u128, 1000) +} + +lazy_static::lazy_static! { +static ref TRACKS_DATA: [(u16, pallet_referenda::TrackInfo); 5] = [ + ( + 0, + pallet_referenda::TrackInfo { + // Name of this track. + name: "root", + // A limit for the number of referenda on this track that can be being decided at once. + // For Root origin this should generally be just one. + max_deciding: 1, + // Amount that must be placed on deposit before a decision can be made. + decision_deposit: 20_000 * dollar(KAR), + // Amount of time this must be submitted for before a decision can be made. + prepare_period: 2 * HOURS, + // Amount of time that a decision may take to be approved prior to cancellation. + decision_period: 7 * DAYS, + // Amount of time that the approval criteria must hold before it can be approved. + confirm_period: DAYS, + // Minimum amount of time that an approved proposal must be in the dispatch queue. + min_enactment_period: DAYS, + // Minimum aye votes as percentage of overall conviction-weighted votes needed for + // approval as a function of time into decision period. + min_approval: Curve::make_reciprocal(2, 7, percent(80), percent(50), percent(100)), + // Minimum pre-conviction aye-votes ("support") as percentage of overall population that + // is needed for approval as a function of time into decision period. + min_support: Curve::make_linear(7, 7, permill(1), percent(40)), + }, + ), + ( + 1, + pallet_referenda::TrackInfo { + name: "whitelisted_caller", + max_deciding: 5, + decision_deposit: 5_000 * dollar(KAR), + prepare_period: 10 * MINUTES, + decision_period: 5 * DAYS, + confirm_period: 10 * MINUTES, + min_enactment_period: 30 * MINUTES, + min_approval: Curve::make_reciprocal(1, 5 * 2, percent(90), percent(50), percent(100)), + min_support: Curve::make_reciprocal(1, 5 * 2, percent(1), percent(0), percent(5)), + }, + ), + ( + 2, + pallet_referenda::TrackInfo { + name: "general_admin", + max_deciding: 10, + decision_deposit: 1000 * dollar(KAR), + prepare_period: HOURS, + decision_period: 5 * DAYS, + confirm_period: 6 * HOURS, + min_enactment_period: DAYS, + min_approval: Curve::make_reciprocal(4, 14, percent(80), percent(50), percent(100)), + min_support: Curve::make_reciprocal(7, 14, percent(10), percent(0), percent(50)), + }, + ), + ( + 3, + pallet_referenda::TrackInfo { + name: "referendum_canceller", + max_deciding: 20, + decision_deposit: 2 * 1000 * dollar(KAR), + prepare_period: HOURS, + decision_period: 5 * DAYS, + confirm_period: 3 * HOURS, + min_enactment_period: 10 * MINUTES, + min_approval: Curve::make_reciprocal(1, 5 * 2, percent(96), percent(50), percent(100)), + min_support: Curve::make_reciprocal(1, 5 * 2, percent(1), percent(0), percent(10)), + }, + ), + ( + 4, + pallet_referenda::TrackInfo { + name: "referendum_killer", + max_deciding: 20, + decision_deposit: 4 * 1000 * dollar(KAR), + prepare_period: HOURS, + decision_period: 5 * DAYS, + confirm_period: 3 * HOURS, + min_enactment_period: 10 * MINUTES, + min_approval: Curve::make_reciprocal(1, 5 * 2, percent(96), percent(50), percent(100)), + min_support: Curve::make_reciprocal(1, 5 * 2, percent(1), percent(0), percent(10)), + }, + ), +]; +} + +pub struct TracksInfo; +impl pallet_referenda::TracksInfo for TracksInfo { + type Id = u16; + type RuntimeOrigin = ::PalletsOrigin; + fn tracks() -> &'static [(Self::Id, pallet_referenda::TrackInfo)] { + &TRACKS_DATA[..] + } + fn track_for(id: &Self::RuntimeOrigin) -> Result { + if let Ok(system_origin) = frame_system::RawOrigin::try_from(id.clone()) { + match system_origin { + frame_system::RawOrigin::Root => { + if let Some((track_id, _)) = Self::tracks().iter().find(|(_, track)| track.name == "root") { + Ok(*track_id) + } else { + Err(()) + } + } + _ => Err(()), + } + } else if let Ok(custom_origin) = custom_origins::Origin::try_from(id.clone()) { + if let Some((track_id, _)) = Self::tracks().iter().find(|(_, track)| { + if let Ok(track_custom_origin) = custom_origins::Origin::from_str(track.name) { + track_custom_origin == custom_origin + } else { + false + } + }) { + Ok(*track_id) + } else { + Err(()) + } + } else { + Err(()) + } + } +} + +#[test] +/// To ensure voters are always locked into their vote +fn vote_locking_always_longer_than_enactment_period() { + for (_, track) in (&TRACKS_DATA).iter() { + assert!( + ::VoteLockingPeriod::get() >= track.min_enactment_period, + "Track {} has enactment period {} < vote locking period {}", + track.name, + track.min_enactment_period, + ::VoteLockingPeriod::get(), + ); + } +} + +#[test] +fn all_tracks_have_origins() { + for (_, track) in (&TRACKS_DATA).iter() { + // check name.into() is successful either converts into "root" or custom origin + let track_is_root = track.name == "root"; + let track_has_custom_origin = custom_origins::Origin::from_str(track.name).is_ok(); + assert!(track_is_root || track_has_custom_origin); + } +} diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index 87a269fb2..201034354 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -76,7 +76,7 @@ use frame_support::{ traits::{ fungible::HoldConsideration, tokens::{PayFromAccount, UnityAssetBalanceConversion}, - ConstBool, ConstU128, ConstU32, Contains, ContainsLengthBound, Currency as PalletCurrency, Currency, + ConstBool, ConstU128, ConstU16, ConstU32, Contains, ContainsLengthBound, Currency as PalletCurrency, Currency, EnsureOrigin, EqualPrivilegeOnly, Get, Imbalance, InstanceFilter, LinearStoragePrice, LockIdentifier, OnRuntimeUpgrade, OnUnbalanced, SortedMembers, }, @@ -123,9 +123,11 @@ pub use nutsfinance_stable_asset; mod authority; mod benchmarking; pub mod constants; +pub mod governance; /// Weights for pallets used in the runtime. mod weights; pub mod xcm_config; +use governance::councils::*; /// This runtime version. #[sp_version::runtime_version] @@ -405,129 +407,6 @@ impl pallet_sudo::Config for Runtime { type WeightInfo = (); } -parameter_types! { - pub const GeneralCouncilMotionDuration: BlockNumber = 3 * DAYS; - pub const CouncilDefaultMaxProposals: u32 = 20; - pub const CouncilDefaultMaxMembers: u32 = 30; - pub MaxProposalWeight: Weight = Perbill::from_percent(50) * RuntimeBlockWeights::get().max_block; -} - -impl pallet_collective::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type Proposal = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type MotionDuration = GeneralCouncilMotionDuration; - type MaxProposals = CouncilDefaultMaxProposals; - type MaxMembers = CouncilDefaultMaxMembers; - type DefaultVote = pallet_collective::PrimeDefaultVote; - type SetMembersOrigin = EnsureRoot; - type WeightInfo = (); - type MaxProposalWeight = MaxProposalWeight; -} - -impl pallet_membership::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type AddOrigin = EnsureRootOrThreeFourthsGeneralCouncil; - type RemoveOrigin = EnsureRootOrThreeFourthsGeneralCouncil; - type SwapOrigin = EnsureRootOrThreeFourthsGeneralCouncil; - type ResetOrigin = EnsureRootOrThreeFourthsGeneralCouncil; - type PrimeOrigin = EnsureRootOrThreeFourthsGeneralCouncil; - type MembershipInitialized = GeneralCouncil; - type MembershipChanged = GeneralCouncil; - type MaxMembers = CouncilDefaultMaxMembers; - type WeightInfo = (); -} - -parameter_types! { - pub const FinancialCouncilMotionDuration: BlockNumber = 3 * DAYS; -} - -impl pallet_collective::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type Proposal = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type MotionDuration = FinancialCouncilMotionDuration; - type MaxProposals = CouncilDefaultMaxProposals; - type MaxMembers = CouncilDefaultMaxMembers; - type DefaultVote = pallet_collective::PrimeDefaultVote; - type SetMembersOrigin = EnsureRoot; - type WeightInfo = (); - type MaxProposalWeight = MaxProposalWeight; -} - -impl pallet_membership::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type MembershipInitialized = FinancialCouncil; - type MembershipChanged = FinancialCouncil; - type MaxMembers = CouncilDefaultMaxMembers; - type WeightInfo = (); -} - -parameter_types! { - pub const HomaCouncilMotionDuration: BlockNumber = 3 * DAYS; -} - -impl pallet_collective::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type Proposal = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type MotionDuration = HomaCouncilMotionDuration; - type MaxProposals = CouncilDefaultMaxProposals; - type MaxMembers = CouncilDefaultMaxMembers; - type DefaultVote = pallet_collective::PrimeDefaultVote; - type SetMembersOrigin = EnsureRoot; - type WeightInfo = (); - type MaxProposalWeight = MaxProposalWeight; -} - -impl pallet_membership::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type MembershipInitialized = HomaCouncil; - type MembershipChanged = HomaCouncil; - type MaxMembers = CouncilDefaultMaxMembers; - type WeightInfo = (); -} - -parameter_types! { - pub const TechnicalCommitteeMotionDuration: BlockNumber = 3 * DAYS; -} - -impl pallet_collective::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type Proposal = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type MotionDuration = TechnicalCommitteeMotionDuration; - type MaxProposals = CouncilDefaultMaxProposals; - type MaxMembers = CouncilDefaultMaxMembers; - type DefaultVote = pallet_collective::PrimeDefaultVote; - type SetMembersOrigin = EnsureRoot; - type WeightInfo = (); - type MaxProposalWeight = MaxProposalWeight; -} - -impl pallet_membership::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type MembershipInitialized = TechnicalCommittee; - type MembershipChanged = TechnicalCommittee; - type MaxMembers = CouncilDefaultMaxMembers; - type WeightInfo = (); -} - impl pallet_membership::Config for Runtime { type RuntimeEvent = RuntimeEvent; type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; @@ -667,60 +546,6 @@ impl pallet_tips::Config for Runtime { type WeightInfo = (); } -parameter_types! { - pub const LaunchPeriod: BlockNumber = 5 * DAYS; - pub const VotingPeriod: BlockNumber = 5 * DAYS; - pub const FastTrackVotingPeriod: BlockNumber = 3 * HOURS; - pub MinimumDeposit: Balance = 100 * dollar(KAR); - pub const EnactmentPeriod: BlockNumber = 2 * DAYS; - pub const VoteLockingPeriod: BlockNumber = 7 * DAYS; - pub const CooloffPeriod: BlockNumber = 7 * DAYS; -} - -impl pallet_democracy::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type EnactmentPeriod = EnactmentPeriod; - type LaunchPeriod = LaunchPeriod; - type VotingPeriod = VotingPeriod; - type VoteLockingPeriod = VoteLockingPeriod; - type MinimumDeposit = MinimumDeposit; - /// A straight majority of the council can decide what their next motion is. - type ExternalOrigin = EnsureRootOrHalfGeneralCouncil; - /// A majority can have the next scheduled referendum be a straight majority-carries vote. - type ExternalMajorityOrigin = EnsureRootOrHalfGeneralCouncil; - /// A unanimous council can have the next scheduled referendum be a straight default-carries - /// (NTB) vote. - type ExternalDefaultOrigin = EnsureRootOrAllGeneralCouncil; - /// Two thirds of the technical committee can have an ExternalMajority/ExternalDefault vote - /// be tabled immediately and with a shorter voting/enactment period. - type FastTrackOrigin = EnsureRootOrTwoThirdsTechnicalCommittee; - type InstantOrigin = EnsureRootOrAllTechnicalCommittee; - type InstantAllowed = ConstBool; - type FastTrackVotingPeriod = FastTrackVotingPeriod; - // To cancel a proposal which has been passed, 2/3 of the council must agree to it. - type CancellationOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type BlacklistOrigin = EnsureRoot; - // To cancel a proposal before it has been passed, the technical committee must be unanimous or - // Root must agree. - type CancelProposalOrigin = EnsureRootOrAllTechnicalCommittee; - // Any single technical committee member may veto a coming council proposal, however they can - // only do it once and it lasts only for the cooloff period. - type VetoOrigin = pallet_collective::EnsureMember; - type CooloffPeriod = CooloffPeriod; - type Slash = Treasury; - type Scheduler = Scheduler; - type PalletsOrigin = OriginCaller; - type MaxVotes = ConstU32<100>; - //TODO: might need to weight for Karura - type WeightInfo = pallet_democracy::weights::SubstrateWeight; - type MaxProposals = ConstU32<100>; - type Preimages = Preimage; - type MaxDeposits = ConstU32<100>; - type MaxBlacklisted = ConstU32<100>; - type SubmitOrigin = EnsureSigned; -} - impl orml_auction::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; @@ -1807,6 +1632,10 @@ construct_runtime!( TechnicalCommittee: pallet_collective:: = 67, TechnicalCommitteeMembership: pallet_membership:: = 68, Democracy: pallet_democracy = 69, + ConvictionVoting: pallet_conviction_voting = 160, + Referenda: pallet_referenda = 161, + Origins: governance::custom_origins::{Origin} = 162, + Whitelist: pallet_whitelist = 163, // Oracle // diff --git a/runtime/mandala/Cargo.toml b/runtime/mandala/Cargo.toml index 403a81211..3f49a2922 100644 --- a/runtime/mandala/Cargo.toml +++ b/runtime/mandala/Cargo.toml @@ -13,6 +13,9 @@ scale-info = { workspace = true } serde_json = { workspace = true, features = ["alloc"] } hex = { workspace = true } hex-literal = { workspace = true } +strum = { workspace = true } +strum_macros = { workspace = true } +lazy_static = { workspace = true } # substrate frame-executive = { workspace = true } @@ -43,6 +46,9 @@ pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-treasury = { workspace = true } pallet-utility = { workspace = true } pallet-preimage = { workspace = true } +pallet-conviction-voting = { workspace = true } +pallet-referenda = { workspace = true } +pallet-whitelist = { workspace = true } sp-api = { workspace = true } sp-application-crypto = { workspace = true } sp-block-builder = { workspace = true } @@ -163,6 +169,7 @@ std = [ "hex/std", "scale-info/std", "serde_json/std", + "strum/std", "frame-benchmarking/std", "frame-executive/std", @@ -210,6 +217,9 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "substrate-wasm-builder", + "pallet-conviction-voting/std", + "pallet-referenda/std", + "pallet-whitelist/std", "cumulus-pallet-aura-ext/std", "cumulus-pallet-dmp-queue/std", @@ -310,6 +320,9 @@ runtime-benchmarks = [ "pallet-utility/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "sp-staking/runtime-benchmarks", + "pallet-conviction-voting/runtime-benchmarks", + "pallet-referenda/runtime-benchmarks", + "pallet-whitelist/runtime-benchmarks", "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", @@ -382,6 +395,9 @@ try-runtime = [ "pallet-transaction-payment/try-runtime", "pallet-treasury/try-runtime", "pallet-utility/try-runtime", + "pallet-conviction-voting/try-runtime", + "pallet-referenda/try-runtime", + "pallet-whitelist/try-runtime", "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-dmp-queue/try-runtime", diff --git a/runtime/mandala/src/governance/councils.rs b/runtime/mandala/src/governance/councils.rs new file mode 100644 index 000000000..083513cce --- /dev/null +++ b/runtime/mandala/src/governance/councils.rs @@ -0,0 +1,144 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Councils for Gov1 and Gov2 + +use super::*; + +parameter_types! { + pub const GeneralCouncilMotionDuration: BlockNumber = 7 * DAYS; + pub const CouncilDefaultMaxProposals: u32 = 100; + pub const CouncilDefaultMaxMembers: u32 = 100; + pub MaxProposalWeight: Weight = Perbill::from_percent(50) * RuntimeBlockWeights::get().max_block; +} + +impl pallet_collective::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = GeneralCouncilMotionDuration; + type MaxProposals = CouncilDefaultMaxProposals; + type MaxMembers = CouncilDefaultMaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type SetMembersOrigin = EnsureRoot; + type WeightInfo = (); + type MaxProposalWeight = MaxProposalWeight; +} + +impl pallet_membership::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type RemoveOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type SwapOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type ResetOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type PrimeOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type MembershipInitialized = GeneralCouncil; + type MembershipChanged = GeneralCouncil; + type MaxMembers = CouncilDefaultMaxMembers; + type WeightInfo = (); +} + +parameter_types! { + pub const FinancialCouncilMotionDuration: BlockNumber = 7 * DAYS; +} + +impl pallet_collective::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = FinancialCouncilMotionDuration; + type MaxProposals = CouncilDefaultMaxProposals; + type MaxMembers = CouncilDefaultMaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type SetMembersOrigin = EnsureRoot; + type WeightInfo = (); + type MaxProposalWeight = MaxProposalWeight; +} + +impl pallet_membership::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type MembershipInitialized = FinancialCouncil; + type MembershipChanged = FinancialCouncil; + type MaxMembers = CouncilDefaultMaxMembers; + type WeightInfo = (); +} + +parameter_types! { + pub const HomaCouncilMotionDuration: BlockNumber = 7 * DAYS; +} + +impl pallet_collective::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = HomaCouncilMotionDuration; + type MaxProposals = CouncilDefaultMaxProposals; + type MaxMembers = CouncilDefaultMaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type SetMembersOrigin = EnsureRoot; + type WeightInfo = (); + type MaxProposalWeight = MaxProposalWeight; +} + +impl pallet_membership::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type MembershipInitialized = HomaCouncil; + type MembershipChanged = HomaCouncil; + type MaxMembers = CouncilDefaultMaxMembers; + type WeightInfo = (); +} + +parameter_types! { + pub const TechnicalCommitteeMotionDuration: BlockNumber = 7 * DAYS; +} + +impl pallet_collective::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = TechnicalCommitteeMotionDuration; + type MaxProposals = CouncilDefaultMaxProposals; + type MaxMembers = CouncilDefaultMaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type SetMembersOrigin = EnsureRoot; + type WeightInfo = (); + type MaxProposalWeight = MaxProposalWeight; +} + +impl pallet_membership::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type MembershipInitialized = TechnicalCommittee; + type MembershipChanged = TechnicalCommittee; + type MaxMembers = CouncilDefaultMaxMembers; + type WeightInfo = (); +} diff --git a/runtime/mandala/src/governance/democracy.rs b/runtime/mandala/src/governance/democracy.rs new file mode 100644 index 000000000..6373a0300 --- /dev/null +++ b/runtime/mandala/src/governance/democracy.rs @@ -0,0 +1,74 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Democracy config for Gov1 + +use crate::*; + +parameter_types! { + pub const LaunchPeriod: BlockNumber = 2 * HOURS; + pub const VotingPeriod: BlockNumber = HOURS; + pub const FastTrackVotingPeriod: BlockNumber = HOURS; + pub MinimumDeposit: Balance = 100 * cent(ACA); + pub const EnactmentPeriod: BlockNumber = MINUTES; + pub const CooloffPeriod: BlockNumber = MINUTES; +} + +impl pallet_democracy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type EnactmentPeriod = EnactmentPeriod; + type LaunchPeriod = LaunchPeriod; + type VotingPeriod = VotingPeriod; + type VoteLockingPeriod = EnactmentPeriod; // Same as EnactmentPeriod + type MinimumDeposit = MinimumDeposit; + /// A straight majority of the council can decide what their next motion is. + type ExternalOrigin = EnsureRootOrHalfGeneralCouncil; + /// A majority can have the next scheduled referendum be a straight majority-carries vote. + type ExternalMajorityOrigin = EnsureRootOrHalfGeneralCouncil; + /// A unanimous council can have the next scheduled referendum be a straight default-carries + /// (NTB) vote. + type ExternalDefaultOrigin = EnsureRootOrAllGeneralCouncil; + /// Two thirds of the technical committee can have an ExternalMajority/ExternalDefault vote + /// be tabled immediately and with a shorter voting/enactment period. + type FastTrackOrigin = EnsureRootOrTwoThirdsTechnicalCommittee; + type InstantOrigin = EnsureRootOrAllTechnicalCommittee; + type InstantAllowed = ConstBool; + type FastTrackVotingPeriod = FastTrackVotingPeriod; + // To cancel a proposal which has been passed, 2/3 of the council must agree to it. + type CancellationOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + type BlacklistOrigin = EnsureRoot; + // To cancel a proposal before it has been passed, the technical committee must be unanimous or + // Root must agree. + type CancelProposalOrigin = EnsureRootOrAllTechnicalCommittee; + // Any single technical committee member may veto a coming council proposal, however they can + // only do it once and it lasts only for the cooloff period. + type VetoOrigin = pallet_collective::EnsureMember; + type CooloffPeriod = CooloffPeriod; + type Slash = Treasury; + type Scheduler = Scheduler; + type PalletsOrigin = OriginCaller; + type MaxVotes = ConstU32<100>; + //TODO: might need to weight for Mandala + type WeightInfo = pallet_democracy::weights::SubstrateWeight; + type MaxProposals = CouncilDefaultMaxProposals; + type Preimages = Preimage; + type MaxDeposits = ConstU32<100>; + type MaxBlacklisted = ConstU32<100>; + type SubmitOrigin = EnsureSigned; +} diff --git a/runtime/mandala/src/governance/mod.rs b/runtime/mandala/src/governance/mod.rs new file mode 100644 index 000000000..9d8000ef0 --- /dev/null +++ b/runtime/mandala/src/governance/mod.rs @@ -0,0 +1,30 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Governance configurations + +pub mod councils; +mod democracy; +pub mod referenda; + +use super::*; + +mod origins; +pub use origins::{custom_origins, GeneralAdmin, ReferendumCanceller, ReferendumKiller, WhitelistedCaller}; +mod tracks; +pub use tracks::TracksInfo; diff --git a/runtime/mandala/src/governance/origins.rs b/runtime/mandala/src/governance/origins.rs new file mode 100644 index 000000000..7f7d7237a --- /dev/null +++ b/runtime/mandala/src/governance/origins.rs @@ -0,0 +1,79 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Custom origins for governance interventions. + +pub use custom_origins::*; + +#[frame_support::pallet] +pub mod custom_origins { + use frame_support::pallet_prelude::*; + use strum_macros::EnumString; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[derive(PartialEq, Eq, Clone, MaxEncodedLen, Encode, Decode, TypeInfo, RuntimeDebug, EnumString)] + #[strum(serialize_all = "snake_case")] + #[pallet::origin] + pub enum Origin { + /// Origin able to dispatch a whitelisted call. + WhitelistedCaller, + /// General admin + GeneralAdmin, + /// Origin able to cancel referenda. + ReferendumCanceller, + /// Origin able to kill referenda. + ReferendumKiller, + } + + macro_rules! decl_unit_ensures { + ( $name:ident: $success_type:ty = $success:expr ) => { + pub struct $name; + impl> + From> + EnsureOrigin for $name + { + type Success = $success_type; + fn try_origin(o: O) -> Result { + o.into().and_then(|o| match o { + Origin::$name => Ok($success), + r => Err(O::from(r)), + }) + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(O::from(Origin::$name)) + } + } + }; + ( $name:ident ) => { decl_unit_ensures! { $name : () = () } }; + ( $name:ident: $success_type:ty = $success:expr, $( $rest:tt )* ) => { + decl_unit_ensures! { $name: $success_type = $success } + decl_unit_ensures! { $( $rest )* } + }; + ( $name:ident, $( $rest:tt )* ) => { + decl_unit_ensures! { $name } + decl_unit_ensures! { $( $rest )* } + }; + () => {} + } + decl_unit_ensures!(ReferendumCanceller, ReferendumKiller, WhitelistedCaller, GeneralAdmin); +} diff --git a/runtime/mandala/src/governance/referenda.rs b/runtime/mandala/src/governance/referenda.rs new file mode 100644 index 000000000..b223a17e7 --- /dev/null +++ b/runtime/mandala/src/governance/referenda.rs @@ -0,0 +1,94 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! # Gov2 config +//! Includes runtime configs for these substrate pallets: +//! 1. pallet-conviction-voting +//! 2. pallet-whitelist +//! 3. pallet-referenda + +use super::*; +use frame_support::traits::{EitherOf, MapSuccess}; +use frame_system::EnsureRootWithSuccess; +use sp_runtime::traits::Replace; + +parameter_types! { + pub const VoteLockingPeriod: BlockNumber = DAYS; +} + +impl pallet_conviction_voting::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type Polls = Referenda; + type MaxTurnout = frame_support::traits::TotalIssuanceOf; + // Maximum number of concurrent votes an account may have + type MaxVotes = ConstU32<20>; + // Minimum period of vote locking + type VoteLockingPeriod = VoteLockingPeriod; +} + +// Origin for general admin or root +pub type GeneralAdminOrRoot = EitherOf, origins::GeneralAdmin>; + +impl custom_origins::Config for Runtime {} + +// The purpose of this pallet is to queue calls to be dispatched as by root later => the Dispatch +// origin corresponds to the Gov2 Whitelist track. +impl pallet_whitelist::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type WhitelistOrigin = EitherOf< + EnsureRootWithSuccess>, + MapSuccess< + pallet_collective::EnsureProportionAtLeast, + Replace>, + >, + >; + type DispatchWhitelistedOrigin = EitherOf, WhitelistedCaller>; + type Preimages = Preimage; +} + +pallet_referenda::impl_tracksinfo_get!(TracksInfo, Balance, BlockNumber); + +parameter_types! { + pub const AlarmInterval: BlockNumber = 1; + pub SubmissionDeposit: Balance = 10 * dollar(ACA); + pub const UndecidingTimeout: BlockNumber = 14 * DAYS; +} + +impl pallet_referenda::Config for Runtime { + type WeightInfo = (); + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type Scheduler = Scheduler; + type Currency = Balances; + type SubmitOrigin = frame_system::EnsureSigned; + type CancelOrigin = EitherOf, ReferendumCanceller>; + type KillOrigin = EitherOf, ReferendumKiller>; + type Slash = Treasury; + type Votes = pallet_conviction_voting::VotesOf; + type Tally = pallet_conviction_voting::TallyOf; + type SubmissionDeposit = SubmissionDeposit; + type MaxQueued = ConstU32<100>; + type UndecidingTimeout = UndecidingTimeout; + type AlarmInterval = AlarmInterval; + type Tracks = TracksInfo; + type Preimages = Preimage; +} diff --git a/runtime/mandala/src/governance/tracks.rs b/runtime/mandala/src/governance/tracks.rs new file mode 100644 index 000000000..40d0afa5c --- /dev/null +++ b/runtime/mandala/src/governance/tracks.rs @@ -0,0 +1,178 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Track configurations for governance. + +use super::*; +use pallet_referenda::Curve; +use sp_std::str::FromStr; + +const fn percent(x: i32) -> sp_runtime::FixedI64 { + sp_runtime::FixedI64::from_rational(x as u128, 100) +} +const fn permill(x: i32) -> sp_runtime::FixedI64 { + sp_runtime::FixedI64::from_rational(x as u128, 1000) +} + +lazy_static::lazy_static! { +static ref TRACKS_DATA: [(u16, pallet_referenda::TrackInfo); 5] = [ + ( + 0, + pallet_referenda::TrackInfo { + // Name of this track. + name: "root", + // A limit for the number of referenda on this track that can be being decided at once. + // For Root origin this should generally be just one. + max_deciding: 5, + // Amount that must be placed on deposit before a decision can be made. + decision_deposit: 20 * 1000 * dollar(ACA), + // Amount of time this must be submitted for before a decision can be made. + prepare_period: DAYS, + // Amount of time that a decision may take to be approved prior to cancellation. + decision_period: 14 * DAYS, + // Amount of time that the approval criteria must hold before it can be approved. + confirm_period: DAYS, + // Minimum amount of time that an approved proposal must be in the dispatch queue. + min_enactment_period: DAYS, + // Minimum aye votes as percentage of overall conviction-weighted votes needed for + // approval as a function of time into decision period. + min_approval: Curve::make_reciprocal(4, 14, percent(80), percent(50), percent(100)), + // Minimum pre-conviction aye-votes ("support") as percentage of overall population that + // is needed for approval as a function of time into decision period. + min_support: Curve::make_linear(14, 14, permill(5), percent(25)), + }, + ), + ( + 1, + pallet_referenda::TrackInfo { + name: "whitelisted_caller", + max_deciding: 100, + decision_deposit: 2 * 1000 * dollar(ACA), + prepare_period: 10 * MINUTES, + decision_period: 14 * DAYS, + confirm_period: 10 * MINUTES, + min_enactment_period: 30 * MINUTES, + min_approval: Curve::make_reciprocal(1, 14, percent(96), percent(50), percent(100)), + min_support: Curve::make_reciprocal(1, 14 * 24, percent(1), percent(0), percent(2)), + }, + ), + ( + 2, + pallet_referenda::TrackInfo { + name: "general_admin", + max_deciding: 10, + decision_deposit: 1000 * dollar(ACA), + prepare_period: HOURS, + decision_period: 14 * DAYS, + confirm_period: DAYS, + min_enactment_period: DAYS, + min_approval: Curve::make_reciprocal(4, 14, percent(80), percent(50), percent(100)), + min_support: Curve::make_reciprocal(7, 14, percent(10), percent(0), percent(50)), + }, + ), + ( + 3, + pallet_referenda::TrackInfo { + name: "referendum_canceller", + max_deciding: 20, + decision_deposit: 2 * 1000 * dollar(ACA), + prepare_period: HOURS, + decision_period: 14 * DAYS, + confirm_period: 3 * HOURS, + min_enactment_period: 10 * MINUTES, + min_approval: Curve::make_reciprocal(1, 14, percent(96), percent(50), percent(100)), + min_support: Curve::make_reciprocal(1, 14, percent(1), percent(0), percent(10)), + }, + ), + ( + 4, + pallet_referenda::TrackInfo { + name: "referendum_killer", + max_deciding: 100, + decision_deposit: 4 * 1000 * dollar(ACA), + prepare_period: HOURS, + decision_period: 14 * DAYS, + confirm_period: 3 * HOURS, + min_enactment_period: 10 * MINUTES, + min_approval: Curve::make_reciprocal(1, 14, percent(96), percent(50), percent(100)), + min_support: Curve::make_reciprocal(1, 14, percent(1), percent(0), percent(10)), + }, + ), +]; +} + +pub struct TracksInfo; +impl pallet_referenda::TracksInfo for TracksInfo { + type Id = u16; + type RuntimeOrigin = ::PalletsOrigin; + fn tracks() -> &'static [(Self::Id, pallet_referenda::TrackInfo)] { + &TRACKS_DATA[..] + } + fn track_for(id: &Self::RuntimeOrigin) -> Result { + if let Ok(system_origin) = frame_system::RawOrigin::try_from(id.clone()) { + match system_origin { + frame_system::RawOrigin::Root => { + if let Some((track_id, _)) = Self::tracks().iter().find(|(_, track)| track.name == "root") { + Ok(*track_id) + } else { + Err(()) + } + } + _ => Err(()), + } + } else if let Ok(custom_origin) = custom_origins::Origin::try_from(id.clone()) { + if let Some((track_id, _)) = Self::tracks().iter().find(|(_, track)| { + if let Ok(track_custom_origin) = custom_origins::Origin::from_str(track.name) { + track_custom_origin == custom_origin + } else { + false + } + }) { + Ok(*track_id) + } else { + Err(()) + } + } else { + Err(()) + } + } +} + +#[test] +/// To ensure voters are always locked into their vote +fn vote_locking_always_longer_than_enactment_period() { + for (_, track) in (&TRACKS_DATA).iter() { + assert!( + ::VoteLockingPeriod::get() >= track.min_enactment_period, + "Track {} has enactment period {} < vote locking period {}", + track.name, + track.min_enactment_period, + ::VoteLockingPeriod::get(), + ); + } +} + +#[test] +fn all_tracks_have_origins() { + for (_, track) in (&TRACKS_DATA).iter() { + // check name.into() is successful either converts into "root" or custom origin + let track_is_root = track.name == "root"; + let track_has_custom_origin = custom_origins::Origin::from_str(track.name).is_ok(); + assert!(track_is_root || track_has_custom_origin); + } +} diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index a5f2a8498..72c7d4453 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -38,9 +38,9 @@ use frame_support::{ traits::{ fungible::HoldConsideration, tokens::{PayFromAccount, UnityAssetBalanceConversion}, - ConstBool, ConstU128, ConstU32, Contains, ContainsLengthBound, Currency as PalletCurrency, EnsureOrigin, - EqualPrivilegeOnly, Get, Imbalance, InstanceFilter, LinearStoragePrice, LockIdentifier, OnUnbalanced, - SortedMembers, + ConstBool, ConstU128, ConstU16, ConstU32, Contains, ContainsLengthBound, Currency as PalletCurrency, + EnsureOrigin, EqualPrivilegeOnly, Get, Imbalance, InstanceFilter, LinearStoragePrice, LockIdentifier, + OnUnbalanced, SortedMembers, }, weights::{constants::RocksDbWeight, ConstantMultiplier, Weight}, PalletId, @@ -122,9 +122,11 @@ pub use nutsfinance_stable_asset; mod authority; mod benchmarking; pub mod constants; +pub mod governance; /// Weights for pallets used in the runtime. mod weights; pub mod xcm_config; +use governance::councils::*; /// This runtime version. #[sp_version::runtime_version] @@ -372,129 +374,6 @@ impl pallet_sudo::Config for Runtime { type WeightInfo = (); } -parameter_types! { - pub const GeneralCouncilMotionDuration: BlockNumber = 7 * DAYS; - pub const CouncilDefaultMaxProposals: u32 = 100; - pub const CouncilDefaultMaxMembers: u32 = 100; - pub MaxProposalWeight: Weight = Perbill::from_percent(50) * RuntimeBlockWeights::get().max_block; -} - -impl pallet_collective::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type Proposal = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type MotionDuration = GeneralCouncilMotionDuration; - type MaxProposals = CouncilDefaultMaxProposals; - type MaxMembers = CouncilDefaultMaxMembers; - type DefaultVote = pallet_collective::PrimeDefaultVote; - type SetMembersOrigin = EnsureRoot; - type WeightInfo = (); - type MaxProposalWeight = MaxProposalWeight; -} - -impl pallet_membership::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type AddOrigin = EnsureRootOrThreeFourthsGeneralCouncil; - type RemoveOrigin = EnsureRootOrThreeFourthsGeneralCouncil; - type SwapOrigin = EnsureRootOrThreeFourthsGeneralCouncil; - type ResetOrigin = EnsureRootOrThreeFourthsGeneralCouncil; - type PrimeOrigin = EnsureRootOrThreeFourthsGeneralCouncil; - type MembershipInitialized = GeneralCouncil; - type MembershipChanged = GeneralCouncil; - type MaxMembers = CouncilDefaultMaxMembers; - type WeightInfo = (); -} - -parameter_types! { - pub const FinancialCouncilMotionDuration: BlockNumber = 7 * DAYS; -} - -impl pallet_collective::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type Proposal = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type MotionDuration = FinancialCouncilMotionDuration; - type MaxProposals = CouncilDefaultMaxProposals; - type MaxMembers = CouncilDefaultMaxMembers; - type DefaultVote = pallet_collective::PrimeDefaultVote; - type SetMembersOrigin = EnsureRoot; - type WeightInfo = (); - type MaxProposalWeight = MaxProposalWeight; -} - -impl pallet_membership::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type MembershipInitialized = FinancialCouncil; - type MembershipChanged = FinancialCouncil; - type MaxMembers = CouncilDefaultMaxMembers; - type WeightInfo = (); -} - -parameter_types! { - pub const HomaCouncilMotionDuration: BlockNumber = 7 * DAYS; -} - -impl pallet_collective::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type Proposal = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type MotionDuration = HomaCouncilMotionDuration; - type MaxProposals = CouncilDefaultMaxProposals; - type MaxMembers = CouncilDefaultMaxMembers; - type DefaultVote = pallet_collective::PrimeDefaultVote; - type SetMembersOrigin = EnsureRoot; - type WeightInfo = (); - type MaxProposalWeight = MaxProposalWeight; -} - -impl pallet_membership::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type MembershipInitialized = HomaCouncil; - type MembershipChanged = HomaCouncil; - type MaxMembers = CouncilDefaultMaxMembers; - type WeightInfo = (); -} - -parameter_types! { - pub const TechnicalCommitteeMotionDuration: BlockNumber = 7 * DAYS; -} - -impl pallet_collective::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type Proposal = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type MotionDuration = TechnicalCommitteeMotionDuration; - type MaxProposals = CouncilDefaultMaxProposals; - type MaxMembers = CouncilDefaultMaxMembers; - type DefaultVote = pallet_collective::PrimeDefaultVote; - type SetMembersOrigin = EnsureRoot; - type WeightInfo = (); - type MaxProposalWeight = MaxProposalWeight; -} - -impl pallet_membership::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type RemoveOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type SwapOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type ResetOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type PrimeOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type MembershipInitialized = TechnicalCommittee; - type MembershipChanged = TechnicalCommittee; - type MaxMembers = CouncilDefaultMaxMembers; - type WeightInfo = (); -} - impl pallet_membership::Config for Runtime { type RuntimeEvent = RuntimeEvent; type AddOrigin = EnsureRootOrTwoThirdsGeneralCouncil; @@ -647,59 +526,6 @@ impl pallet_recovery::Config for Runtime { type WeightInfo = (); } -parameter_types! { - pub const LaunchPeriod: BlockNumber = 2 * HOURS; - pub const VotingPeriod: BlockNumber = HOURS; - pub const FastTrackVotingPeriod: BlockNumber = HOURS; - pub MinimumDeposit: Balance = 100 * cent(ACA); - pub const EnactmentPeriod: BlockNumber = MINUTES; - pub const CooloffPeriod: BlockNumber = MINUTES; -} - -impl pallet_democracy::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type EnactmentPeriod = EnactmentPeriod; - type LaunchPeriod = LaunchPeriod; - type VotingPeriod = VotingPeriod; - type VoteLockingPeriod = EnactmentPeriod; // Same as EnactmentPeriod - type MinimumDeposit = MinimumDeposit; - /// A straight majority of the council can decide what their next motion is. - type ExternalOrigin = EnsureRootOrHalfGeneralCouncil; - /// A majority can have the next scheduled referendum be a straight majority-carries vote. - type ExternalMajorityOrigin = EnsureRootOrHalfGeneralCouncil; - /// A unanimous council can have the next scheduled referendum be a straight default-carries - /// (NTB) vote. - type ExternalDefaultOrigin = EnsureRootOrAllGeneralCouncil; - /// Two thirds of the technical committee can have an ExternalMajority/ExternalDefault vote - /// be tabled immediately and with a shorter voting/enactment period. - type FastTrackOrigin = EnsureRootOrTwoThirdsTechnicalCommittee; - type InstantOrigin = EnsureRootOrAllTechnicalCommittee; - type InstantAllowed = ConstBool; - type FastTrackVotingPeriod = FastTrackVotingPeriod; - // To cancel a proposal which has been passed, 2/3 of the council must agree to it. - type CancellationOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type BlacklistOrigin = EnsureRoot; - // To cancel a proposal before it has been passed, the technical committee must be unanimous or - // Root must agree. - type CancelProposalOrigin = EnsureRootOrAllTechnicalCommittee; - // Any single technical committee member may veto a coming council proposal, however they can - // only do it once and it lasts only for the cooloff period. - type VetoOrigin = pallet_collective::EnsureMember; - type CooloffPeriod = CooloffPeriod; - type Slash = Treasury; - type Scheduler = Scheduler; - type PalletsOrigin = OriginCaller; - type MaxVotes = ConstU32<100>; - //TODO: might need to weight for Mandala - type WeightInfo = pallet_democracy::weights::SubstrateWeight; - type MaxProposals = CouncilDefaultMaxProposals; - type Preimages = Preimage; - type MaxDeposits = ConstU32<100>; - type MaxBlacklisted = ConstU32<100>; - type SubmitOrigin = EnsureSigned; -} - impl orml_auction::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; @@ -2017,6 +1843,10 @@ construct_runtime!( HomaCouncilMembership: pallet_membership:: = 55, TechnicalCommittee: pallet_collective:: = 56, TechnicalCommitteeMembership: pallet_membership:: = 57, + ConvictionVoting: pallet_conviction_voting = 150, + Referenda: pallet_referenda = 151, + Origins: governance::custom_origins::{Origin} = 152, + Whitelist: pallet_whitelist = 153, Authority: orml_authority = 70, PhragmenElection: pallet_elections_phragmen = 71,