diff --git a/frame/collective/src/benchmarking.rs b/frame/collective/src/benchmarking.rs index cd4fcfba5fe1e..b4a4485a44fdc 100644 --- a/frame/collective/src/benchmarking.rs +++ b/frame/collective/src/benchmarking.rs @@ -22,7 +22,7 @@ use super::*; use frame_system::RawOrigin as SystemOrigin; use frame_system::EventRecord; use frame_benchmarking::{ - benchmarks_instance, + benchmarks_instance_pallet, account, whitelisted_caller, impl_benchmark_test_suite, @@ -32,13 +32,13 @@ use sp_std::mem::size_of; use frame_system::Call as SystemCall; use frame_system::Pallet as System; -use crate::Module as Collective; +use crate::Pallet as Collective; const SEED: u32 = 0; const MAX_BYTES: u32 = 1_024; -fn assert_last_event, I: Instance>(generic_event: >::Event) { +fn assert_last_event, I: 'static>(generic_event: >::Event) { let events = System::::events(); let system_event: ::Event = generic_event.into(); // compare to the last event record @@ -46,7 +46,7 @@ fn assert_last_event, I: Instance>(generic_event: >: assert_eq!(event, &system_event); } -benchmarks_instance! { +benchmarks_instance_pallet! { set_members { let m in 1 .. T::MaxMembers::get(); let n in 1 .. T::MaxMembers::get(); @@ -138,7 +138,7 @@ benchmarks_instance! { let proposal_hash = T::Hashing::hash_of(&proposal); // Note that execution fails due to mis-matched origin assert_last_event::( - RawEvent::MemberExecuted(proposal_hash, Err(DispatchError::BadOrigin)).into() + Event::MemberExecuted(proposal_hash, Err(DispatchError::BadOrigin)).into() ); } @@ -169,7 +169,7 @@ benchmarks_instance! { let proposal_hash = T::Hashing::hash_of(&proposal); // Note that execution fails due to mis-matched origin assert_last_event::( - RawEvent::Executed(proposal_hash, Err(DispatchError::BadOrigin)).into() + Event::Executed(proposal_hash, Err(DispatchError::BadOrigin)).into() ); } @@ -213,7 +213,7 @@ benchmarks_instance! { // New proposal is recorded assert_eq!(Collective::::proposals().len(), p as usize); let proposal_hash = T::Hashing::hash_of(&proposal); - assert_last_event::(RawEvent::Proposed(caller, p - 1, proposal_hash, threshold).into()); + assert_last_event::(Event::Proposed(caller, p - 1, proposal_hash, threshold).into()); } vote { @@ -369,7 +369,7 @@ benchmarks_instance! { verify { // The last proposal is removed. assert_eq!(Collective::::proposals().len(), (p - 1) as usize); - assert_last_event::(RawEvent::Disapproved(last_hash).into()); + assert_last_event::(Event::Disapproved(last_hash).into()); } close_early_approved { @@ -450,7 +450,7 @@ benchmarks_instance! { verify { // The last proposal is removed. assert_eq!(Collective::::proposals().len(), (p - 1) as usize); - assert_last_event::(RawEvent::Executed(last_hash, Err(DispatchError::BadOrigin)).into()); + assert_last_event::(Event::Executed(last_hash, Err(DispatchError::BadOrigin)).into()); } close_disapproved { @@ -522,7 +522,7 @@ benchmarks_instance! { }: close(SystemOrigin::Signed(caller), last_hash, index, Weight::max_value(), bytes_in_storage) verify { assert_eq!(Collective::::proposals().len(), (p - 1) as usize); - assert_last_event::(RawEvent::Disapproved(last_hash).into()); + assert_last_event::(Event::Disapproved(last_hash).into()); } close_approved { @@ -586,7 +586,7 @@ benchmarks_instance! { }: close(SystemOrigin::Signed(caller), last_hash, p - 1, Weight::max_value(), bytes_in_storage) verify { assert_eq!(Collective::::proposals().len(), (p - 1) as usize); - assert_last_event::(RawEvent::Executed(last_hash, Err(DispatchError::BadOrigin)).into()); + assert_last_event::(Event::Executed(last_hash, Err(DispatchError::BadOrigin)).into()); } disapprove_proposal { @@ -634,7 +634,7 @@ benchmarks_instance! { }: _(SystemOrigin::Root, last_hash) verify { assert_eq!(Collective::::proposals().len(), (p - 1) as usize); - assert_last_event::(RawEvent::Disapproved(last_hash).into()); + assert_last_event::(Event::Disapproved(last_hash).into()); } } diff --git a/frame/collective/src/lib.rs b/frame/collective/src/lib.rs index 5c33bff3006b7..fffb3c0f050d5 100644 --- a/frame/collective/src/lib.rs +++ b/frame/collective/src/lib.rs @@ -49,16 +49,23 @@ use sp_runtime::{RuntimeDebug, traits::Hash}; use frame_support::{ codec::{Decode, Encode}, - decl_error, decl_event, decl_module, decl_storage, + storage::migration::{move_storage_from_pallet}, dispatch::{ - DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable, Parameter, + DispatchError, DispatchResultWithPostInfo, Dispatchable, PostDispatchInfo, }, ensure, - traits::{ChangeMembers, EnsureOrigin, Get, InitializeMembers, GetBacking, Backing}, - weights::{DispatchClass, GetDispatchInfo, Weight, Pays}, + traits::{ + ChangeMembers, EnsureOrigin, + Get, InitializeMembers, PalletInfo, + GetBacking, Backing, + }, + weights::{GetDispatchInfo, Weight}, }; -use frame_system::{self as system, ensure_signed, ensure_root}; +#[cfg(feature = "std")] +use frame_support::traits::GenesisBuild; + +use frame_system::{self as system}; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -66,6 +73,8 @@ mod benchmarking; pub mod weights; pub use weights::WeightInfo; +pub use pallet::*; + /// Simple index type for proposal counting. pub type ProposalIndex = u32; @@ -121,174 +130,79 @@ impl DefaultVote for MoreThanMajorityThenPrimeDefaultVote { } } -pub trait Config: frame_system::Config { - /// The outer origin type. - type Origin: From>; +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use super::*; - /// The outer call dispatch type. - type Proposal: Parameter - + Dispatchable>::Origin, PostInfo=PostDispatchInfo> - + From> - + GetDispatchInfo; + #[pallet::config] + pub trait Config: frame_system::Config { - /// The outer event type. - type Event: From> + Into<::Event>; + /// The outer origin type. + type Origin: From>; - /// The time-out for council motions. - type MotionDuration: Get; + /// The outer call dispatch type. + type Proposal: Parameter + + Dispatchable>::Origin, PostInfo=PostDispatchInfo> + + From> + + GetDispatchInfo; - /// Maximum number of proposals allowed to be active in parallel. - type MaxProposals: Get; + /// The overarching event type. + type Event: From> + IsType<::Event>; - /// The maximum number of members supported by the pallet. Used for weight estimation. - /// - /// NOTE: - /// + Benchmarks will need to be re-run and weights adjusted if this changes. - /// + This pallet assumes that dependents keep to the limit without enforcing it. - type MaxMembers: Get; + /// The time-out for council motions. + #[pallet::constant] + type MotionDuration: Get; - /// Default vote strategy of this collective. - type DefaultVote: DefaultVote; + /// Maximum number of proposals allowed to be active in parallel. + #[pallet::constant] + type MaxProposals: Get; - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; -} + /// The maximum number of members supported by the pallet. Used for weight estimation. + /// + /// NOTE: + /// + Benchmarks will need to be re-run and weights adjusted if this changes. + /// + This pallet assumes that dependents keep to the limit without enforcing it. + #[pallet::constant] + type MaxMembers: Get; -/// Origin for the collective module. -#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode)] -pub enum RawOrigin { - /// It has been condoned by a given number of members of the collective from a given total. - Members(MemberCount, MemberCount), - /// It has been condoned by a single member of the collective. - Member(AccountId), - /// Dummy to manage the fact we have instancing. - _Phantom(sp_std::marker::PhantomData), -} + /// Default vote strategy of this collective. + type DefaultVote: DefaultVote; -impl GetBacking for RawOrigin { - fn get_backing(&self) -> Option { - match self { - RawOrigin::Members(n, d) => Some(Backing { approvals: *n, eligible: *d }), - _ => None, - } + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; } -} -/// Origin for the collective module. -pub type Origin = RawOrigin<::AccountId, I>; + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData<(T, I)>); -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] -/// Info for keeping track of a motion being voted on. -pub struct Votes { - /// The proposal's unique index. - index: ProposalIndex, - /// The number of approval votes that are needed to pass the motion. - threshold: MemberCount, - /// The current set of voters that approved it. - ayes: Vec, - /// The current set of voters that rejected it. - nays: Vec, - /// The hard end time of this vote. - end: BlockNumber, -} + /// Origin for the collective module. + #[pallet::origin] + pub type Origin = RawOrigin<::AccountId, I>; -decl_storage! { - trait Store for Module, I: Instance=DefaultInstance> as Collective { - /// The hashes of the active proposals. - pub Proposals get(fn proposals): Vec; - /// Actual proposal for a given hash, if it's current. - pub ProposalOf get(fn proposal_of): - map hasher(identity) T::Hash => Option<>::Proposal>; - /// Votes on a given proposal, if it is ongoing. - pub Voting get(fn voting): - map hasher(identity) T::Hash => Option>; - /// Proposals so far. - pub ProposalCount get(fn proposal_count): u32; - /// The current members of the collective. This is stored sorted (just by value). - pub Members get(fn members): Vec; - /// The prime member that helps determine the default vote behavior in case of absentations. - pub Prime get(fn prime): Option; - } - add_extra_genesis { - config(phantom): sp_std::marker::PhantomData; - config(members): Vec; - build(|config| Module::::initialize_members(&config.members)) - } -} - -decl_event! { - pub enum Event where - ::Hash, - ::AccountId, - { - /// A motion (given hash) has been proposed (by given account) with a threshold (given - /// `MemberCount`). - /// \[account, proposal_index, proposal_hash, threshold\] - Proposed(AccountId, ProposalIndex, Hash, MemberCount), - /// A motion (given hash) has been voted on by given account, leaving - /// a tally (yes votes and no votes given respectively as `MemberCount`). - /// \[account, proposal_hash, voted, yes, no\] - Voted(AccountId, Hash, bool, MemberCount, MemberCount), - /// A motion was approved by the required threshold. - /// \[proposal_hash\] - Approved(Hash), - /// A motion was not approved by the required threshold. - /// \[proposal_hash\] - Disapproved(Hash), - /// A motion was executed; result will be `Ok` if it returned without error. - /// \[proposal_hash, result\] - Executed(Hash, DispatchResult), - /// A single member did some action; result will be `Ok` if it returned without error. - /// \[proposal_hash, result\] - MemberExecuted(Hash, DispatchResult), - /// A proposal was closed because its threshold was reached or after its duration was up. - /// \[proposal_hash, yes, no\] - Closed(Hash, MemberCount, MemberCount), - } -} - -decl_error! { - pub enum Error for Module, I: Instance> { - /// Account is not a member - NotMember, - /// Duplicate proposals not allowed - DuplicateProposal, - /// Proposal must exist - ProposalMissing, - /// Mismatched index - WrongIndex, - /// Duplicate vote ignored - DuplicateVote, - /// Members are already initialized! - AlreadyInitialized, - /// The close call was made too early, before the end of the voting. - TooEarly, - /// There can only be a maximum of `MaxProposals` active proposals. - TooManyProposals, - /// The given weight bound for the proposal was too low. - WrongProposalWeight, - /// The given length bound for the proposal was too low. - WrongProposalLength, - } -} + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + fn on_runtime_upgrade() -> frame_support::weights::Weight { + if !Self::upgraded_pallet_prefix() { + UpgradedPalletPrefix::::put(true); + migrations::migrate_to_new_pallet_prefix::() + } else { + 0 + } + } -/// Return the weight of a dispatch call result as an `Option`. -/// -/// Will return the weight regardless of what the state of the result is. -fn get_result_weight(result: DispatchResultWithPostInfo) -> Option { - match result { - Ok(post_info) => post_info.actual_weight, - Err(err) => err.post_info.actual_weight, + // It is an extra not really part of the PR, + fn integrity_test() { + T::BlockWeights::get() + .validate() + .expect("The weights are invalid."); + } } -} - - -// Note that councillor operations are assigned to the operational class. -decl_module! { - pub struct Module, I: Instance=DefaultInstance> for enum Call where origin: ::Origin { - type Error = Error; - fn deposit_event() = default; + #[pallet::call] + impl, I: 'static> Pallet { /// Set the collective's membership. /// @@ -301,28 +215,16 @@ decl_module! { /// /// NOTE: Does not enforce the expected `MaxMembers` limit on the amount of members, but /// the weight estimations rely on it to estimate dispatchable weight. - /// - /// # - /// ## Weight - /// - `O(MP + N)` where: - /// - `M` old-members-count (code- and governance-bounded) - /// - `N` new-members-count (code- and governance-bounded) - /// - `P` proposals-count (code-bounded) - /// - DB: - /// - 1 storage mutation (codec `O(M)` read, `O(N)` write) for reading and writing the members - /// - 1 storage read (codec `O(P)`) for reading the proposals - /// - `P` storage mutations (codec `O(M)`) for updating the votes for each proposal - /// - 1 storage write (codec `O(1)`) for deleting the old `prime` and setting the new one - /// # - #[weight = ( + #[pallet::weight(( T::WeightInfo::set_members( *old_count, // M new_members.len() as u32, // N T::MaxProposals::get() // P ), DispatchClass::Operational - )] - fn set_members(origin, + ))] + pub fn set_members( + origin: OriginFor, new_members: Vec, prime: Option, old_count: MemberCount, @@ -361,34 +263,28 @@ decl_module! { /// Dispatch a proposal from a member using the `Member` origin. /// /// Origin must be a member of the collective. - /// - /// # - /// ## Weight - /// - `O(M + P)` where `M` members-count (code-bounded) and `P` complexity of dispatching `proposal` - /// - DB: 1 read (codec `O(M)`) + DB access of `proposal` - /// - 1 event - /// # - #[weight = ( + #[pallet::weight(( T::WeightInfo::execute( *length_bound, // B T::MaxMembers::get(), // M ).saturating_add(proposal.get_dispatch_info().weight), // P - DispatchClass::Operational - )] - fn execute(origin, + DispatchClass::Operational, + ))] + pub fn execute( + origin: OriginFor, proposal: Box<>::Proposal>, - #[compact] length_bound: u32, + #[pallet::compact] length_bound: u32, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let members = Self::members(); - ensure!(members.contains(&who), Error::::NotMember); + ensure!(members.contains(&who), >::NotMember); let proposal_len = proposal.using_encoded(|x| x.len()); - ensure!(proposal_len <= length_bound as usize, Error::::WrongProposalLength); + ensure!(proposal_len <= length_bound as usize, >::WrongProposalLength); let proposal_hash = T::Hashing::hash_of(&proposal); let result = proposal.dispatch(RawOrigin::Member(who).into()); Self::deposit_event( - RawEvent::MemberExecuted(proposal_hash, result.map(|_| ()).map_err(|e| e.error)) + Event::MemberExecuted(proposal_hash, result.map(|_| ()).map_err(|e| e.error)) ); Ok(get_result_weight(result).map(|w| { @@ -406,27 +302,7 @@ decl_module! { /// `threshold` determines whether `proposal` is executed directly (`threshold < 2`) /// or put up for voting. /// - /// # - /// ## Weight - /// - `O(B + M + P1)` or `O(B + M + P2)` where: - /// - `B` is `proposal` size in bytes (length-fee-bounded) - /// - `M` is members-count (code- and governance-bounded) - /// - branching is influenced by `threshold` where: - /// - `P1` is proposal execution complexity (`threshold < 2`) - /// - `P2` is proposals-count (code-bounded) (`threshold >= 2`) - /// - DB: - /// - 1 storage read `is_member` (codec `O(M)`) - /// - 1 storage read `ProposalOf::contains_key` (codec `O(1)`) - /// - DB accesses influenced by `threshold`: - /// - EITHER storage accesses done by `proposal` (`threshold < 2`) - /// - OR proposal insertion (`threshold <= 2`) - /// - 1 storage mutation `Proposals` (codec `O(P2)`) - /// - 1 storage mutation `ProposalCount` (codec `O(1)`) - /// - 1 storage write `ProposalOf` (codec `O(B)`) - /// - 1 storage write `Voting` (codec `O(M)`) - /// - 1 event - /// # - #[weight = ( + #[pallet::weight(( if *threshold < 2 { T::WeightInfo::propose_execute( *length_bound, // B @@ -440,26 +316,28 @@ decl_module! { ) }, DispatchClass::Operational - )] - fn propose(origin, - #[compact] threshold: MemberCount, - proposal: Box<>::Proposal>, - #[compact] length_bound: u32 + ))] + pub fn propose( + origin: OriginFor, + #[pallet::compact] threshold: MemberCount, + proposal: Box, + #[pallet::compact] length_bound: u32 ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let members = Self::members(); - ensure!(members.contains(&who), Error::::NotMember); + ensure!(members.contains(&who), >::NotMember); let proposal_len = proposal.using_encoded(|x| x.len()); - ensure!(proposal_len <= length_bound as usize, Error::::WrongProposalLength); + ensure!(proposal_len <= length_bound as usize, >::WrongProposalLength); let proposal_hash = T::Hashing::hash_of(&proposal); - ensure!(!>::contains_key(proposal_hash), Error::::DuplicateProposal); + + ensure!(!>::contains_key(proposal_hash),>::DuplicateProposal); if threshold < 2 { let seats = Self::members().len() as MemberCount; let result = proposal.dispatch(RawOrigin::Members(1, seats).into()); Self::deposit_event( - RawEvent::Executed(proposal_hash, result.map(|_| ()).map_err(|e| e.error)) + Event::Executed(proposal_hash, result.map(|_| ()).map_err(|e| e.error)) ); Ok(get_result_weight(result).map(|w| { @@ -472,20 +350,18 @@ decl_module! { let active_proposals = >::try_mutate(|proposals| -> Result { proposals.push(proposal_hash); - ensure!( - proposals.len() <= T::MaxProposals::get() as usize, - Error::::TooManyProposals - ); + ensure!(proposals.len() <= T::MaxProposals::get() as usize, >::TooManyProposals); Ok(proposals.len()) })?; let index = Self::proposal_count(); - >::mutate(|i| *i += 1); + + >::mutate(|i| *i += 1); >::insert(proposal_hash, *proposal); let end = system::Pallet::::block_number() + T::MotionDuration::get(); let votes = Votes { index, threshold, ayes: vec![who.clone()], nays: vec![], end }; >::insert(proposal_hash, votes); - Self::deposit_event(RawEvent::Proposed(who, index, proposal_hash, threshold)); + Self::deposit_event(Event::Proposed(who, index, proposal_hash, threshold)); Ok(Some(T::WeightInfo::propose_proposed( proposal_len as u32, // B @@ -501,29 +377,22 @@ decl_module! { /// /// Transaction fees will be waived if the member is voting on any particular proposal /// for the first time and the call is successful. Subsequent vote changes will charge a fee. - /// # - /// ## Weight - /// - `O(M)` where `M` is members-count (code- and governance-bounded) - /// - DB: - /// - 1 storage read `Members` (codec `O(M)`) - /// - 1 storage mutation `Voting` (codec `O(M)`) - /// - 1 event - /// # - #[weight = ( + #[pallet::weight(( T::WeightInfo::vote(T::MaxMembers::get()), DispatchClass::Operational - )] - fn vote(origin, + ))] + pub fn vote( + origin: OriginFor, proposal: T::Hash, - #[compact] index: ProposalIndex, + #[pallet::compact] index: ProposalIndex, approve: bool, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let members = Self::members(); - ensure!(members.contains(&who), Error::::NotMember); + ensure!(members.contains(&who), >::NotMember); - let mut voting = Self::voting(&proposal).ok_or(Error::::ProposalMissing)?; - ensure!(voting.index == index, Error::::WrongIndex); + let mut voting = Self::voting(&proposal).ok_or(>::ProposalMissing)?; + ensure!(voting.index == index, >::WrongIndex); let position_yes = voting.ayes.iter().position(|a| a == &who); let position_no = voting.nays.iter().position(|a| a == &who); @@ -535,7 +404,7 @@ decl_module! { if position_yes.is_none() { voting.ayes.push(who.clone()); } else { - Err(Error::::DuplicateVote)? + Err(>::DuplicateVote)? } if let Some(pos) = position_no { voting.nays.swap_remove(pos); @@ -544,7 +413,7 @@ decl_module! { if position_no.is_none() { voting.nays.push(who.clone()); } else { - Err(Error::::DuplicateVote)? + Err(>::DuplicateVote)? } if let Some(pos) = position_yes { voting.ayes.swap_remove(pos); @@ -553,9 +422,9 @@ decl_module! { let yes_votes = voting.ayes.len() as MemberCount; let no_votes = voting.nays.len() as MemberCount; - Self::deposit_event(RawEvent::Voted(who, proposal, approve, yes_votes, no_votes)); + Self::deposit_event(Event::Voted(who, proposal, approve, yes_votes, no_votes)); - Voting::::insert(&proposal, voting); + >::insert(&proposal, voting); if is_account_voting_first_time { Ok(( @@ -587,20 +456,7 @@ decl_module! { /// + `length_bound`: The upper bound for the length of the proposal in storage. Checked via /// `storage::read` so it is `size_of::() == 4` larger than the pure length. /// - /// # - /// ## Weight - /// - `O(B + M + P1 + P2)` where: - /// - `B` is `proposal` size in bytes (length-fee-bounded) - /// - `M` is members-count (code- and governance-bounded) - /// - `P1` is the complexity of `proposal` preimage. - /// - `P2` is proposal-count (code-bounded) - /// - DB: - /// - 2 storage reads (`Members`: codec `O(M)`, `Prime`: codec `O(1)`) - /// - 3 mutations (`Voting`: codec `O(M)`, `ProposalOf`: codec `O(B)`, `Proposals`: codec `O(P2)`) - /// - any mutations done while executing `proposal` (`P1`) - /// - up to 3 events - /// # - #[weight = ( + #[pallet::weight(( { let b = *length_bound; let m = T::MaxMembers::get(); @@ -613,17 +469,18 @@ decl_module! { .saturating_add(p1) }, DispatchClass::Operational - )] - fn close(origin, + ))] + pub fn close( + origin: OriginFor, proposal_hash: T::Hash, - #[compact] index: ProposalIndex, - #[compact] proposal_weight_bound: Weight, - #[compact] length_bound: u32 + #[pallet::compact] index: ProposalIndex, + #[pallet::compact] proposal_weight_bound: Weight, + #[pallet::compact] length_bound: u32 ) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; - let voting = Self::voting(&proposal_hash).ok_or(Error::::ProposalMissing)?; - ensure!(voting.index == index, Error::::WrongIndex); + let voting = Self::voting(&proposal_hash).ok_or(>::ProposalMissing)?; + ensure!(voting.index == index, >::WrongIndex); let mut no_votes = voting.nays.len() as MemberCount; let mut yes_votes = voting.ayes.len() as MemberCount; @@ -637,7 +494,7 @@ decl_module! { length_bound, proposal_weight_bound, )?; - Self::deposit_event(RawEvent::Closed(proposal_hash, yes_votes, no_votes)); + Self::deposit_event(Event::Closed(proposal_hash, yes_votes, no_votes)); let (proposal_weight, proposal_count) = Self::do_approve_proposal(seats, voting, proposal_hash, proposal); return Ok(( @@ -647,7 +504,7 @@ decl_module! { ).into()); } else if disapproved { - Self::deposit_event(RawEvent::Closed(proposal_hash, yes_votes, no_votes)); + Self::deposit_event(Event::Closed(proposal_hash, yes_votes, no_votes)); let proposal_count = Self::do_disapprove_proposal(proposal_hash); return Ok(( Some(T::WeightInfo::close_early_disapproved(seats, proposal_count)), @@ -676,7 +533,7 @@ decl_module! { length_bound, proposal_weight_bound, )?; - Self::deposit_event(RawEvent::Closed(proposal_hash, yes_votes, no_votes)); + Self::deposit_event(Event::Closed(proposal_hash, yes_votes, no_votes)); let (proposal_weight, proposal_count) = Self::do_approve_proposal(seats, voting, proposal_hash, proposal); return Ok(( @@ -685,7 +542,7 @@ decl_module! { Pays::Yes, ).into()); } else { - Self::deposit_event(RawEvent::Closed(proposal_hash, yes_votes, no_votes)); + Self::deposit_event(Event::Closed(proposal_hash, yes_votes, no_votes)); let proposal_count = Self::do_disapprove_proposal(proposal_hash); return Ok(( Some(T::WeightInfo::close_disapproved(seats, proposal_count)), @@ -694,29 +551,235 @@ decl_module! { } } - /// Disapprove a proposal, close, and remove it from the system, regardless of its current state. + /// Disapprove a proposal, close, and remove it from the system, + /// regardless of its current state. /// /// Must be called by the Root origin. /// /// Parameters: /// * `proposal_hash`: The hash of the proposal that should be disapproved. - /// - /// # - /// Complexity: O(P) where P is the number of max proposals - /// DB Weight: - /// * Reads: Proposals - /// * Writes: Voting, Proposals, ProposalOf - /// # - #[weight = T::WeightInfo::disapprove_proposal(T::MaxProposals::get())] - fn disapprove_proposal(origin, proposal_hash: T::Hash) -> DispatchResultWithPostInfo { + #[pallet::weight( + T::WeightInfo::disapprove_proposal(T::MaxProposals::get()) + )] + pub fn disapprove_proposal( + origin: OriginFor, + proposal_hash: T::Hash + ) -> DispatchResultWithPostInfo { ensure_root(origin)?; let proposal_count = Self::do_disapprove_proposal(proposal_hash); Ok(Some(T::WeightInfo::disapprove_proposal(proposal_count)).into()) } } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + #[pallet::metadata(T::AccountId = "AccountId", T::Hash = "Hash")] + pub enum Event, I: 'static = ()> { + /// A motion (given hash) has been proposed (by given account) with a threshold (given + /// `MemberCount`). + /// \[account, proposal_index, proposal_hash, threshold\] + Proposed(T::AccountId, ProposalIndex, T::Hash, MemberCount), + /// A motion (given hash) has been voted on by given account, leaving + /// a tally (yes votes and no votes given respectively as `MemberCount`). + /// \[account, proposal_hash, voted, yes, no\] + Voted(T::AccountId, T::Hash, bool, MemberCount, MemberCount), + /// A motion was approved by the required threshold. + /// \[proposal_hash\] + Approved(T::Hash), + /// A motion was not approved by the required threshold. + /// \[proposal_hash\] + Disapproved(T::Hash), + /// A motion was executed; result will be `Ok` if it returned without error. + /// \[proposal_hash, result\] + Executed(T::Hash, DispatchResult), + /// A single member did some action; result will be `Ok` if it returned without error. + /// \[proposal_hash, result\] + MemberExecuted(T::Hash, DispatchResult), + /// A proposal was closed because its threshold was reached or after its duration was up. + /// \[proposal_hash, yes, no\] + Closed(T::Hash, MemberCount, MemberCount), + } + + /// Old name generated by `decl_event`. + #[deprecated(note = "use `Event` instead")] + pub type RawEvent = Event; + + #[pallet::error] + pub enum Error { + /// Account is not a member + NotMember, + /// Duplicate proposals not allowed + DuplicateProposal, + /// Proposal must exist + ProposalMissing, + /// Mismatched index + WrongIndex, + /// Duplicate vote ignored + DuplicateVote, + /// Members are already initialized! + AlreadyInitialized, + /// The close call was made too early, before the end of the voting. + TooEarly, + /// There can only be a maximum of `MaxProposals` active proposals. + TooManyProposals, + /// The given weight bound for the proposal was too low. + WrongProposalWeight, + /// The given length bound for the proposal was too low. + WrongProposalLength, + } + + /// The hashes of the active proposals. + #[pallet::storage] + #[pallet::getter(fn proposals)] + pub type Proposals, I: 'static = ()> = StorageValue<_, Vec, ValueQuery>; + + /// Actual proposal for a given hash, if it's current. + #[pallet::storage] + #[pallet::getter(fn proposal_of)] + pub type ProposalOf, I: 'static = ()> = StorageMap< + _, + Identity, + T::Hash, + T::Proposal, + OptionQuery, + >; + + /// Votes on a given proposal, if it is ongoing. + #[pallet::storage] + #[pallet::getter(fn voting)] + pub type Voting, I: 'static = ()> = StorageMap< + _, + Identity, + T::Hash, + Votes, + OptionQuery + >; + + /// Proposals so far. + #[pallet::storage] + #[pallet::getter(fn proposal_count)] + pub type ProposalCount, I: 'static = ()> = StorageValue<_, u32, ValueQuery>; + + /// The current members of the collective. This is stored sorted (just by value). + #[pallet::storage] + #[pallet::getter(fn members)] + pub type Members, I: 'static = ()> = StorageValue<_, Vec, ValueQuery>; + + /// The prime member that helps determine the default vote behavior in case of absentations. + #[pallet::storage] + #[pallet::getter(fn prime)] + pub type Prime, I: 'static = ()> = StorageValue<_, T::AccountId, OptionQuery>; + + /// (default) if not. + #[pallet::storage] + #[pallet::getter(fn upgraded_pallet_prefix)] + pub(super) type UpgradedPalletPrefix, I: 'static = ()> = + StorageValue<_, bool, ValueQuery>; + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()>{ + pub members: Vec, + pub phantom: sp_std::marker::PhantomData<(T, I)>, + } + + #[cfg(feature = "std")] + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + Self { + members: Default::default(), + phantom: Default::default(), + } + } + } + + #[pallet::genesis_build] + impl, I: 'static> GenesisBuild for GenesisConfig { + fn build(&self) { + >::put(true); + Pallet::::initialize_members(&self.members); + } + } +} + +mod migrations { + use super::*; + + pub fn migrate_to_new_pallet_prefix, I: 'static>() -> frame_support::weights::Weight { + + let new_name = T::PalletInfo::name::>() + .expect("Fatal Error Invalid PalletInfo name") + .as_bytes(); + + + move_storage_from_pallet(b"Proposals", b"Collective", new_name); + move_storage_from_pallet(b"ProposalOf", b"Collective", new_name); + move_storage_from_pallet(b"Voting", b"Collective", new_name); + move_storage_from_pallet(b"ProposalCount", b"Collective", new_name); + move_storage_from_pallet(b"Members", b"Collective", new_name); + move_storage_from_pallet(b"Prime", b"Collective", new_name); + + T::BlockWeights::get().max_block + } } -impl, I: Instance> Module { +#[cfg(feature = "std")] +impl, I: 'static> GenesisConfig { + /// Direct implementation of `GenesisBuild::build_storage`. + /// + /// Kept in order not to break dependency. + #[deprecated(note = "use [`GenesisBuild::build_storage`] instead")] + pub fn build_storage(&self) -> Result { + >::build_storage(self) + } + + /// Direct implementation of `GenesisBuild::assimilate_storage`. + /// + /// Kept in order not to break dependency. + #[deprecated(note = "use [`GenesisBuild::assimilate_storage`] instead")] + pub fn assimilate_storage( + &self, + storage: &mut sp_runtime::Storage + ) -> Result<(), String> { + >::assimilate_storage(self, storage) + } +} + +/// Origin for the collective module. +#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode)] +pub enum RawOrigin { + /// It has been condoned by a given number of members of the collective from a given total. + Members(MemberCount, MemberCount), + /// It has been condoned by a single member of the collective. + Member(AccountId), + /// Dummy to manage the fact we have instancing. + _Phantom(sp_std::marker::PhantomData), +} + +impl GetBacking for RawOrigin { + fn get_backing(&self) -> Option { + match self { + RawOrigin::Members(n, d) => Some(Backing { approvals: *n, eligible: *d }), + _ => None, + } + } +} + +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] +/// Info for keeping track of a motion being voted on. +pub struct Votes { + /// The proposal's unique index. + index: ProposalIndex, + /// The number of approval votes that are needed to pass the motion. + threshold: MemberCount, + /// The current set of voters that approved it. + ayes: Vec, + /// The current set of voters that rejected it. + nays: Vec, + /// The hard end time of this vote. + end: BlockNumber, +} + +impl, I: 'static> Pallet { /// Check whether `who` is a member of the collective. pub fn is_member(who: &T::AccountId) -> bool { // Note: The dispatchables *do not* use this to check membership so make sure @@ -737,40 +800,29 @@ impl, I: Instance> Module { // read the length of the proposal storage entry directly let proposal_len = storage::read(&key, &mut [0; 0], 0) .ok_or(Error::::ProposalMissing)?; + ensure!(proposal_len <= length_bound, Error::::WrongProposalLength); + let proposal = ProposalOf::::get(hash).ok_or(Error::::ProposalMissing)?; let proposal_weight = proposal.get_dispatch_info().weight; ensure!(proposal_weight <= weight_bound, Error::::WrongProposalWeight); Ok((proposal, proposal_len as usize)) } - /// Weight: - /// If `approved`: - /// - the weight of `proposal` preimage. - /// - two events deposited. - /// - two removals, one mutation. - /// - computation and i/o `O(P + L)` where: - /// - `P` is number of active proposals, - /// - `L` is the encoded length of `proposal` preimage. - /// - /// If not `approved`: - /// - one event deposited. - /// Two removals, one mutation. - /// Computation and i/o `O(P)` where: - /// - `P` is number of active proposals + // Approve proposal fn do_approve_proposal( seats: MemberCount, voting: Votes, proposal_hash: T::Hash, proposal: >::Proposal, ) -> (Weight, u32) { - Self::deposit_event(RawEvent::Approved(proposal_hash)); + Self::deposit_event(Event::Approved(proposal_hash)); let dispatch_weight = proposal.get_dispatch_info().weight; let origin = RawOrigin::Members(voting.threshold, seats).into(); let result = proposal.dispatch(origin); Self::deposit_event( - RawEvent::Executed(proposal_hash, result.map(|_| ()).map_err(|e| e.error)) + Event::Executed(proposal_hash, result.map(|_| ()).map_err(|e| e.error)) ); // default to the dispatch info weight for safety let proposal_weight = get_result_weight(result).unwrap_or(dispatch_weight); // P1 @@ -779,9 +831,10 @@ impl, I: Instance> Module { (proposal_weight, proposal_count) } + // disapprove proposal from the pallet fn do_disapprove_proposal(proposal_hash: T::Hash) -> u32 { // disapproved - Self::deposit_event(RawEvent::Disapproved(proposal_hash)); + Self::deposit_event(Event::Disapproved(proposal_hash)); Self::remove_proposal(proposal_hash) } @@ -798,24 +851,11 @@ impl, I: Instance> Module { } } -impl, I: Instance> ChangeMembers for Module { +impl, I: 'static> ChangeMembers for Pallet { /// Update the members of the collective. Votes are updated and the prime is reset. /// /// NOTE: Does not enforce the expected `MaxMembers` limit on the amount of members, but /// the weight estimations rely on it to estimate dispatchable weight. - /// - /// # - /// ## Weight - /// - `O(MP + N)` - /// - where `M` old-members-count (governance-bounded) - /// - where `N` new-members-count (governance-bounded) - /// - where `P` proposals-count - /// - DB: - /// - 1 storage read (codec `O(P)`) for reading the proposals - /// - `P` storage mutations for updating the votes (codec `O(M)`) - /// - 1 storage write (codec `O(N)`) for storing the new members - /// - 1 storage write (codec `O(1)`) for deleting the old prime - /// # fn change_members_sorted( _incoming: &[T::AccountId], outgoing: &[T::AccountId], @@ -858,7 +898,17 @@ impl, I: Instance> ChangeMembers for Module { } } -impl, I: Instance> InitializeMembers for Module { +/// Return the weight of a dispatch call result as an `Option`. +/// +/// Will return the weight regardless of what the state of the result is. +fn get_result_weight(result: DispatchResultWithPostInfo) -> Option { + match result { + Ok(post_info) => post_info.actual_weight, + Err(err) => err.post_info.actual_weight, + } +} + +impl, I: 'static> InitializeMembers for Pallet { fn initialize_members(members: &[T::AccountId]) { if !members.is_empty() { assert!(>::get().is_empty(), "Members are already initialized!"); @@ -880,7 +930,7 @@ where } } -pub struct EnsureMember(sp_std::marker::PhantomData<(AccountId, I)>); +pub struct EnsureMember(sp_std::marker::PhantomData<(AccountId, I)>); impl< O: Into, O>> + From>, AccountId: Default, @@ -900,7 +950,7 @@ impl< } } -pub struct EnsureMembers(sp_std::marker::PhantomData<(N, AccountId, I)>); +pub struct EnsureMembers(sp_std::marker::PhantomData<(N, AccountId, I)>); impl< O: Into, O>> + From>, N: U32, @@ -921,7 +971,7 @@ impl< } } -pub struct EnsureProportionMoreThan( +pub struct EnsureProportionMoreThan( sp_std::marker::PhantomData<(N, D, AccountId, I)> ); impl< @@ -945,7 +995,7 @@ impl< } } -pub struct EnsureProportionAtLeast( +pub struct EnsureProportionAtLeast( sp_std::marker::PhantomData<(N, D, AccountId, I)> ); impl< @@ -969,15 +1019,23 @@ impl< } } + #[cfg(test)] mod tests { use super::*; - use frame_support::{Hashable, assert_ok, assert_noop, parameter_types}; + use frame_support::{ + Hashable, assert_ok, assert_noop, parameter_types, + Identity, + weights::{Pays}, + pallet_prelude::{StorageValue, StorageMap}, + traits::{OnRuntimeUpgrade}, + }; use frame_system::{self as system, EventRecord, Phase}; use hex_literal::hex; use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + testing::Header, BuildStorage, }; use crate as collective; @@ -1112,10 +1170,10 @@ mod tests { let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] }; assert_eq!(System::events(), vec![ - record(Event::collective_Instance1(RawEvent::Proposed(1, 0, hash.clone(), 3))), - record(Event::collective_Instance1(RawEvent::Voted(2, hash.clone(), true, 2, 0))), - record(Event::collective_Instance1(RawEvent::Closed(hash.clone(), 2, 1))), - record(Event::collective_Instance1(RawEvent::Disapproved(hash.clone()))) + record(Event::collective_Instance1(collective::Event::Proposed(1, 0, hash.clone(), 3))), + record(Event::collective_Instance1(collective::Event::Voted(2, hash.clone(), true, 2, 0))), + record(Event::collective_Instance1(collective::Event::Closed(hash.clone(), 2, 1))), + record(Event::collective_Instance1(collective::Event::Disapproved(hash.clone()))) ]); }); } @@ -1174,10 +1232,10 @@ mod tests { let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] }; assert_eq!(System::events(), vec![ - record(Event::collective_Instance1(RawEvent::Proposed(1, 0, hash.clone(), 3))), - record(Event::collective_Instance1(RawEvent::Voted(2, hash.clone(), true, 2, 0))), - record(Event::collective_Instance1(RawEvent::Closed(hash.clone(), 2, 1))), - record(Event::collective_Instance1(RawEvent::Disapproved(hash.clone()))) + record(Event::collective_Instance1(collective::Event::Proposed(1, 0, hash.clone(), 3))), + record(Event::collective_Instance1(collective::Event::Voted(2, hash.clone(), true, 2, 0))), + record(Event::collective_Instance1(collective::Event::Closed(hash.clone(), 2, 1))), + record(Event::collective_Instance1(collective::Event::Disapproved(hash.clone()))) ]); }); } @@ -1199,11 +1257,11 @@ mod tests { let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] }; assert_eq!(System::events(), vec![ - record(Event::collective_Instance1(RawEvent::Proposed(1, 0, hash.clone(), 3))), - record(Event::collective_Instance1(RawEvent::Voted(2, hash.clone(), true, 2, 0))), - record(Event::collective_Instance1(RawEvent::Closed(hash.clone(), 3, 0))), - record(Event::collective_Instance1(RawEvent::Approved(hash.clone()))), - record(Event::collective_Instance1(RawEvent::Executed(hash.clone(), Err(DispatchError::BadOrigin)))) + record(Event::collective_Instance1(collective::Event::Proposed(1, 0, hash.clone(), 3))), + record(Event::collective_Instance1(collective::Event::Voted(2, hash.clone(), true, 2, 0))), + record(Event::collective_Instance1(collective::Event::Closed(hash.clone(), 3, 0))), + record(Event::collective_Instance1(collective::Event::Approved(hash.clone()))), + record(Event::collective_Instance1(collective::Event::Executed(hash.clone(), Err(DispatchError::BadOrigin)))) ]); }); } @@ -1226,12 +1284,12 @@ mod tests { let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] }; assert_eq!(System::events(), vec![ - record(Event::collective_Instance2(RawEvent::Proposed(1, 0, hash.clone(), 5))), - record(Event::collective_Instance2(RawEvent::Voted(2, hash.clone(), true, 2, 0))), - record(Event::collective_Instance2(RawEvent::Voted(3, hash.clone(), true, 3, 0))), - record(Event::collective_Instance2(RawEvent::Closed(hash.clone(), 5, 0))), - record(Event::collective_Instance2(RawEvent::Approved(hash.clone()))), - record(Event::collective_Instance2(RawEvent::Executed(hash.clone(), Err(DispatchError::BadOrigin)))) + record(Event::collective_Instance2(collective::Event::Proposed(1, 0, hash.clone(), 5))), + record(Event::collective_Instance2(collective::Event::Voted(2, hash.clone(), true, 2, 0))), + record(Event::collective_Instance2(collective::Event::Voted(3, hash.clone(), true, 3, 0))), + record(Event::collective_Instance2(collective::Event::Closed(hash.clone(), 5, 0))), + record(Event::collective_Instance2(collective::Event::Approved(hash.clone()))), + record(Event::collective_Instance2(collective::Event::Executed(hash.clone(), Err(DispatchError::BadOrigin)))) ]); }); } @@ -1326,7 +1384,7 @@ mod tests { assert_eq!(System::events(), vec![ EventRecord { phase: Phase::Initialization, - event: Event::collective_Instance1(RawEvent::Proposed( + event: Event::collective_Instance1(collective::Event::Proposed( 1, 0, hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), @@ -1454,7 +1512,7 @@ mod tests { assert_eq!(System::events(), vec![ EventRecord { phase: Phase::Initialization, - event: Event::collective_Instance1(RawEvent::Proposed( + event: Event::collective_Instance1(collective::Event::Proposed( 1, 0, hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), @@ -1464,7 +1522,7 @@ mod tests { }, EventRecord { phase: Phase::Initialization, - event: Event::collective_Instance1(RawEvent::Voted( + event: Event::collective_Instance1(collective::Event::Voted( 1, hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), false, @@ -1598,7 +1656,7 @@ mod tests { EventRecord { phase: Phase::Initialization, event: Event::collective_Instance1( - RawEvent::Proposed( + collective::Event::Proposed( 1, 0, hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), @@ -1608,7 +1666,7 @@ mod tests { }, EventRecord { phase: Phase::Initialization, - event: Event::collective_Instance1(RawEvent::Voted( + event: Event::collective_Instance1(collective::Event::Voted( 2, hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), false, @@ -1619,14 +1677,14 @@ mod tests { }, EventRecord { phase: Phase::Initialization, - event: Event::collective_Instance1(RawEvent::Closed( + event: Event::collective_Instance1(collective::Event::Closed( hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), 1, 1, )), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: Event::collective_Instance1(RawEvent::Disapproved( + event: Event::collective_Instance1(collective::Event::Disapproved( hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), )), topics: vec![], @@ -1649,7 +1707,7 @@ mod tests { assert_eq!(System::events(), vec![ EventRecord { phase: Phase::Initialization, - event: Event::collective_Instance1(RawEvent::Proposed( + event: Event::collective_Instance1(collective::Event::Proposed( 1, 0, hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), @@ -1659,7 +1717,7 @@ mod tests { }, EventRecord { phase: Phase::Initialization, - event: Event::collective_Instance1(RawEvent::Voted( + event: Event::collective_Instance1(collective::Event::Voted( 2, hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), true, @@ -1670,21 +1728,21 @@ mod tests { }, EventRecord { phase: Phase::Initialization, - event: Event::collective_Instance1(RawEvent::Closed( + event: Event::collective_Instance1(collective::Event::Closed( hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), 2, 0, )), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: Event::collective_Instance1(RawEvent::Approved( + event: Event::collective_Instance1(collective::Event::Approved( hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), )), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: Event::collective_Instance1(RawEvent::Executed( + event: Event::collective_Instance1(collective::Event::Executed( hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), Err(DispatchError::BadOrigin), )), @@ -1736,10 +1794,151 @@ mod tests { assert_ok!(Collective::disapprove_proposal(Origin::root(), hash.clone())); let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] }; assert_eq!(System::events(), vec![ - record(Event::collective_Instance1(RawEvent::Proposed(1, 0, hash.clone(), 2))), - record(Event::collective_Instance1(RawEvent::Voted(2, hash.clone(), true, 2, 0))), - record(Event::collective_Instance1(RawEvent::Disapproved(hash.clone()))), + record(Event::collective_Instance1(collective::Event::Proposed(1, 0, hash.clone(), 2))), + record(Event::collective_Instance1(collective::Event::Voted(2, hash.clone(), true, 2, 0))), + record(Event::collective_Instance1(collective::Event::Disapproved(hash.clone()))), ]); }) } + + #[test] + fn on_runtime_upgrade_works() { + + struct OldProposalsPrefix; + impl frame_support::traits::StorageInstance for OldProposalsPrefix { + const STORAGE_PREFIX: &'static str = "Proposals"; + fn pallet_prefix() -> &'static str { + "Collective" + } + } + type OldProposals = StorageValue>; + + struct OldProposalOfPrefix; + impl frame_support::traits::StorageInstance for OldProposalOfPrefix { + const STORAGE_PREFIX: &'static str = "ProposalOf"; + fn pallet_prefix() -> &'static str { + "Collective" + } + } + type OldProposalOf = StorageMap; + + struct OldVotingPrefix; + impl frame_support::traits::StorageInstance for OldVotingPrefix { + const STORAGE_PREFIX: &'static str = "Voting"; + fn pallet_prefix() -> &'static str { + "Collective" + } + } + type OldVoting = StorageMap>; + + struct OldProposalCountPrefix; + impl frame_support::traits::StorageInstance for OldProposalCountPrefix { + const STORAGE_PREFIX: &'static str = "ProposalCount"; + fn pallet_prefix() -> &'static str { + "Collective" + } + } + type OldProposalCount = StorageValue; + + struct OldMembersPrefix; + impl frame_support::traits::StorageInstance for OldMembersPrefix { + const STORAGE_PREFIX: &'static str = "Members"; + fn pallet_prefix() -> &'static str { + "Collective" + } + } + type OldMembers = StorageValue>; + + struct OldPrimePrefix; + impl frame_support::traits::StorageInstance for OldPrimePrefix { + const STORAGE_PREFIX: &'static str = "Prime"; + fn pallet_prefix() -> &'static str { + "Collective" + } + } + type OldPrime = StorageValue; + + new_test_ext().execute_with(|| { + + let end = 4; + + collective::UpgradedPalletPrefix::::put(false); + + // Storage Item "Proposals" + let proposal = make_proposal(42); + let hash: H256 = proposal.blake2_256().into(); + OldProposals::put(vec![hash]); + + assert_eq!(OldProposals::get(), Some(vec![hash])); + assert_eq!(collective::Proposals::::get(), []); + + // Storage Item "ProposalOf" + OldProposalOf::insert(hash, proposal); + + assert_eq!(OldProposalOf::iter().collect::>().len(), 1); + assert_eq!(collective::ProposalOf::::iter().collect::>().len(), 0); + + // Storage Item "Votes" + OldVoting::insert(hash, + Votes { + index: 0, + threshold: 2, + ayes: vec![], + nays: vec![1], + end + }, + ); + + assert_eq!(OldVoting::iter().collect::>().len(), 1); + assert_eq!(collective::Voting::::iter().collect::>().len(), 0); + + // Storage Item "ProposalCount" + OldProposalCount::put(10); + + assert_eq!(OldProposalCount::get(), Some(10)); + assert_eq!(collective::ProposalCount::::get(), 0); + + // Storage Item "Members" + OldMembers::put(vec![2]); + + assert_eq!(OldMembers::get(), Some(vec![2])); + assert_eq!(collective::Members::::get().is_empty(), true); + + // Storage Item "Prime" + OldPrime::put(2); + + assert_eq!(OldPrime::get(), Some(2)); + assert_eq!(collective::Prime::::get(), None); + + // Trigger runtime upgraade + AllPallets::on_runtime_upgrade(); + + // Storage Item "Proposals" + assert_eq!(OldProposals::get(), None); + assert_eq!(collective::Proposals::::get(), vec![hash]); + + // Storage Item "ProposalOf" + assert_eq!(OldProposalOf::iter().collect::>().len(), 0); + assert_eq!(collective::ProposalOf::::iter().collect::>().len(), 1); + + // Storage Item "Voting" + assert_eq!(OldVoting::iter().collect::>().len(), 0); + assert_eq!(collective::Voting::::iter().collect::>().len(), 1); + + // Storage Item "ProposalCount" + assert_eq!(OldProposalCount::get(), None); + assert_eq!(collective::ProposalCount::::get(), 10); + + // Storage Item "Members" + assert_eq!(OldMembers::get(), None); + assert_eq!(collective::Members::::get(), [2]); + + // Storage Item "Prime" + assert_eq!(OldPrime::get(), None); + assert_eq!(collective::Prime::::get(), Some(2)); + + + }) + } + }