diff --git a/system-parachains/collectives/collectives-polkadot/Cargo.toml b/system-parachains/collectives/collectives-polkadot/Cargo.toml index 6c7eec3d7d..dd2cd7425a 100644 --- a/system-parachains/collectives/collectives-polkadot/Cargo.toml +++ b/system-parachains/collectives/collectives-polkadot/Cargo.toml @@ -94,6 +94,7 @@ substrate-wasm-builder = { optional = true, workspace = true } [features] default = ["std"] +fast-runtime = [] runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", diff --git a/system-parachains/collectives/collectives-polkadot/src/lib.rs b/system-parachains/collectives/collectives-polkadot/src/lib.rs index 41771e98ce..926532a827 100644 --- a/system-parachains/collectives/collectives-polkadot/src/lib.rs +++ b/system-parachains/collectives/collectives-polkadot/src/lib.rs @@ -44,6 +44,7 @@ mod weights; pub mod xcm_config; // Fellowship configurations. pub mod fellowship; +pub mod potoc; pub use ambassador::pallet_ambassador_origins; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; @@ -53,6 +54,7 @@ use impls::{AllianceProposalProvider, EqualOrGreatestRootCmp, ToParentTreasury}; use polkadot_runtime_common::impls::{ ContainsParts as ContainsLocationParts, VersionedLocatableAsset, }; +use potoc::pallet_potoc_origins; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ @@ -725,16 +727,11 @@ construct_runtime!( AllianceMotion: pallet_collective:: = 51, // The Fellowship. - // pub type FellowshipCollectiveInstance = pallet_ranked_collective::Instance1; FellowshipCollective: pallet_ranked_collective:: = 60, - // pub type FellowshipReferendaInstance = pallet_referenda::Instance1; FellowshipReferenda: pallet_referenda:: = 61, FellowshipOrigins: pallet_fellowship_origins = 62, - // pub type FellowshipCoreInstance = pallet_core_fellowship::Instance1; FellowshipCore: pallet_core_fellowship:: = 63, - // pub type FellowshipSalaryInstance = pallet_salary::Instance1; FellowshipSalary: pallet_salary:: = 64, - // pub type FellowshipTreasuryInstance = pallet_treasury::Instance1; FellowshipTreasury: pallet_treasury:: = 65, // Ambassador Program. @@ -744,6 +741,14 @@ construct_runtime!( AmbassadorCore: pallet_core_fellowship:: = 73, AmbassadorSalary: pallet_salary:: = 74, AmbassadorTreasury: pallet_treasury:: = 75, + + // Tooling Collective + PotocCollective: pallet_ranked_collective:: = 80, + PotocReferenda: pallet_referenda:: = 81, + PotocOrigins: pallet_potoc_origins = 82, + PotocCore: pallet_core_fellowship:: = 83, + PotocSalary: pallet_salary:: = 84, + PotocTreasury: pallet_treasury:: = 85 } ); @@ -779,6 +784,7 @@ type Migrations = ( pallet_core_fellowship::migration::MigrateV0ToV1, pallet_core_fellowship::migration::MigrateV0ToV1, ambassador::migrations::TruncateHeadAmbassadors, + potoc::InsertSeedMembers, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, ); @@ -1134,7 +1140,7 @@ impl_runtime_apis! { Asset { fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) - }.into(), + }, Parent.into(), )) } diff --git a/system-parachains/collectives/collectives-polkadot/src/potoc/mod.rs b/system-parachains/collectives/collectives-polkadot/src/potoc/mod.rs new file mode 100644 index 0000000000..d722ac9252 --- /dev/null +++ b/system-parachains/collectives/collectives-polkadot/src/potoc/mod.rs @@ -0,0 +1,323 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +//! The Polkadot Tooling Collective. + +mod origins; +mod tracks; + +use crate::{ + impls::ToParentTreasury, + weights, + xcm_config::{AssetHubUsdt, LocationToAccountId, TreasurerBodyId}, + AccountId, AssetRateWithNative, Balance, Balances, GovernanceLocation, PolkadotTreasuryAccount, + PotocReferenda, Preimage, Runtime, RuntimeCall, RuntimeEvent, Scheduler, DAYS, + POTOC_TREASURY_PALLET_ID, *, +}; +// There is only one admin for all collectives: +use crate::xcm_config::FellowshipAdminBodyId as PotocAdminBodyId; +use frame_support::{ + parameter_types, + traits::{ + DefensiveResult, EitherOf, EitherOfDiverse, MapSuccess, NeverEnsureOrigin, + OnRuntimeUpgrade, PalletInfoAccess, RankedMembers, + }, + PalletId, +}; +use frame_system::{EnsureRoot, EnsureRootWithSuccess}; +pub use origins::{pallet_origins as pallet_potoc_origins, Members}; +use pallet_ranked_collective::WeightInfo; +use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; +use polkadot_runtime_common::impls::{ + LocatableAssetConverter, VersionedLocatableAsset, VersionedLocationConverter, +}; +use polkadot_runtime_constants::{currency::GRAND, time::HOURS}; +use sp_arithmetic::Permill; +use sp_core::{crypto::Ss58Codec, ConstU128, ConstU32}; +use sp_runtime::traits::{ConstU16, ConvertToValue, IdentityLookup, Replace, ReplaceWithDefault}; +use xcm_builder::{AliasesIntoAccountId32, PayOverXcm}; + +#[cfg(feature = "runtime-benchmarks")] +use crate::impls::benchmarks::{OpenHrmpChannel, PayWithEnsure}; + +/// PoToC's ranks. +pub mod ranks { + use pallet_ranked_collective::Rank; + + /// A Candidate. + pub const CANDIDATE: Rank = 0; + /// A Member. + pub const MEMBER: Rank = 1; +} + +/// Origin of either Member vote, OpenGov or Root. +pub type OpenGovOrMembers = EitherOfDiverse< + Members, + EitherOfDiverse< + EnsureXcm>, + EnsureRoot, + >, +>; + +/// Promote origin, either: +/// - Root +/// - PoToC's Admin origin (i.e. token holder referendum) +/// - Members vote +pub type PromoteOrigin = MapSuccess>>; + +impl pallet_potoc_origins::Config for Runtime {} + +pub type PotocReferendaInstance = pallet_referenda::Instance3; +impl pallet_referenda::Config for Runtime { + type WeightInfo = weights::pallet_referenda_fellowship_referenda::WeightInfo; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type Scheduler = Scheduler; + type Currency = Balances; + // Members can submit proposals, candidates cannot. + type SubmitOrigin = + pallet_ranked_collective::EnsureMember; + type CancelOrigin = OpenGovOrMembers; + type KillOrigin = OpenGovOrMembers; + type Slash = ToParentTreasury; + type Votes = pallet_ranked_collective::Votes; + type Tally = pallet_ranked_collective::TallyOf; + type SubmissionDeposit = ConstU128<0>; + type MaxQueued = ConstU32<100>; + type UndecidingTimeout = ConstU32<{ 7 * DAYS }>; + type AlarmInterval = ConstU32<1>; + type Tracks = tracks::TracksInfo; + type Preimages = Preimage; +} + +pub type PotocCollectiveInstance = pallet_ranked_collective::Instance3; +impl pallet_ranked_collective::Config for Runtime { + type WeightInfo = weights::pallet_ranked_collective_fellowship_collective::WeightInfo; + type RuntimeEvent = RuntimeEvent; + + // Promotions and the induction of new members are serviced by `PotocCore` pallet instance. + #[cfg(not(feature = "runtime-benchmarks"))] + type PromoteOrigin = frame_system::EnsureNever; + // The maximum value of `u16` set as a success value for the root to ensure the benchmarks will + // pass. + #[cfg(feature = "runtime-benchmarks")] + type PromoteOrigin = EnsureRootWithSuccess>; + + // Demotion is by any of: + // - Root can demote arbitrarily. + // - PoToC's Admin origin (i.e. token holder referendum); + type DemoteOrigin = EitherOf< + EnsureRootWithSuccess>, + MapSuccess< + EnsureXcm>, + Replace>, + >, + >; + // Exchange is by any of: + // - Root can exchange arbitrarily. + // - the Members origin + type ExchangeOrigin = EitherOf, Members>; + type AddOrigin = MapSuccess>; + type RemoveOrigin = Self::DemoteOrigin; + type Polls = PotocReferenda; + // Map ranks 1:1 to the tracks that they can vote on. + type MinRankOfClass = sp_runtime::traits::Identity; + type MemberSwappedHandler = (crate::PotocCore, crate::PotocSalary); + type VoteWeight = pallet_ranked_collective::Geometric; + type MaxMemberCount = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkSetup = (crate::PotocCore, crate::PotocSalary); +} + +pub type PotocCoreInstance = pallet_core_fellowship::Instance3; + +impl pallet_core_fellowship::Config for Runtime { + type WeightInfo = weights::pallet_core_fellowship_fellowship_core::WeightInfo; + type RuntimeEvent = RuntimeEvent; + type Members = pallet_ranked_collective::Pallet; + type Balance = Balance; + type ParamsOrigin = OpenGovOrMembers; + type InductOrigin = OpenGovOrMembers; + type PromoteOrigin = PromoteOrigin; + type ApproveOrigin = Self::PromoteOrigin; + // Fast promotions are not needed with a single rank and would require higher turnout. + type FastPromoteOrigin = NeverEnsureOrigin; + type EvidenceSize = ConstU32<65536>; + type MaxRank = ConstU32<9>; +} + +pub type PotocSalaryInstance = pallet_salary::Instance3; + +use xcm::prelude::*; + +parameter_types! { + // The interior location on AssetHub for the paying account. This is PoToC's Salary + // pallet instance. This sovereign account will need funding. + pub Interior: InteriorLocation = PalletInstance(::index() as u8).into(); +} + +const USDT_UNITS: u128 = 1_000_000; + +/// [`PayOverXcm`] setup to pay PoToC's salary on the AssetHub in USDT. +pub type PotocSalaryPaymaster = PayOverXcm< + Interior, + crate::xcm_config::XcmRouter, + crate::PolkadotXcm, + ConstU32<{ 6 * HOURS }>, + AccountId, + (), + ConvertToValue, + AliasesIntoAccountId32<(), AccountId>, +>; + +impl pallet_salary::Config for Runtime { + type WeightInfo = weights::pallet_salary_fellowship_salary::WeightInfo; + type RuntimeEvent = RuntimeEvent; + + #[cfg(not(feature = "runtime-benchmarks"))] + type Paymaster = PotocSalaryPaymaster; + #[cfg(feature = "runtime-benchmarks")] + type Paymaster = PayWithEnsure>>; + type Members = pallet_ranked_collective::Pallet; + + #[cfg(not(feature = "runtime-benchmarks"))] + type Salary = pallet_core_fellowship::Pallet; + #[cfg(feature = "runtime-benchmarks")] + type Salary = frame_support::traits::tokens::ConvertRank< + crate::impls::benchmarks::RankToSalary, + >; + // 15 days to register for a salary payment. + type RegistrationPeriod = ConstU32<{ 15 * DAYS }>; + // 15 days to claim the salary payment. + type PayoutPeriod = ConstU32<{ 15 * DAYS }>; + // Total monthly salary budget. + type Budget = ConstU128<{ 250_000 * USDT_UNITS }>; +} + +parameter_types! { + pub const PotocTreasuryPalletId: PalletId = POTOC_TREASURY_PALLET_ID; + pub const ProposalBond: Permill = Permill::from_percent(100); + pub const Burn: Permill = Permill::from_percent(0); + pub const MaxBalance: Balance = Balance::MAX; + // The asset's interior location for the paying account. This is PoToC's Treasury + // pallet instance. + pub PotocTreasuryInteriorLocation: InteriorLocation = + PalletInstance(::index() as u8).into(); +} + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub const ProposalBondForBenchmark: Permill = Permill::from_percent(5); +} + +/// [`PayOverXcm`] setup to pay PoToC's Treasury. +pub type PotocTreasuryPaymaster = PayOverXcm< + PotocTreasuryInteriorLocation, + crate::xcm_config::XcmRouter, + crate::PolkadotXcm, + ConstU32<{ 6 * HOURS }>, + VersionedLocation, + VersionedLocatableAsset, + LocatableAssetConverter, + VersionedLocationConverter, +>; + +pub type PotocTreasuryInstance = pallet_treasury::Instance3; + +impl pallet_treasury::Config for Runtime { + type WeightInfo = weights::pallet_treasury_fellowship_treasury::WeightInfo; + type RuntimeEvent = RuntimeEvent; + type PalletId = PotocTreasuryPalletId; + type Currency = Balances; + type RejectOrigin = EitherOfDiverse< + EnsureRoot, + EitherOfDiverse>, Fellows>, + >; + type SpendPeriod = ConstU32<{ 7 * DAYS }>; + type Burn = Burn; + type BurnDestination = (); + type SpendFunds = (); + type MaxApprovals = ConstU32<100>; + type SpendOrigin = EitherOf< + EitherOf< + EnsureRootWithSuccess, + MapSuccess< + EnsureXcm>, + Replace>, + >, + >, + MapSuccess>>, + >; + type AssetKind = VersionedLocatableAsset; + type Beneficiary = VersionedLocation; + type BeneficiaryLookup = IdentityLookup; + #[cfg(not(feature = "runtime-benchmarks"))] + type Paymaster = PotocTreasuryPaymaster; + #[cfg(feature = "runtime-benchmarks")] + type Paymaster = PayWithEnsure>>; + type BalanceConverter = AssetRateWithNative; + type PayoutPeriod = ConstU32<{ 30 * DAYS }>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = polkadot_runtime_common::impls::benchmarks::TreasuryArguments< + sp_core::ConstU8<1>, + ConstU32<1000>, + >; +} + +pub struct InsertSeedMembers; +impl OnRuntimeUpgrade for InsertSeedMembers { + fn on_runtime_upgrade() -> Weight { + let mut weight = Weight::default(); + + let seed_member = vec![ + "151S1YrZd4zfUYCeWhNERkGdmom8kdqAtRqtHwh9HYMTfFYJ", + "14DsLzVyTUTDMm2eP3czwPbH53KgqnQRp3CJJZS9GR7yxGDP", + "16JskuojL6mSp6HNcjiHYa9jqksWbLD8L9YGWU1ppiPWQ9sa", + "15oLanodWWweiZJSoDTEBtrX7oGfq6e8ct5y5E6fVRDPhUgj", + "14iKbZws1fjJ6TH27yoRq6KeeVNof83VmxUBN2W2udQVBe5o", + "12TNvHiRkwzYqT5UZ86cfUvBeZBjLLYUzHLa4Hix99oTrgqT", + "12W3ea6jWKhzSWSCMjUKqtDwasRACeYFGkyvVb9Y9b5dGm2v", + "15roJ4ZrgrZam5BQWJgiGHpgp7ShFQBRNLq6qUfiNqXDZjMK", + "15DCWHQknBjc5YPFoVj8Pn2KoqrqYywJJ95BYNYJ4Fj3NLqz", + "15DCZocYEM2ThYCAj22QE4QENRvUNVrDtoLBVbCm5x4EQncr", + "16a357f5Sxab3V2ne4emGQvqJaCLeYpTMx3TCjnQhmJQ71DX", + "13ogXJ1tpHZoaav2iQQRDH5eHcvpAEfwB1UFY6dijDBmDcic", + "16JGzEsi8gcySKjpmxHVrkLTHdFHodRepEz8n244gNZpr9J", + ]; + + for address in seed_member.iter() { + let Ok(member) = AccountId::from_ss58check(address) else { + frame_support::defensive!("Invalid seed member: {member}"); + continue; + }; + + as RankedMembers>::induct( + &member, + ).defensive_ok(); + as RankedMembers>::promote( + &member, + ).defensive_ok(); + + log::info!("PoToC Seed member inserted: {address}"); + + // TODO use potoc weight + weight.saturating_accrue(weights::pallet_ranked_collective_fellowship_collective::WeightInfo::::add_member()); + weight.saturating_accrue(weights::pallet_ranked_collective_fellowship_collective::WeightInfo::::promote_member(1)); + } + + weight + } +} diff --git a/system-parachains/collectives/collectives-polkadot/src/potoc/origins.rs b/system-parachains/collectives/collectives-polkadot/src/potoc/origins.rs new file mode 100644 index 0000000000..30b6b1d096 --- /dev/null +++ b/system-parachains/collectives/collectives-polkadot/src/potoc/origins.rs @@ -0,0 +1,66 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +//! PoToC custom origins. + +use super::ranks; +pub use pallet_origins::*; + +#[frame_support::pallet] +pub mod pallet_origins { + use super::ranks; + use frame_support::pallet_prelude::*; + use pallet_ranked_collective::Rank; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[derive(PartialEq, Eq, Clone, MaxEncodedLen, Encode, Decode, TypeInfo, RuntimeDebug)] + #[pallet::origin] + pub enum Origin { + /// Plurality voice of the [ranks::MEMBER] members given via referendum. + Members, + } + + impl Origin { + /// Returns the rank that the origin `self` speaks for, or `None` if it doesn't speak for + /// any. + pub fn as_voice(&self) -> Option { + Some(match &self { + Origin::Members => ranks::MEMBER, + }) + } + } + + /// Ensures [`Origin::Members`] origin. + pub struct Members; + impl> + From> EnsureOrigin for Members { + type Success = (); + fn try_origin(o: O) -> Result { + o.into().map(|o| match o { + Origin::Members => (), + }) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(O::from(Origin::Members)) + } + } +} diff --git a/system-parachains/collectives/collectives-polkadot/src/potoc/tracks.rs b/system-parachains/collectives/collectives-polkadot/src/potoc/tracks.rs new file mode 100644 index 0000000000..dfbfd29ebd --- /dev/null +++ b/system-parachains/collectives/collectives-polkadot/src/potoc/tracks.rs @@ -0,0 +1,92 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +//! Track configurations for PoToC. + +use super::origins::Origin; +use crate::{Balance, BlockNumber, RuntimeOrigin, DAYS, DOLLARS, HOURS, MINUTES}; +use pallet_referenda::{Curve::LinearDecreasing, TrackInfo}; +use polkadot_runtime_common::prod_or_fast; +use sp_runtime::Perbill; + +/// Referendum `TrackId` type. +pub type TrackId = u16; + +/// Referendum track IDs. +pub mod constants { + use super::TrackId; + + /// The members track. + pub const MEMBERS: TrackId = 1; +} + +/// The type implementing the [`pallet_referenda::TracksInfo`] trait for referenda pallet. +pub struct TracksInfo; + +/// Information on the voting tracks. +impl pallet_referenda::TracksInfo for TracksInfo { + type Id = TrackId; + + type RuntimeOrigin = ::PalletsOrigin; + + /// Return the array of available tracks and their information. + fn tracks() -> &'static [(Self::Id, TrackInfo)] { + static DATA: [(TrackId, TrackInfo); 1] = [( + constants::MEMBERS, + TrackInfo { + name: "members", + max_deciding: 10, + decision_deposit: 5 * DOLLARS, + prepare_period: prod_or_fast!(24 * HOURS, 1 * MINUTES), + decision_period: prod_or_fast!(7 * DAYS, 5 * MINUTES), + confirm_period: prod_or_fast!(24 * HOURS, 1 * MINUTES), + min_enactment_period: prod_or_fast!(HOURS, 1 * MINUTES), + min_approval: LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(50), + ceil: Perbill::from_percent(100), + }, + min_support: LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(0), + ceil: Perbill::from_percent(50), + }, + }, + )]; + &DATA[..] + } + + /// Determine the voting track for the given `origin`. + fn track_for(id: &Self::RuntimeOrigin) -> Result { + #[cfg(feature = "runtime-benchmarks")] + { + // For benchmarks, we enable a root origin. + // It is important that this is not available in production! + let root: Self::RuntimeOrigin = frame_system::RawOrigin::Root.into(); + if &root == id { + return Ok(constants::MEMBERS) + } + } + + match Origin::try_from(id.clone()) { + Ok(Origin::Members) => Ok(constants::MEMBERS), + _ => Err(()), + } + } +} + +// implements [`frame_support::traits::Get`] for [`TracksInfo`] +pallet_referenda::impl_tracksinfo_get!(TracksInfo, Balance, BlockNumber); diff --git a/system-parachains/constants/src/polkadot.rs b/system-parachains/constants/src/polkadot.rs index d42ca84d82..fc2f057bdc 100644 --- a/system-parachains/constants/src/polkadot.rs +++ b/system-parachains/constants/src/polkadot.rs @@ -25,9 +25,6 @@ pub mod account { /// Referenda pallet ID. /// Used as a temporary place to deposit a slashed imbalance before teleporting to the Treasury. pub const REFERENDA_PALLET_ID: PalletId = PalletId(*b"py/refer"); - /// Ambassador Referenda pallet ID. - /// Used as a temporary place to deposit a slashed imbalance before teleporting to the Treasury. - pub const AMBASSADOR_REFERENDA_PALLET_ID: PalletId = PalletId(*b"py/amref"); /// Identity pallet ID. /// Used as a temporary place to deposit a slashed imbalance before teleporting to the Treasury. pub const IDENTITY_PALLET_ID: PalletId = PalletId(*b"py/ident"); @@ -35,6 +32,8 @@ pub mod account { pub const FELLOWSHIP_TREASURY_PALLET_ID: PalletId = PalletId(*b"py/feltr"); /// Ambassador treasury pallet ID pub const AMBASSADOR_TREASURY_PALLET_ID: PalletId = PalletId(*b"py/ambtr"); + /// Potoc treasury pallet ID + pub const POTOC_TREASURY_PALLET_ID: PalletId = PalletId(*b"py/ptctr"); } /// Consensus-related.