From 598ca59edeecef0061e5a254fc2b5cdde5e32769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Fri, 13 Oct 2023 11:08:23 -0500 Subject: [PATCH 1/7] feat(pallets/communities): define MemberRank trait, for MembershipPassport --- Cargo.lock | 2 ++ pallets/communities/src/lib.rs | 5 ++++- pallets/communities/src/tests/mock.rs | 5 ++++- pallets/communities/src/traits/mod.rs | 1 + pallets/communities/src/traits/rank.rs | 18 ++++++++++++++++++ 5 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 pallets/communities/src/traits/mod.rs create mode 100644 pallets/communities/src/traits/rank.rs diff --git a/Cargo.lock b/Cargo.lock index 03309842..81e66268 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6329,6 +6329,8 @@ dependencies = [ "frame-system 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", "pallet-assets 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", "pallet-balances 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", + "pallet-preimage", + "pallet-scheduler", "parity-scale-codec", "scale-info", "sp-core 21.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", diff --git a/pallets/communities/src/lib.rs b/pallets/communities/src/lib.rs index e42bf7ce..f6335f98 100644 --- a/pallets/communities/src/lib.rs +++ b/pallets/communities/src/lib.rs @@ -229,7 +229,10 @@ pub mod pallet { type CommunityId: Parameter + MaxEncodedLen; /// This type represents a rank for a member in a community - type MembershipPassport: Default + Parameter + MaxEncodedLen; + type MembershipRank: Default + AtLeast32BitUnsigned + Parameter + MaxEncodedLen + PartialOrd; + + /// This type represents a rank for a member in a community + type MembershipPassport: Default + Parameter + MaxEncodedLen + MemberRank; /// Type represents interactions between fungibles (i.e. assets) type Assets: fungibles::Inspect diff --git a/pallets/communities/src/tests/mock.rs b/pallets/communities/src/tests/mock.rs index 8b3318a0..8ed98de8 100644 --- a/pallets/communities/src/tests/mock.rs +++ b/pallets/communities/src/tests/mock.rs @@ -111,7 +111,10 @@ impl pallet_communities::Config for Test { type Assets = Assets; type Balances = Balances; type CommunityId = CommunityId; - type MembershipPassport = (); + + type MembershipRank = MembershipRank; + type MembershipPassport = MembershipPassport; + type PalletId = CommunitiesPalletId; type FreezeIdentifier = CommunitiesFreezeIdentifier; type MetadataUrlSize = MetadataUrlSize; diff --git a/pallets/communities/src/traits/mod.rs b/pallets/communities/src/traits/mod.rs new file mode 100644 index 00000000..731aabc5 --- /dev/null +++ b/pallets/communities/src/traits/mod.rs @@ -0,0 +1 @@ +pub mod rank; diff --git a/pallets/communities/src/traits/rank.rs b/pallets/communities/src/traits/rank.rs new file mode 100644 index 00000000..d0d26b3d --- /dev/null +++ b/pallets/communities/src/traits/rank.rs @@ -0,0 +1,18 @@ +use frame_support::Parameter; +use sp_runtime::traits::AtLeast32BitUnsigned; + +pub trait MemberRank +where + Rank: Default + AtLeast32BitUnsigned + Parameter + PartialEq + PartialOrd, +{ + fn rank(&self) -> Rank; +} + +impl MemberRank for Rank +where + Rank: Default + AtLeast32BitUnsigned + Parameter + PartialEq + PartialOrd, +{ + fn rank(&self) -> Rank { + Default::default() + } +} From 5178433957f640bfe005e6428db8fa0f4fdc66a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Fri, 13 Oct 2023 11:18:13 -0500 Subject: [PATCH 2/7] feat(pallets/communities): add structures and storage for handling governance --- pallets/communities/src/functions/getters.rs | 9 +- .../communities/src/functions/membership.rs | 32 ++++--- pallets/communities/src/functions/registry.rs | 5 +- pallets/communities/src/lib.rs | 52 ++++++++++- pallets/communities/src/tests/mock.rs | 41 ++++++--- pallets/communities/src/tests/registry.rs | 6 +- pallets/communities/src/types/governance.rs | 88 +++++++++++++++++++ pallets/communities/src/types/mod.rs | 6 +- pallets/communities/src/types/origin.rs | 29 ++++++ pallets/communities/src/types/parameters.rs | 7 +- pallets/communities/src/types/registry.rs | 9 +- 11 files changed, 235 insertions(+), 49 deletions(-) create mode 100644 pallets/communities/src/types/governance.rs create mode 100644 pallets/communities/src/types/origin.rs diff --git a/pallets/communities/src/functions/getters.rs b/pallets/communities/src/functions/getters.rs index 9739a381..90206edb 100644 --- a/pallets/communities/src/functions/getters.rs +++ b/pallets/communities/src/functions/getters.rs @@ -6,9 +6,10 @@ impl Pallet { T::PalletId::get().into_sub_account_truncating(community_id) } - pub(crate) fn get_community_admin(community_id: &CommunityIdOf) -> Result, DispatchError> { - let community = Self::community(community_id).ok_or(Error::::CommunityDoesNotExist)?; - - Ok(community.admin) + pub(crate) fn get_community_admin(community_id: &CommunityIdOf) -> Option> { + match GovernanceStrategy::::get(community_id) { + Some(CommunityGovernanceStrategy::AdminBased(admin)) => Some(admin), + _ => None, + } } } diff --git a/pallets/communities/src/functions/membership.rs b/pallets/communities/src/functions/membership.rs index 230ffc15..b826bc4c 100644 --- a/pallets/communities/src/functions/membership.rs +++ b/pallets/communities/src/functions/membership.rs @@ -4,27 +4,35 @@ impl Pallet { pub(crate) fn ensure_origin_member( origin: OriginFor, community_id: &CommunityIdOf, - ) -> Result<(), DispatchError> { + ) -> Result, DispatchError> { let caller = ensure_signed(origin)?; - if Self::membership(community_id, caller).is_none() { - return Err(DispatchError::BadOrigin); - } + Self::membership(community_id, &caller) + .ok_or(DispatchError::BadOrigin.into()) + .and_then(|_| Ok(caller)) + } - Ok(()) + #[allow(dead_code)] + pub(crate) fn ensure_member( + community_id: &CommunityIdOf, + who: &AccountIdOf, + ) -> Result, DispatchError> { + Self::membership(community_id, who).ok_or(Error::::NotAMember.into()) } pub(crate) fn ensure_origin_privileged( origin: OriginFor, community_id: &CommunityIdOf, - ) -> Result<(), DispatchError> { + ) -> Result>, DispatchError> { if let Some(caller) = ensure_signed_or_root(origin)? { - if caller != Self::get_community_admin(community_id)? { - return Err(DispatchError::BadOrigin); + if let Some(admin) = Self::get_community_admin(community_id) && admin == caller { + return Ok(Some(admin)) + } else { + return Err(DispatchError::BadOrigin) } } - Ok(()) + Ok(None) } pub(crate) fn do_insert_member(community_id: &CommunityIdOf, who: &AccountIdOf) -> DispatchResult { @@ -50,11 +58,7 @@ impl Pallet { return Err(Error::::NotAMember.into()); } - let Some(community_info) = Self::community(community_id) else { - return Err(Error::::CommunityDoesNotExist.into()); - }; - - if community_info.admin == *who { + if let Some(community_admin) = Self::get_community_admin(community_id) && community_admin == *who { return Err(Error::::CannotRemoveAdmin.into()); } diff --git a/pallets/communities/src/functions/registry.rs b/pallets/communities/src/functions/registry.rs index be6f0e78..33cf6b75 100644 --- a/pallets/communities/src/functions/registry.rs +++ b/pallets/communities/src/functions/registry.rs @@ -15,13 +15,12 @@ impl Pallet { } Info::::insert( - community_id.clone(), + community_id, CommunityInfo { - admin: who.clone(), state: Default::default(), - sufficient_asset_id: None, }, ); + GovernanceStrategy::::insert(community_id, CommunityGovernanceStrategy::AdminBased(who.clone())); Self::do_insert_member(community_id, who)?; diff --git a/pallets/communities/src/lib.rs b/pallets/communities/src/lib.rs index f6335f98..47fff9df 100644 --- a/pallets/communities/src/lib.rs +++ b/pallets/communities/src/lib.rs @@ -253,6 +253,17 @@ pub mod pallet { /// Type representing the weight of this pallet type WeightInfo: WeightInfo; + /// Type represents a vote unit + type VoteWeight: AtLeast32BitUnsigned + + Parameter + + Default + + Copy + + Saturating + + PartialOrd + + MaxEncodedLen + + From> + + From; + /// The Communities' pallet id, used for deriving its sovereign account /// ID. #[pallet::constant] @@ -274,14 +285,22 @@ pub mod pallet { /// Max amount of locations a community can hold on its metadata. #[pallet::constant] type MaxLocations: Get + Clone + PartialEq + core::fmt::Debug; + + /// Max amount of proposals a community can have enqueued + #[pallet::constant] + type MaxProposals: Get + Clone + PartialEq + core::fmt::Debug; } + /// The origin of the pallet + #[pallet::origin] + pub type Origin = types::RawOrigin; + /// Stores the basic information of the community. If a value exists for a /// specified [`ComumunityId`][`Config::CommunityId`], this means a /// community exists. #[pallet::storage] #[pallet::getter(fn community)] - pub(super) type Info = StorageMap<_, Blake2_128Concat, CommunityIdOf, CommunityInfo>; + pub(super) type Info = StorageMap<_, Blake2_128Concat, CommunityIdOf, CommunityInfo>; /// Stores the metadata regarding a community. #[pallet::storage] @@ -308,6 +327,37 @@ pub mod pallet { #[pallet::getter(fn members_count)] pub(super) type MembersCount = StorageMap<_, Blake2_128Concat, CommunityIdOf, u128>; + /// Stores the governance strategy for the community. + #[pallet::storage] + #[pallet::getter(fn governance_strategy)] + pub(super) type GovernanceStrategy = StorageMap< + _, + Blake2_128Concat, + CommunityIdOf, + CommunityGovernanceStrategy, AssetIdOf, VoteWeightFor>, + >; + + /// Stores a queue of the proposals. + #[pallet::storage] + #[pallet::getter(fn proposals)] + pub(super) type Proposals = StorageMap< + _, + Blake2_128Concat, + CommunityIdOf, + BoundedVec, ::MaxProposals>, + ValueQuery, + >; + + /// Stores a poll representing the current proposal. + #[pallet::storage] + #[pallet::getter(fn poll)] + pub(super) type Poll = StorageMap<_, Blake2_128Concat, CommunityIdOf, CommunityPoll>; + + /// Stores the list of votes for a community. + #[pallet::storage] + pub(super) type VoteOf = + StorageDoubleMap<_, Blake2_128Concat, CommunityIdOf, Blake2_128Concat, AccountIdOf, ()>; + // Pallets use events to inform users when important changes are made. // https://docs.substrate.io/main-docs/build/events-errors/ #[pallet::event] diff --git a/pallets/communities/src/tests/mock.rs b/pallets/communities/src/tests/mock.rs index 8ed98de8..b197fb97 100644 --- a/pallets/communities/src/tests/mock.rs +++ b/pallets/communities/src/tests/mock.rs @@ -12,20 +12,31 @@ use sp_runtime::{ use crate as pallet_communities; -pub type Block = frame_system::mocking::MockBlock; -pub type Balance = u128; +type Block = frame_system::mocking::MockBlock; +type WeightInfo = (); + pub type AccountId = u64; +pub type Balance = u128; pub type AssetId = u32; pub type CommunityId = u128; +pub type MembershipRank = u32; +pub type MembershipPassport = (); + +impl pallet_communities::traits::rank::MemberRank for MembershipPassport { + fn rank(&self) -> u32 { + 0 + } +} + // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( pub enum Test { Assets: pallet_assets, Balances: pallet_balances, - System: frame_system, Communities: pallet_communities, + System: frame_system, } ); @@ -72,7 +83,7 @@ impl pallet_assets::Config for Test { type Freezer = (); type Extra = (); type CallbackHandle = (); - type WeightInfo = (); + type WeightInfo = WeightInfo; type RemoveItemsLimit = ConstU32<1000>; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); @@ -84,7 +95,7 @@ impl pallet_balances::Config for Test { type RuntimeEvent = RuntimeEvent; type ExistentialDeposit = ConstU128<1>; type AccountStore = System; - type WeightInfo = (); + type WeightInfo = WeightInfo; type MaxLocks = ConstU32<10>; type MaxReserves = (); type ReserveIdentifier = [u8; 8]; @@ -96,18 +107,19 @@ impl pallet_balances::Config for Test { parameter_types! { pub const CommunitiesPalletId: PalletId = PalletId(*b"kv/comms"); - pub const CommunitiesFreezeIdentifier: () = (); #[derive(Debug, Clone, PartialEq)] - pub const MetadataUrlSize: u32 = 32; + pub const CommunitiesMetadataUrlSize: u32 = 32; + #[derive(Debug, Clone, PartialEq)] + pub const CommunitiesMaxUrls: u32 = 5; #[derive(Debug, Clone, PartialEq)] - pub const MaxUrls: u32 = 5; + pub const CommunitiesMaxLocations: u32 = 2; #[derive(Debug, Clone, PartialEq)] - pub const MaxLocations: u32 = 2; + pub const CommunitiesMaxProposals: u32 = 2; } impl pallet_communities::Config for Test { type RuntimeEvent = RuntimeEvent; - type WeightInfo = (); + type WeightInfo = WeightInfo; type Assets = Assets; type Balances = Balances; type CommunityId = CommunityId; @@ -116,10 +128,11 @@ impl pallet_communities::Config for Test { type MembershipPassport = MembershipPassport; type PalletId = CommunitiesPalletId; - type FreezeIdentifier = CommunitiesFreezeIdentifier; - type MetadataUrlSize = MetadataUrlSize; - type MaxUrls = MaxUrls; - type MaxLocations = MaxLocations; + type FreezeIdentifier = ::FreezeIdentifier; + type MetadataUrlSize = CommunitiesMetadataUrlSize; + type MaxUrls = CommunitiesMaxUrls; + type MaxLocations = CommunitiesMaxLocations; + type MaxProposals = CommunitiesMaxProposals; } // Build genesis storage according to the mock runtime. diff --git a/pallets/communities/src/tests/registry.rs b/pallets/communities/src/tests/registry.rs index 6e18c486..b90a7f12 100644 --- a/pallets/communities/src/tests/registry.rs +++ b/pallets/communities/src/tests/registry.rs @@ -1,5 +1,5 @@ use super::*; -use crate::{Event, Info}; +use crate::{Event, GovernanceStrategy, Info}; use sp_runtime::BoundedVec; mod apply { @@ -12,11 +12,11 @@ mod apply { Info::::insert( COMMUNITY, CommunityInfo { - admin: COMMUNITY_ADMIN, state: CommunityState::Awaiting, - sufficient_asset_id: None, }, ); + GovernanceStrategy::::insert(COMMUNITY, CommunityGovernanceStrategy::AdminBased(COMMUNITY_ADMIN)); + assert_ok!(Communities::do_insert_member(&COMMUNITY, &COMMUNITY_ADMIN)); // Should fail adding the community diff --git a/pallets/communities/src/types/governance.rs b/pallets/communities/src/types/governance.rs new file mode 100644 index 00000000..7abb031a --- /dev/null +++ b/pallets/communities/src/types/governance.rs @@ -0,0 +1,88 @@ +use super::*; +use frame_support::traits::Bounded; + +/// This structure holds a governance strategy. This defines how to behave +/// when ensuring privileged calls and deciding executing +/// calls +#[derive(TypeInfo, Encode, Decode, MaxEncodedLen, Clone, Eq, PartialEq, Debug)] +#[scale_info(skip_type_params(AccountId, AssetId))] +pub enum CommunityGovernanceStrategy { + /// The community governance lies in the shoulders of the admin of it. + /// + /// This is equivalent to `RawOrigin::Member` on collectives-pallet, or + /// `BodyPart::Voice` on XCM. + AdminBased(AccountId), + /// The community governance relies on a member count-based (one member, + /// one vote) poll. + /// + /// This is equivalent to `RawOrigin::Members` on collectives-pallet, or + /// `BodyPart::Members` on XCM. + MemberCountPoll { min: VoteWeight }, + /// The community governance relies on an asset-weighed (one token, + /// one vote) poll. + /// + /// This is equivalent to `RawOrigin::Members` on collectives-pallet, or + /// `BodyPart::Fraction` on XCM. + AssetWeighedPoll { + asset_id: AssetId, + #[codec(compact)] + num: VoteWeight, + #[codec(compact)] + denum: VoteWeight, + }, + /// The community governance relies on an ranked-weighed (one member vote, + /// the number of votes corresponding to the rank of member) poll, + /// + /// This is equivalent to `RawOrigin::Members` on collectives-pallet, or + /// `BodyPart::Fraction` on XCM. + RankedWeighedPoll { + #[codec(compact)] + num: VoteWeight, + #[codec(compact)] + denum: VoteWeight, + }, +} + +/// This structure holds the basic definition of a proposal. +/// It includes the information about the proposer, +/// the hash of the call to be executed if approved, +/// and the information of the +#[derive(TypeInfo, Encode, Decode, MaxEncodedLen, Clone, PartialEq, Eq, Debug)] +#[scale_info(skip_type_params(T))] +pub struct CommunityProposal { + pub(crate) proposer: AccountIdOf, + pub(crate) call: Bounded>, +} + +/// This structure holds a poll and the methods to increase/decrease +/// votes +#[derive(TypeInfo, Encode, Decode, MaxEncodedLen, Clone)] +#[scale_info(skip_type_params(T))] +pub struct CommunityPoll { + #[codec(compact)] + pub(crate) ayes: VoteWeightFor, + #[codec(compact)] + pub(crate) nays: VoteWeightFor, +} + +impl Default for CommunityPoll { + fn default() -> Self { + Self { + ayes: Default::default(), + nays: Default::default(), + } + } +} + +/// This enum defines a vote in a community poll +#[derive(TypeInfo, Encode, Decode, MaxEncodedLen, Clone)] +pub enum CommunityPollVote { + Aye(VoteWeightFor), + Nay(VoteWeightFor), +} + +/// This enum describes the outcome of a closed poll +pub enum PollOutcome { + Approved, + Rejected, +} diff --git a/pallets/communities/src/types/mod.rs b/pallets/communities/src/types/mod.rs index a150fb74..20a0f412 100644 --- a/pallets/communities/src/types/mod.rs +++ b/pallets/communities/src/types/mod.rs @@ -6,12 +6,16 @@ use scale_info::{prelude::vec::Vec, TypeInfo}; use sp_runtime::traits::StaticLookup; use crate::Config; -use frame_system::Config as SystemConfig; +pub(crate) use frame_system::Config as SystemConfig; +pub use governance::*; +pub use origin::*; pub use parameters::*; pub use primitives::*; pub use registry::*; +mod governance; +mod origin; mod parameters; mod primitives; mod registry; diff --git a/pallets/communities/src/types/origin.rs b/pallets/communities/src/types/origin.rs new file mode 100644 index 00000000..67b17b34 --- /dev/null +++ b/pallets/communities/src/types/origin.rs @@ -0,0 +1,29 @@ +use super::*; + +/// The origin of the comnunity governance, as well as the origin +/// sent to emit on behalf of the pallet +#[derive(TypeInfo, Encode, Decode, MaxEncodedLen, Clone, Eq, PartialEq, Debug)] +#[scale_info(skip_type_params(T))] +pub struct RawOrigin { + /// The community id. Used to get the account of the + /// community for certain origin conversions + pub community_id: CommunityIdOf, + /// + pub body_part: BodyPart>, +} + +#[derive(TypeInfo, Encode, Decode, MaxEncodedLen, Clone, Eq, PartialEq, Debug)] +#[scale_info(skip_type_params(VotesCount))] +pub enum BodyPart { + Voice, + Members { + #[codec(compact)] + min: VotesCount, + }, + Fraction { + #[codec(compact)] + num: VotesCount, + #[codec(compact)] + denum: VotesCount, + }, +} diff --git a/pallets/communities/src/types/parameters.rs b/pallets/communities/src/types/parameters.rs index c936feff..8822732f 100644 --- a/pallets/communities/src/types/parameters.rs +++ b/pallets/communities/src/types/parameters.rs @@ -5,9 +5,14 @@ pub type BalanceOf = <::Assets as InspectFuns>>:: pub type NativeBalanceOf = <::Balances as Inspect>>::Balance; pub type AccountIdOf = ::AccountId; pub type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + pub type CommunityIdOf = ::CommunityId; -pub type MembershipPassportOf = ::MembershipPassport; pub type MemberListOf = Vec>; + pub type RuntimeCallOf = ::RuntimeCall; +pub type MembershipPassportOf = ::MembershipPassport; +pub type MembershipRankOf = ::MembershipRank; +pub type VoteWeightFor = ::VoteWeight; + pub type Cell = u32; diff --git a/pallets/communities/src/types/registry.rs b/pallets/communities/src/types/registry.rs index eb343ad1..5bd1d4b4 100644 --- a/pallets/communities/src/types/registry.rs +++ b/pallets/communities/src/types/registry.rs @@ -7,16 +7,9 @@ use super::*; /// /// [1]: `frame_system::Config::AccountId` #[derive(TypeInfo, Encode, Decode, MaxEncodedLen)] -#[scale_info(skip_type_params(T))] -pub struct CommunityInfo { - /// The [`AccountId`][1] of the current community administrator. - /// - /// [1]: `frame_system::Config::AccountId` - pub admin: AccountIdOf, +pub struct CommunityInfo { /// The current state of the community. pub state: CommunityState, - /// The ID of the asset marked by the community as sufficient. - pub sufficient_asset_id: Option>, } /// The current state of the community. It represents whether a community From 7e93be2394416ccff8261f264814d618f086d881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Fri, 13 Oct 2023 11:21:08 -0500 Subject: [PATCH 3/7] feat(pallets/communities): add governance (polls/proposals) handling functions --- .../src/functions/governance/mod.rs | 4 + .../src/functions/governance/poll.rs | 136 ++++++++++++++++++ .../src/functions/governance/proposals.rs | 57 ++++++++ pallets/communities/src/functions/mod.rs | 3 + pallets/communities/src/functions/origin.rs | 23 +++ pallets/communities/src/lib.rs | 47 +++++- pallets/communities/src/tests/mock.rs | 58 +++++++- pallets/communities/src/tests/mod.rs | 3 + 8 files changed, 325 insertions(+), 6 deletions(-) create mode 100644 pallets/communities/src/functions/governance/mod.rs create mode 100644 pallets/communities/src/functions/governance/poll.rs create mode 100644 pallets/communities/src/functions/governance/proposals.rs create mode 100644 pallets/communities/src/functions/origin.rs diff --git a/pallets/communities/src/functions/governance/mod.rs b/pallets/communities/src/functions/governance/mod.rs new file mode 100644 index 00000000..df85c929 --- /dev/null +++ b/pallets/communities/src/functions/governance/mod.rs @@ -0,0 +1,4 @@ +pub(self) use super::*; + +mod poll; +mod proposals; diff --git a/pallets/communities/src/functions/governance/poll.rs b/pallets/communities/src/functions/governance/poll.rs new file mode 100644 index 00000000..21527bde --- /dev/null +++ b/pallets/communities/src/functions/governance/poll.rs @@ -0,0 +1,136 @@ +use super::*; +use sp_runtime::Permill; + +impl Pallet { + pub(crate) fn do_initiate_poll(community_id: &CommunityIdOf) -> DispatchResult { + Poll::::insert(community_id, CommunityPoll::default()); + + Ok(()) + } + + pub(crate) fn do_vote_in_poll( + who: &AccountIdOf, + community_id: &CommunityIdOf, + vote: CommunityPollVote, + ) -> DispatchResult { + Poll::::try_mutate(community_id, |value| { + let Some(mut poll) = value.clone() else { + return Err(Error::::PollAlreadyClosed)?; + }; + + match vote { + CommunityPollVote::Aye(weight) => { + poll.ayes = poll + .ayes + .saturating_add(Self::get_vote_weight(who, community_id, weight)?) + } + CommunityPollVote::Nay(weight) => { + poll.ayes = poll + .nays + .saturating_add(Self::get_vote_weight(who, community_id, weight)?) + } + } + + *value = Some(poll); + + Ok(()) + }) + } + + #[allow(dead_code)] + pub(crate) fn do_close_poll(community_id: &CommunityIdOf) -> DispatchResult { + let poll_outcome = Self::do_calculate_poll_outcome(community_id)?; + let proposal = Self::do_deequeue_proposal(community_id)?; + + match poll_outcome { + // Schedule the approved proposal + PollOutcome::Approved => Self::do_call_execute(community_id, proposal), + // Do nothing + PollOutcome::Rejected => Ok(()), + }?; + + Ok(()) + } +} + +impl Pallet { + fn get_vote_weight( + who: &AccountIdOf, + community_id: &CommunityIdOf, + _input_weight: VoteWeightFor, + ) -> Result, DispatchError> { + let governance_strategy = Self::governance_strategy(community_id).ok_or(Error::::CommunityDoesNotExist)?; + + match governance_strategy { + CommunityGovernanceStrategy::AdminBased(admin) => { + if *who == admin { + Ok(1u32.into()) + } else { + Ok(0u32.into()) + } + } + CommunityGovernanceStrategy::MemberCountPoll { min: _ } => Ok(1u32.into()), + CommunityGovernanceStrategy::AssetWeighedPoll { + asset_id: _, + num: _, + denum: _, + } => todo!(), + CommunityGovernanceStrategy::RankedWeighedPoll { num: _, denum: _ } => { + // use crate::traits::rank::GetRank; + + // let membership = Self::ensure_member(community_id, who)?; + // let rank = as + // GetRank>::rank_of(&membership); + + // Ok(input_weight.max(rank.into())) + todo!() + } + } + } + + fn do_calculate_poll_outcome(community_id: &CommunityIdOf) -> Result { + let governance_strategy = Self::governance_strategy(community_id).ok_or(Error::::CommunityDoesNotExist)?; + let poll = Self::poll(community_id).ok_or(Error::::PollAlreadyClosed)?; + + let (ayes, nays) = (poll.ayes, poll.nays); + + match governance_strategy { + CommunityGovernanceStrategy::AdminBased(_) => Ok(PollOutcome::Approved), + CommunityGovernanceStrategy::MemberCountPoll { min } => { + if ayes.saturating_add(nays) >= min.into() { + if ayes > nays { + Ok(PollOutcome::Approved) + } else { + Ok(PollOutcome::Rejected) + } + } else { + Err(Error::::CannotClosePoll.into()) + } + } + CommunityGovernanceStrategy::AssetWeighedPoll { + asset_id: _, + num, + denum, + } => { + let criteria_fraction = Permill::from_rational(num, denum); + let poll_fraction = Permill::from_rational(ayes, ayes.saturating_add(nays)); + + if poll_fraction >= criteria_fraction { + Ok(PollOutcome::Approved) + } else { + Ok(PollOutcome::Rejected) + } + } + CommunityGovernanceStrategy::RankedWeighedPoll { num, denum } => { + let criteria_fraction = Permill::from_rational(num, denum); + let poll_fraction = Permill::from_rational(ayes, ayes.saturating_add(nays)); + + if poll_fraction >= criteria_fraction { + Ok(PollOutcome::Approved) + } else { + Ok(PollOutcome::Rejected) + } + } + } + } +} diff --git a/pallets/communities/src/functions/governance/proposals.rs b/pallets/communities/src/functions/governance/proposals.rs new file mode 100644 index 00000000..a4ba2ba0 --- /dev/null +++ b/pallets/communities/src/functions/governance/proposals.rs @@ -0,0 +1,57 @@ +use super::*; +pub(self) use frame_support::traits::{schedule::v3::Anon, StorePreimage}; +pub(self) use sp_runtime::traits::Zero; + +impl Pallet { + pub(crate) fn do_create_proposal( + proposer: &AccountIdOf, + community_id: &CommunityIdOf, + call: RuntimeCallOf, + ) -> DispatchResult { + let bounded_call = T::Preimage::bound(call).map_err(|_| Error::::CannotEncodeCall)?; + + Self::do_enqueue_proposal( + community_id, + CommunityProposal { + proposer: proposer.clone(), + call: bounded_call, + }, + )?; + + Ok(()) + } + + #[allow(dead_code)] + pub(crate) fn do_call_execute(community_id: &CommunityIdOf, proposal: CommunityProposal) -> DispatchResult { + let origin = Self::get_origin(community_id)?; + + T::Scheduler::schedule( + frame_support::traits::schedule::DispatchTime::After(Zero::zero()), + None, + Default::default(), + origin.into(), + proposal.call, + )?; + + Ok(()) + } + + fn do_enqueue_proposal(community_id: &CommunityIdOf, proposal: CommunityProposal) -> DispatchResult { + if Proposals::::decode_len(community_id).unwrap_or_default() >= T::MaxProposals::get() as usize { + Err(Error::::ExceededMaxProposals)?; + } + + Proposals::::try_append(community_id, proposal).map_err(|_| Error::::CannotEnqueueProposal)?; + + Ok(()) + } + + pub(crate) fn do_deequeue_proposal(community_id: &CommunityIdOf) -> Result, DispatchError> { + Proposals::::try_mutate(community_id, |proposals| { + let first_proposal = proposals.first().ok_or(Error::::CannotDequeueProposal)?.clone(); + proposals.remove(0); + + Ok(first_proposal) + }) + } +} diff --git a/pallets/communities/src/functions/mod.rs b/pallets/communities/src/functions/mod.rs index a75ef9e5..09b65e2b 100644 --- a/pallets/communities/src/functions/mod.rs +++ b/pallets/communities/src/functions/mod.rs @@ -5,10 +5,13 @@ pub(self) use frame_support::traits::tokens::{ fungibles::Mutate as MutateFuns, }; pub(self) use frame_system::pallet_prelude::*; +pub(self) use sp_runtime::Saturating; pub(self) use types::*; mod challenges; mod getters; +mod governance; mod membership; +mod origin; mod registry; mod treasury; diff --git a/pallets/communities/src/functions/origin.rs b/pallets/communities/src/functions/origin.rs new file mode 100644 index 00000000..624c80f0 --- /dev/null +++ b/pallets/communities/src/functions/origin.rs @@ -0,0 +1,23 @@ +use super::*; +use frame_support::traits::{EnsureOrigin, OriginTrait}; + +impl Pallet { + pub(crate) fn get_origin(community_id: &CommunityIdOf) -> Result, DispatchError> { + let governance_strategy = + GovernanceStrategy::::get(community_id).ok_or(Error::::CommunityDoesNotExist)?; + + Ok(RawOrigin:: { + community_id: community_id.clone(), + body_part: match governance_strategy { + CommunityGovernanceStrategy::AdminBased(_) => BodyPart::Voice, + CommunityGovernanceStrategy::MemberCountPoll { min } => BodyPart::Members { min }, + CommunityGovernanceStrategy::AssetWeighedPoll { + asset_id: _, + num, + denum, + } => BodyPart::Fraction { num, denum }, + CommunityGovernanceStrategy::RankedWeighedPoll { num, denum } => BodyPart::Fraction { num, denum }, + }, + }) + } +} diff --git a/pallets/communities/src/lib.rs b/pallets/communities/src/lib.rs index 47fff9df..f75edfef 100644 --- a/pallets/communities/src/lib.rs +++ b/pallets/communities/src/lib.rs @@ -1,4 +1,6 @@ #![cfg_attr(not(feature = "std"), no_std)] +#![feature(let_chains)] +#![feature(trait_alias)] //! # Communities Pallet //! @@ -193,6 +195,7 @@ //! [g02]: `crate::Pallet::membership` //! [g03]: `crate::Pallet::members_count` pub use pallet::*; +pub use types::RawOrigin; #[cfg(test)] mod tests; @@ -202,6 +205,7 @@ mod benchmarking; mod functions; +pub mod traits; pub mod types; pub mod weights; pub use weights::*; @@ -210,12 +214,18 @@ pub use weights::*; pub mod pallet { use super::*; use frame_support::{ - pallet_prelude::*, - traits::tokens::{fungible, fungibles}, + pallet_prelude::{DispatchResult, ValueQuery, *}, + traits::{ + schedule::v3::Anon as AnonV3, + tokens::{fungible, fungibles}, + CallerTrait, QueryPreimage, StorePreimage, + }, Parameter, }; - use frame_system::pallet_prelude::{OriginFor, *}; - use sp_runtime::traits::StaticLookup; + use frame_system::pallet_prelude::{BlockNumberFor, OriginFor, *}; + use scale_info::prelude::boxed::Box; + use sp_runtime::traits::{AtLeast32BitUnsigned, Saturating, StaticLookup}; + use traits::rank::MemberRank; use types::*; #[pallet::pallet] @@ -226,7 +236,7 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// This type represents an unique ID for the community - type CommunityId: Parameter + MaxEncodedLen; + type CommunityId: Default + Parameter + MaxEncodedLen; /// This type represents a rank for a member in a community type MembershipRank: Default + AtLeast32BitUnsigned + Parameter + MaxEncodedLen + PartialOrd; @@ -253,6 +263,15 @@ pub mod pallet { /// Type representing the weight of this pallet type WeightInfo: WeightInfo; + /// Type represents the actions for storing call preimages + type Preimage: QueryPreimage + StorePreimage; + + /// Type represents interactions with the dispatch scheduler + type Scheduler: AnonV3, RuntimeCallOf, Self::PalletsOrigin>; + + /// The caller origin, overarching type of all pallets origins. + type PalletsOrigin: From> + CallerTrait + MaxEncodedLen; + /// Type represents a vote unit type VoteWeight: AtLeast32BitUnsigned + Parameter @@ -397,6 +416,24 @@ pub mod pallet { /// [`CommunityId`][`Config::CommunityId`], especially if it's the /// only member remaining. Please consider changing the admin first. CannotRemoveAdmin, + /// It is not possible to encode the call into a preimage. + CannotEncodeCall, + /// It is not possible to enqueue a proposal for a community + CannotEnqueueProposal, + /// The community has exceeded the max amount of enqueded proposals at + /// this moment. + ExceededMaxProposals, + /// It is not possible to dequeue a proposal + CannotDequeueProposal, + /// A call for the spciefied [Hash][`frame_system::Config::Hash`] is not + /// found + CannotFindCall, + /// The poll the caller is trying to open is already opened. + PollAlreadyOpened, + /// The poll the caller is trying to close is already closed. + PollAlreadyClosed, + /// The criteria needed to close the poll is not met + CannotClosePoll, } // Dispatchable functions allows users to interact with the pallet and invoke diff --git a/pallets/communities/src/tests/mock.rs b/pallets/communities/src/tests/mock.rs index b197fb97..46c8bb49 100644 --- a/pallets/communities/src/tests/mock.rs +++ b/pallets/communities/src/tests/mock.rs @@ -1,6 +1,7 @@ use frame_support::{ parameter_types, - traits::{AsEnsureOriginWithArg, ConstU128, ConstU16, ConstU32, ConstU64}, + traits::{AsEnsureOriginWithArg, ConstU128, ConstU16, ConstU32, ConstU64, EqualPrivilegeOnly}, + weights::Weight, PalletId, }; use frame_system::{EnsureRoot, EnsureSigned}; @@ -22,6 +23,7 @@ pub type CommunityId = u128; pub type MembershipRank = u32; pub type MembershipPassport = (); +pub type VoteWeight = u128; impl pallet_communities::traits::rank::MemberRank for MembershipPassport { fn rank(&self) -> u32 { @@ -36,10 +38,31 @@ frame_support::construct_runtime!( Assets: pallet_assets, Balances: pallet_balances, Communities: pallet_communities, + Preimage: pallet_preimage, + Scheduler: pallet_scheduler, System: frame_system, } ); +impl + Into, pallet_communities::RawOrigin>> + for pallet_communities::RawOrigin +{ + fn into(self) -> Result, pallet_communities::RawOrigin> { + Ok(frame_system::RawOrigin::Signed( + pallet_communities::Pallet::::get_community_account_id(&self.community_id), + )) + } +} + +impl From> for frame_system::RawOrigin { + fn from(o: pallet_communities::RawOrigin) -> frame_system::RawOrigin { + frame_system::RawOrigin::Signed(pallet_communities::Pallet::::get_community_account_id( + &o.community_id, + )) + } +} + impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); @@ -105,6 +128,35 @@ impl pallet_balances::Config for Test { type MaxFreezes = ConstU32<10>; } +parameter_types! { + pub MaximumSchedulerWeight: Weight = Weight::from_parts(1_000_000_000, 1_048_576); + pub const MaxScheduledPerBlock: u32 = 512; + pub const PreimageBaseDeposit: u64 = 2; + pub const PreimageByteDeposit: u64 = 1; +} + +impl pallet_preimage::Config for Test { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type ManagerOrigin = EnsureSigned; + type BaseDeposit = PreimageBaseDeposit; + type ByteDeposit = PreimageByteDeposit; +} + +impl pallet_scheduler::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type PalletsOrigin = OriginCaller; + type RuntimeCall = RuntimeCall; + type MaximumWeight = MaximumSchedulerWeight; + type ScheduleOrigin = EnsureRoot; + type OriginPrivilegeCmp = EqualPrivilegeOnly; + type MaxScheduledPerBlock = MaxScheduledPerBlock; + type WeightInfo = pallet_scheduler::weights::SubstrateWeight; + type Preimages = Preimage; +} + parameter_types! { pub const CommunitiesPalletId: PalletId = PalletId(*b"kv/comms"); #[derive(Debug, Clone, PartialEq)] @@ -126,12 +178,16 @@ impl pallet_communities::Config for Test { type MembershipRank = MembershipRank; type MembershipPassport = MembershipPassport; + type VoteWeight = VoteWeight; type PalletId = CommunitiesPalletId; type FreezeIdentifier = ::FreezeIdentifier; type MetadataUrlSize = CommunitiesMetadataUrlSize; type MaxUrls = CommunitiesMaxUrls; type MaxLocations = CommunitiesMaxLocations; + type Preimage = Preimage; + type Scheduler = Scheduler; + type PalletsOrigin = OriginCaller; type MaxProposals = CommunitiesMaxProposals; } diff --git a/pallets/communities/src/tests/mod.rs b/pallets/communities/src/tests/mod.rs index fd5e883a..1d5e330b 100644 --- a/pallets/communities/src/tests/mod.rs +++ b/pallets/communities/src/tests/mod.rs @@ -5,11 +5,14 @@ use sp_runtime::{ArithmeticError, DispatchError}; mod mock; pub use mock::*; +mod helpers; + type Error = crate::Error; const COMMUNITY: CommunityId = 1; const COMMUNITY_ADMIN: AccountId = 42; +mod governance; mod membership; mod registry; mod treasury; From 1b49304a91298045b613c735d5a0595d54055ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Fri, 13 Oct 2023 11:21:27 -0500 Subject: [PATCH 4/7] feat(pallets/communities): define open_proposal/execute_call --- pallets/communities/Cargo.toml | 2 + pallets/communities/src/lib.rs | 63 ++++++++ pallets/communities/src/tests/governance.rs | 159 ++++++++++++++++++++ pallets/communities/src/tests/helpers.rs | 24 +++ pallets/communities/src/weights.rs | 46 ++++++ 5 files changed, 294 insertions(+) create mode 100644 pallets/communities/src/tests/governance.rs create mode 100644 pallets/communities/src/tests/helpers.rs diff --git a/pallets/communities/Cargo.toml b/pallets/communities/Cargo.toml index 7e53a953..d9f31fd9 100644 --- a/pallets/communities/Cargo.toml +++ b/pallets/communities/Cargo.toml @@ -29,6 +29,8 @@ sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "polkado pallet-assets = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } pallet-balances = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } +pallet-preimage = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } +pallet-scheduler = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } [features] default = ["std"] diff --git a/pallets/communities/src/lib.rs b/pallets/communities/src/lib.rs index f75edfef..3422143f 100644 --- a/pallets/communities/src/lib.rs +++ b/pallets/communities/src/lib.rs @@ -393,6 +393,13 @@ pub mod pallet { urls: Option, T::MaxUrls>>, locations: Option>, }, + /// A proposal has been enqueued and is ready to be + /// decided whenever it comes to the head of the comomunity + /// proposals queue + ProposalEnqueued { + community_id: CommunityIdOf, + proposer: AccountIdOf, + }, } // Errors inform users that something worked or went wrong. @@ -589,5 +596,61 @@ pub mod pallet { Ok(()) } + + /// Schedules a call on behalf of the treasury account. It should be + /// exectued within the following block + #[pallet::call_index(6)] + pub fn open_proposal( + origin: OriginFor, + community_id: T::CommunityId, + call: Box>, + ) -> DispatchResult { + let caller = Self::ensure_origin_member(origin, &community_id)?; + Self::ensure_active(&community_id)?; + + Self::do_create_proposal(&caller, &community_id, *call)?; + + Self::deposit_event(Event::ProposalEnqueued { + community_id, + proposer: caller, + }); + + Ok(()) + } + + /// Creates a proposal, and immediately closes a poll, effectively + /// executing a call (to be dispatched by the runtime scheduler). Called + /// by the community admin when the current governance strategy for the + /// community is + /// [`AdminBased`][`CommunityGovernanceStrategy::AdminBased`]. + + #[pallet::call_index(9)] + pub fn execute_call( + origin: OriginFor, + community_id: T::CommunityId, + call: Box>, + ) -> DispatchResult { + let Some(caller) = Self::ensure_origin_privileged(origin, &community_id)? else { + Err(DispatchError::BadOrigin)? + }; + Self::ensure_active(&community_id)?; + + Self::do_create_proposal(&caller, &community_id, *call)?; + Self::deposit_event(Event::ProposalEnqueued { + community_id: community_id.clone(), + proposer: caller.clone(), + }); + + Self::do_initiate_poll(&community_id)?; + // Deposit event for Poll Initiated + + Self::do_vote_in_poll(&caller, &community_id, CommunityPollVote::Aye(1u32.into()))?; + // Deposit event for Poll Voted + + Self::do_close_poll(&community_id)?; + // Deposit event for Poll Closed + + Ok(()) + } } } diff --git a/pallets/communities/src/tests/governance.rs b/pallets/communities/src/tests/governance.rs new file mode 100644 index 00000000..a72d58c6 --- /dev/null +++ b/pallets/communities/src/tests/governance.rs @@ -0,0 +1,159 @@ +use super::*; +use crate::{tests::helpers::run_to_block, Event}; +use sp_core::{blake2_256, H256}; + +fn setup() { + super::setup(); + assert_ok!(Communities::do_force_complete_challenge(&COMMUNITY)); +} + +fn call_remark() -> RuntimeCall { + frame_system::Call::remark_with_event { + remark: b"Hello, governance!".to_vec(), + } + .into() +} + +mod open_proposal { + use super::*; + + #[test] + fn fails_if_not_called_by_a_community_member() { + new_test_ext().execute_with(|| { + setup(); + + assert_noop!( + Communities::open_proposal(RuntimeOrigin::none(), COMMUNITY, Box::new(call_remark())), + DispatchError::BadOrigin + ); + + assert_noop!( + Communities::open_proposal(RuntimeOrigin::root(), COMMUNITY, Box::new(call_remark())), + DispatchError::BadOrigin + ); + }) + } + + #[test] + fn it_works() { + new_test_ext().execute_with(|| { + setup(); + + assert_ok!(Communities::open_proposal( + RuntimeOrigin::signed(COMMUNITY_ADMIN), + COMMUNITY, + Box::new(call_remark()) + )); + + run_to_block(3); + + assert!(System::events().iter().any(|record| { + record.event + == Event::::ProposalEnqueued { + community_id: COMMUNITY, + proposer: COMMUNITY_ADMIN, + } + .into() + })); + + assert!(Communities::proposals(COMMUNITY).iter().len() == 1); + + Communities::proposals(COMMUNITY).iter().for_each(|p| { + println!("{:#?}", &p); + }); + }); + } +} + +mod execute_call { + use super::*; + + const COMMUNITY_MEMBER_1: AccountId = 43; + + fn setup() { + super::setup(); + assert_ok!(Communities::add_member( + RuntimeOrigin::signed(COMMUNITY_ADMIN), + COMMUNITY, + COMMUNITY_MEMBER_1 + )); + } + + #[test] + fn fails_if_not_called_by_a_community_admin() { + new_test_ext().execute_with(|| { + setup(); + + assert_noop!( + Communities::execute_call(RuntimeOrigin::none(), COMMUNITY, Box::new(call_remark())), + DispatchError::BadOrigin + ); + + assert_noop!( + Communities::execute_call(RuntimeOrigin::root(), COMMUNITY, Box::new(call_remark())), + DispatchError::BadOrigin + ); + + assert_noop!( + Communities::execute_call( + RuntimeOrigin::signed(COMMUNITY_MEMBER_1), + COMMUNITY, + Box::new(call_remark()) + ), + DispatchError::BadOrigin + ); + }) + } + + #[test] + fn it_works() { + new_test_ext().execute_with(|| { + setup(); + + assert_ok!(Communities::execute_call( + RuntimeOrigin::signed(COMMUNITY_ADMIN), + COMMUNITY, + Box::new(call_remark()) + )); + + println!("Events for block 1"); + System::events().iter().for_each(|record| { + println!("{:#?}", &record.event); + }); + + assert!(System::events().iter().any(|record| { + record.event + == Event::::ProposalEnqueued { + community_id: COMMUNITY, + proposer: COMMUNITY_ADMIN, + } + .into() + })); + + run_to_block(2); + + println!("Events for block 2"); + System::events().iter().for_each(|record| { + println!("{:#?}", &record.event); + }); + + run_to_block(3); + + println!("Events for block 3"); + System::events().iter().for_each(|record| { + println!("{:#?}", &record.event); + }); + + let community_account_id = Communities::get_community_account_id(&COMMUNITY); + assert!(System::events().iter().any(|record| { + println!("{:#?}", &record.event); + record.event + == frame_system::Event::::Remarked { + sender: community_account_id, + hash: H256::from(blake2_256(&b"Hello, governance!".to_vec())), + } + .into() + })); + }); + } +} diff --git a/pallets/communities/src/tests/helpers.rs b/pallets/communities/src/tests/helpers.rs new file mode 100644 index 00000000..69aaf81d --- /dev/null +++ b/pallets/communities/src/tests/helpers.rs @@ -0,0 +1,24 @@ +use super::*; +use frame_support::traits::{OnFinalize, OnInitialize}; + +pub(super) fn run_to_block(n: u64) { + let current_block = System::block_number(); + assert!(n > current_block); + + while System::block_number() < n { + Assets::on_finalize(System::block_number()); + Balances::on_finalize(System::block_number()); + Communities::on_finalize(System::block_number()); + Preimage::on_finalize(System::block_number()); + Scheduler::on_finalize(System::block_number()); + + System::reset_events(); + System::set_block_number(System::block_number() + 1); + + System::on_initialize(System::block_number()); + Assets::on_initialize(System::block_number()); + Balances::on_initialize(System::block_number()); + Preimage::on_initialize(System::block_number()); + Scheduler::on_initialize(System::block_number()); + } +} diff --git a/pallets/communities/src/weights.rs b/pallets/communities/src/weights.rs index 814881ab..08da7741 100644 --- a/pallets/communities/src/weights.rs +++ b/pallets/communities/src/weights.rs @@ -40,6 +40,8 @@ pub trait WeightInfo { fn remove_member() -> Weight; fn assets_transfer() -> Weight; fn balance_transfer() -> Weight; + fn open_proposal() -> Weight; + fn execute_call() -> Weight; } /// Weights for pallet_communities using the Substrate node and recommended hardware. @@ -110,6 +112,28 @@ impl WeightInfo for SubstrateWeight { Weight::from_parts(9_000_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + + /// Storage: Communities Something (r:0 w:1) + /// Proof: Communities Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn open_proposal() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(9_000_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + + /// Storage: Communities Something (r:0 w:1) + /// Proof: Communities Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn execute_call() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(9_000_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } } // For backwards compatibility and tests @@ -179,4 +203,26 @@ impl WeightInfo for () { Weight::from_parts(9_000_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + + /// Storage: Communities Something (r:0 w:1) + /// Proof: Communities Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn open_proposal() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(9_000_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + + /// Storage: Communities Something (r:0 w:1) + /// Proof: Communities Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn execute_call() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(9_000_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } } From 54ad06d2136a9eaab102dcc3016e84d2aa245e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Mon, 16 Oct 2023 17:13:36 -0500 Subject: [PATCH 5/7] change(pallets/communities): explictly state call origin when creating proposal --- .../src/functions/governance/poll.rs | 2 +- .../src/functions/governance/proposals.rs | 15 +-- pallets/communities/src/functions/origin.rs | 39 ++++--- pallets/communities/src/lib.rs | 19 +++- pallets/communities/src/tests/governance.rs | 107 +++++++++++++----- pallets/communities/src/tests/mock.rs | 19 ---- pallets/communities/src/types/governance.rs | 1 + pallets/communities/src/types/origin.rs | 12 +- pallets/communities/src/types/parameters.rs | 2 + pallets/communities/src/weights.rs | 6 +- 10 files changed, 134 insertions(+), 88 deletions(-) diff --git a/pallets/communities/src/functions/governance/poll.rs b/pallets/communities/src/functions/governance/poll.rs index 21527bde..7f003219 100644 --- a/pallets/communities/src/functions/governance/poll.rs +++ b/pallets/communities/src/functions/governance/poll.rs @@ -44,7 +44,7 @@ impl Pallet { match poll_outcome { // Schedule the approved proposal - PollOutcome::Approved => Self::do_call_execute(community_id, proposal), + PollOutcome::Approved => Self::do_execute_proposal(proposal), // Do nothing PollOutcome::Rejected => Ok(()), }?; diff --git a/pallets/communities/src/functions/governance/proposals.rs b/pallets/communities/src/functions/governance/proposals.rs index a4ba2ba0..44d48a85 100644 --- a/pallets/communities/src/functions/governance/proposals.rs +++ b/pallets/communities/src/functions/governance/proposals.rs @@ -6,8 +6,11 @@ impl Pallet { pub(crate) fn do_create_proposal( proposer: &AccountIdOf, community_id: &CommunityIdOf, + call_origin: PalletsOriginOf, call: RuntimeCallOf, ) -> DispatchResult { + Self::ensure_proposal_origin(community_id, call_origin.clone())?; + let bounded_call = T::Preimage::bound(call).map_err(|_| Error::::CannotEncodeCall)?; Self::do_enqueue_proposal( @@ -15,6 +18,7 @@ impl Pallet { CommunityProposal { proposer: proposer.clone(), call: bounded_call, + origin: call_origin, }, )?; @@ -22,18 +26,15 @@ impl Pallet { } #[allow(dead_code)] - pub(crate) fn do_call_execute(community_id: &CommunityIdOf, proposal: CommunityProposal) -> DispatchResult { - let origin = Self::get_origin(community_id)?; - + pub(crate) fn do_execute_proposal(proposal: CommunityProposal) -> DispatchResult { T::Scheduler::schedule( frame_support::traits::schedule::DispatchTime::After(Zero::zero()), None, Default::default(), - origin.into(), + proposal.origin, proposal.call, - )?; - - Ok(()) + ) + .and_then(|_| Ok(())) } fn do_enqueue_proposal(community_id: &CommunityIdOf, proposal: CommunityProposal) -> DispatchResult { diff --git a/pallets/communities/src/functions/origin.rs b/pallets/communities/src/functions/origin.rs index 624c80f0..058fbb39 100644 --- a/pallets/communities/src/functions/origin.rs +++ b/pallets/communities/src/functions/origin.rs @@ -1,23 +1,28 @@ use super::*; -use frame_support::traits::{EnsureOrigin, OriginTrait}; + +macro_rules! as_origin { + ($origin: ident, $t: ty) => {{ + TryInto::<$t>::try_into($origin.clone()).ok() + }}; +} impl Pallet { - pub(crate) fn get_origin(community_id: &CommunityIdOf) -> Result, DispatchError> { - let governance_strategy = - GovernanceStrategy::::get(community_id).ok_or(Error::::CommunityDoesNotExist)?; + pub(crate) fn ensure_proposal_origin( + community_id: &CommunityIdOf, + origin: PalletsOriginOf, + ) -> DispatchResult { + let community_account_id = Self::get_community_account_id(community_id); - Ok(RawOrigin:: { - community_id: community_id.clone(), - body_part: match governance_strategy { - CommunityGovernanceStrategy::AdminBased(_) => BodyPart::Voice, - CommunityGovernanceStrategy::MemberCountPoll { min } => BodyPart::Members { min }, - CommunityGovernanceStrategy::AssetWeighedPoll { - asset_id: _, - num, - denum, - } => BodyPart::Fraction { num, denum }, - CommunityGovernanceStrategy::RankedWeighedPoll { num, denum } => BodyPart::Fraction { num, denum }, - }, - }) + if let Some(o) = as_origin!(origin, frame_system::Origin) { + match o { + frame_system::Origin::::Signed(account) if account == community_account_id => Ok(()), + _ => Err(Error::::InvalidProposalOrigin.into()), + } + } else { + match as_origin!(origin, pallet::Origin) { + Some(_) => Ok(()), + None => Err(Error::::InvalidProposalOrigin.into()), + } + } } } diff --git a/pallets/communities/src/lib.rs b/pallets/communities/src/lib.rs index 3422143f..a260f85c 100644 --- a/pallets/communities/src/lib.rs +++ b/pallets/communities/src/lib.rs @@ -270,7 +270,12 @@ pub mod pallet { type Scheduler: AnonV3, RuntimeCallOf, Self::PalletsOrigin>; /// The caller origin, overarching type of all pallets origins. - type PalletsOrigin: From> + CallerTrait + MaxEncodedLen; + type PalletsOrigin: From> + + From> + + TryInto> + + TryInto> + + CallerTrait + + MaxEncodedLen; /// Type represents a vote unit type VoteWeight: AtLeast32BitUnsigned @@ -312,7 +317,7 @@ pub mod pallet { /// The origin of the pallet #[pallet::origin] - pub type Origin = types::RawOrigin; + pub type Origin = types::RawOrigin, VoteWeightFor>; /// Stores the basic information of the community. If a value exists for a /// specified [`ComumunityId`][`Config::CommunityId`], this means a @@ -423,6 +428,8 @@ pub mod pallet { /// [`CommunityId`][`Config::CommunityId`], especially if it's the /// only member remaining. Please consider changing the admin first. CannotRemoveAdmin, + /// The origin specified to propose the call execution is invalid. + InvalidProposalOrigin, /// It is not possible to encode the call into a preimage. CannotEncodeCall, /// It is not possible to enqueue a proposal for a community @@ -603,12 +610,13 @@ pub mod pallet { pub fn open_proposal( origin: OriginFor, community_id: T::CommunityId, + call_origin: Box>, call: Box>, ) -> DispatchResult { let caller = Self::ensure_origin_member(origin, &community_id)?; Self::ensure_active(&community_id)?; - Self::do_create_proposal(&caller, &community_id, *call)?; + Self::do_create_proposal(&caller, &community_id, *call_origin, *call)?; Self::deposit_event(Event::ProposalEnqueued { community_id, @@ -625,9 +633,10 @@ pub mod pallet { /// [`AdminBased`][`CommunityGovernanceStrategy::AdminBased`]. #[pallet::call_index(9)] - pub fn execute_call( + pub fn execute( origin: OriginFor, community_id: T::CommunityId, + call_origin: Box>, call: Box>, ) -> DispatchResult { let Some(caller) = Self::ensure_origin_privileged(origin, &community_id)? else { @@ -635,7 +644,7 @@ pub mod pallet { }; Self::ensure_active(&community_id)?; - Self::do_create_proposal(&caller, &community_id, *call)?; + Self::do_create_proposal(&caller, &community_id, *call_origin, *call)?; Self::deposit_event(Event::ProposalEnqueued { community_id: community_id.clone(), proposer: caller.clone(), diff --git a/pallets/communities/src/tests/governance.rs b/pallets/communities/src/tests/governance.rs index a72d58c6..0f128410 100644 --- a/pallets/communities/src/tests/governance.rs +++ b/pallets/communities/src/tests/governance.rs @@ -21,31 +21,77 @@ mod open_proposal { fn fails_if_not_called_by_a_community_member() { new_test_ext().execute_with(|| { setup(); + let community_account_id = Communities::get_community_account_id(&COMMUNITY); assert_noop!( - Communities::open_proposal(RuntimeOrigin::none(), COMMUNITY, Box::new(call_remark())), + Communities::open_proposal( + RuntimeOrigin::none(), + COMMUNITY, + Box::new(OriginCaller::system(frame_system::RawOrigin::Signed( + community_account_id + ))), + Box::new(call_remark()) + ), DispatchError::BadOrigin ); assert_noop!( - Communities::open_proposal(RuntimeOrigin::root(), COMMUNITY, Box::new(call_remark())), + Communities::open_proposal( + RuntimeOrigin::root(), + COMMUNITY, + Box::new(OriginCaller::system(frame_system::RawOrigin::Signed( + community_account_id + ))), + Box::new(call_remark()) + ), DispatchError::BadOrigin ); }) } + #[test] + fn fails_if_call_origin_is_invalid() { + new_test_ext().execute_with(|| { + setup(); + + assert_noop!( + Communities::open_proposal( + RuntimeOrigin::signed(COMMUNITY_ADMIN), + COMMUNITY, + Box::new(OriginCaller::system(frame_system::RawOrigin::Signed(COMMUNITY_ADMIN))), + Box::new(call_remark()) + ), + Error::InvalidProposalOrigin + ); + }); + } + #[test] fn it_works() { new_test_ext().execute_with(|| { setup(); + let community_account_id = Communities::get_community_account_id(&COMMUNITY); assert_ok!(Communities::open_proposal( RuntimeOrigin::signed(COMMUNITY_ADMIN), COMMUNITY, + Box::new(OriginCaller::Communities(crate::Origin:: { + community_id: COMMUNITY, + body_part: types::BodyPart::Voice + })), Box::new(call_remark()) )); - run_to_block(3); + run_to_block(2); + + assert_ok!(Communities::open_proposal( + RuntimeOrigin::signed(COMMUNITY_ADMIN), + COMMUNITY, + Box::new(OriginCaller::system(frame_system::RawOrigin::Signed( + community_account_id + ))), + Box::new(call_remark()) + )); assert!(System::events().iter().any(|record| { record.event @@ -56,11 +102,7 @@ mod open_proposal { .into() })); - assert!(Communities::proposals(COMMUNITY).iter().len() == 1); - - Communities::proposals(COMMUNITY).iter().for_each(|p| { - println!("{:#?}", &p); - }); + assert!(Communities::proposals(COMMUNITY).iter().len() == 2); }); } } @@ -83,21 +125,39 @@ mod execute_call { fn fails_if_not_called_by_a_community_admin() { new_test_ext().execute_with(|| { setup(); + let community_account_id = Communities::get_community_account_id(&COMMUNITY); assert_noop!( - Communities::execute_call(RuntimeOrigin::none(), COMMUNITY, Box::new(call_remark())), + Communities::execute( + RuntimeOrigin::none(), + COMMUNITY, + Box::new(OriginCaller::system(frame_system::RawOrigin::Signed( + community_account_id + ))), + Box::new(call_remark()) + ), DispatchError::BadOrigin ); assert_noop!( - Communities::execute_call(RuntimeOrigin::root(), COMMUNITY, Box::new(call_remark())), + Communities::execute( + RuntimeOrigin::root(), + COMMUNITY, + Box::new(OriginCaller::system(frame_system::RawOrigin::Signed( + community_account_id + ))), + Box::new(call_remark()) + ), DispatchError::BadOrigin ); assert_noop!( - Communities::execute_call( + Communities::execute( RuntimeOrigin::signed(COMMUNITY_MEMBER_1), COMMUNITY, + Box::new(OriginCaller::system(frame_system::RawOrigin::Signed( + community_account_id + ))), Box::new(call_remark()) ), DispatchError::BadOrigin @@ -109,18 +169,17 @@ mod execute_call { fn it_works() { new_test_ext().execute_with(|| { setup(); + let community_account_id = Communities::get_community_account_id(&COMMUNITY); - assert_ok!(Communities::execute_call( + assert_ok!(Communities::execute( RuntimeOrigin::signed(COMMUNITY_ADMIN), COMMUNITY, + Box::new(OriginCaller::system(frame_system::RawOrigin::Signed( + community_account_id + ))), Box::new(call_remark()) )); - println!("Events for block 1"); - System::events().iter().for_each(|record| { - println!("{:#?}", &record.event); - }); - assert!(System::events().iter().any(|record| { record.event == Event::::ProposalEnqueued { @@ -132,21 +191,7 @@ mod execute_call { run_to_block(2); - println!("Events for block 2"); - System::events().iter().for_each(|record| { - println!("{:#?}", &record.event); - }); - - run_to_block(3); - - println!("Events for block 3"); - System::events().iter().for_each(|record| { - println!("{:#?}", &record.event); - }); - - let community_account_id = Communities::get_community_account_id(&COMMUNITY); assert!(System::events().iter().any(|record| { - println!("{:#?}", &record.event); record.event == frame_system::Event::::Remarked { sender: community_account_id, diff --git a/pallets/communities/src/tests/mock.rs b/pallets/communities/src/tests/mock.rs index 46c8bb49..ac4ccd9c 100644 --- a/pallets/communities/src/tests/mock.rs +++ b/pallets/communities/src/tests/mock.rs @@ -44,25 +44,6 @@ frame_support::construct_runtime!( } ); -impl - Into, pallet_communities::RawOrigin>> - for pallet_communities::RawOrigin -{ - fn into(self) -> Result, pallet_communities::RawOrigin> { - Ok(frame_system::RawOrigin::Signed( - pallet_communities::Pallet::::get_community_account_id(&self.community_id), - )) - } -} - -impl From> for frame_system::RawOrigin { - fn from(o: pallet_communities::RawOrigin) -> frame_system::RawOrigin { - frame_system::RawOrigin::Signed(pallet_communities::Pallet::::get_community_account_id( - &o.community_id, - )) - } -} - impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/pallets/communities/src/types/governance.rs b/pallets/communities/src/types/governance.rs index 7abb031a..1d0be0bd 100644 --- a/pallets/communities/src/types/governance.rs +++ b/pallets/communities/src/types/governance.rs @@ -52,6 +52,7 @@ pub enum CommunityGovernanceStrategy { pub struct CommunityProposal { pub(crate) proposer: AccountIdOf, pub(crate) call: Bounded>, + pub(crate) origin: PalletsOriginOf, } /// This structure holds a poll and the methods to increase/decrease diff --git a/pallets/communities/src/types/origin.rs b/pallets/communities/src/types/origin.rs index 67b17b34..51de6ee1 100644 --- a/pallets/communities/src/types/origin.rs +++ b/pallets/communities/src/types/origin.rs @@ -3,17 +3,19 @@ use super::*; /// The origin of the comnunity governance, as well as the origin /// sent to emit on behalf of the pallet #[derive(TypeInfo, Encode, Decode, MaxEncodedLen, Clone, Eq, PartialEq, Debug)] -#[scale_info(skip_type_params(T))] -pub struct RawOrigin { +pub struct RawOrigin +where + CommunityId: TypeInfo + MaxEncodedLen, + VoteWeight: TypeInfo + MaxEncodedLen, +{ /// The community id. Used to get the account of the /// community for certain origin conversions - pub community_id: CommunityIdOf, + pub community_id: CommunityId, /// - pub body_part: BodyPart>, + pub body_part: BodyPart, } #[derive(TypeInfo, Encode, Decode, MaxEncodedLen, Clone, Eq, PartialEq, Debug)] -#[scale_info(skip_type_params(VotesCount))] pub enum BodyPart { Voice, Members { diff --git a/pallets/communities/src/types/parameters.rs b/pallets/communities/src/types/parameters.rs index 8822732f..6c275a63 100644 --- a/pallets/communities/src/types/parameters.rs +++ b/pallets/communities/src/types/parameters.rs @@ -1,4 +1,5 @@ use super::*; +pub use frame_support::traits::OriginTrait; pub type AssetIdOf = <::Assets as InspectFuns>>::AssetId; pub type BalanceOf = <::Assets as InspectFuns>>::Balance; @@ -9,6 +10,7 @@ pub type AccountIdLookupOf = <::Lookup as StaticLookup>::S pub type CommunityIdOf = ::CommunityId; pub type MemberListOf = Vec>; +pub type PalletsOriginOf = ::PalletsOrigin; pub type RuntimeCallOf = ::RuntimeCall; pub type MembershipPassportOf = ::MembershipPassport; diff --git a/pallets/communities/src/weights.rs b/pallets/communities/src/weights.rs index 08da7741..628454a8 100644 --- a/pallets/communities/src/weights.rs +++ b/pallets/communities/src/weights.rs @@ -41,7 +41,7 @@ pub trait WeightInfo { fn assets_transfer() -> Weight; fn balance_transfer() -> Weight; fn open_proposal() -> Weight; - fn execute_call() -> Weight; + fn execute() -> Weight; } /// Weights for pallet_communities using the Substrate node and recommended hardware. @@ -126,7 +126,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: Communities Something (r:0 w:1) /// Proof: Communities Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - fn execute_call() -> Weight { + fn execute() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` @@ -217,7 +217,7 @@ impl WeightInfo for () { /// Storage: Communities Something (r:0 w:1) /// Proof: Communities Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - fn execute_call() -> Weight { + fn execute() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` From ee0463ff7b16b50c2889c5bd1c5987e8b23c95b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Mon, 16 Oct 2023 17:15:56 -0500 Subject: [PATCH 6/7] fix(pallets/communities): lint --- pallets/communities/src/functions/governance/poll.rs | 2 +- pallets/communities/src/functions/governance/proposals.rs | 2 +- pallets/communities/src/functions/membership.rs | 4 ++-- pallets/communities/src/tests/governance.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pallets/communities/src/functions/governance/poll.rs b/pallets/communities/src/functions/governance/poll.rs index 7f003219..e7b61074 100644 --- a/pallets/communities/src/functions/governance/poll.rs +++ b/pallets/communities/src/functions/governance/poll.rs @@ -97,7 +97,7 @@ impl Pallet { match governance_strategy { CommunityGovernanceStrategy::AdminBased(_) => Ok(PollOutcome::Approved), CommunityGovernanceStrategy::MemberCountPoll { min } => { - if ayes.saturating_add(nays) >= min.into() { + if ayes.saturating_add(nays) >= min { if ayes > nays { Ok(PollOutcome::Approved) } else { diff --git a/pallets/communities/src/functions/governance/proposals.rs b/pallets/communities/src/functions/governance/proposals.rs index 44d48a85..9b7c3b1f 100644 --- a/pallets/communities/src/functions/governance/proposals.rs +++ b/pallets/communities/src/functions/governance/proposals.rs @@ -34,7 +34,7 @@ impl Pallet { proposal.origin, proposal.call, ) - .and_then(|_| Ok(())) + .map(|_| ()) } fn do_enqueue_proposal(community_id: &CommunityIdOf, proposal: CommunityProposal) -> DispatchResult { diff --git a/pallets/communities/src/functions/membership.rs b/pallets/communities/src/functions/membership.rs index b826bc4c..8b058a8b 100644 --- a/pallets/communities/src/functions/membership.rs +++ b/pallets/communities/src/functions/membership.rs @@ -8,8 +8,8 @@ impl Pallet { let caller = ensure_signed(origin)?; Self::membership(community_id, &caller) - .ok_or(DispatchError::BadOrigin.into()) - .and_then(|_| Ok(caller)) + .ok_or(DispatchError::BadOrigin) + .map(|_| caller) } #[allow(dead_code)] diff --git a/pallets/communities/src/tests/governance.rs b/pallets/communities/src/tests/governance.rs index 0f128410..b2b80840 100644 --- a/pallets/communities/src/tests/governance.rs +++ b/pallets/communities/src/tests/governance.rs @@ -195,7 +195,7 @@ mod execute_call { record.event == frame_system::Event::::Remarked { sender: community_account_id, - hash: H256::from(blake2_256(&b"Hello, governance!".to_vec())), + hash: H256::from(blake2_256(b"Hello, governance!".as_ref())), } .into() })); From d901411e32d2637e47762582c8118e3b8adf5d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Tue, 17 Oct 2023 19:20:34 -0500 Subject: [PATCH 7/7] change(communities:functions/origin): use CheckedConversion to replace as_origin! --- pallets/communities/src/functions/origin.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/pallets/communities/src/functions/origin.rs b/pallets/communities/src/functions/origin.rs index 058fbb39..b12b8c90 100644 --- a/pallets/communities/src/functions/origin.rs +++ b/pallets/communities/src/functions/origin.rs @@ -1,10 +1,6 @@ -use super::*; +use sp_runtime::traits::CheckedConversion; -macro_rules! as_origin { - ($origin: ident, $t: ty) => {{ - TryInto::<$t>::try_into($origin.clone()).ok() - }}; -} +use super::*; impl Pallet { pub(crate) fn ensure_proposal_origin( @@ -13,13 +9,13 @@ impl Pallet { ) -> DispatchResult { let community_account_id = Self::get_community_account_id(community_id); - if let Some(o) = as_origin!(origin, frame_system::Origin) { + if let Some(o) = origin.clone().checked_into::>() { match o { frame_system::Origin::::Signed(account) if account == community_account_id => Ok(()), _ => Err(Error::::InvalidProposalOrigin.into()), } } else { - match as_origin!(origin, pallet::Origin) { + match origin.checked_into::>() { Some(_) => Ok(()), None => Err(Error::::InvalidProposalOrigin.into()), }