// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Integration test together with the ranked-collective pallet. use frame_support::{ assert_noop, assert_ok, derive_impl, hypothetically, ord_parameter_types, pallet_prelude::Weight, parameter_types, traits::{ConstU16, EitherOf, IsInVec, MapSuccess, PollStatus, Polling, TryMapSuccess}, }; use frame_system::EnsureSignedBy; use pallet_ranked_collective::{EnsureRanked, Geometric, Rank, TallyOf, Votes}; use sp_core::Get; use sp_runtime::{ traits::{Convert, ReduceBy, ReplaceWithDefault, TryMorphInto}, BuildStorage, DispatchError, }; type Class = Rank; use crate as pallet_core_fellowship; use crate::*; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( pub enum Test { System: frame_system, CoreFellowship: pallet_core_fellowship, Club: pallet_ranked_collective, } ); parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1_000_000, u64::max_value())); } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type Block = Block; } parameter_types! { pub static MinRankOfClassDelta: Rank = 0; } parameter_types! { pub ZeroToNine: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; pub EvidenceSize: u32 = 1024; } ord_parameter_types! { pub const One: u64 = 1; } impl Config for Test { type WeightInfo = (); type RuntimeEvent = RuntimeEvent; type Members = Club; type Balance = u64; type ParamsOrigin = EnsureSignedBy; type InductOrigin = EnsureInducted; type ApproveOrigin = TryMapSuccess, u64>, TryMorphInto>; type PromoteOrigin = TryMapSuccess, u64>, TryMorphInto>; type EvidenceSize = EvidenceSize; } pub struct TestPolls; impl Polling> for TestPolls { type Index = u8; type Votes = Votes; type Moment = u64; type Class = Class; fn classes() -> Vec { unimplemented!() } fn as_ongoing(_: u8) -> Option<(TallyOf, Self::Class)> { unimplemented!() } fn access_poll( _: Self::Index, _: impl FnOnce(PollStatus<&mut TallyOf, Self::Moment, Self::Class>) -> R, ) -> R { unimplemented!() } fn try_access_poll( _: Self::Index, _: impl FnOnce( PollStatus<&mut TallyOf, Self::Moment, Self::Class>, ) -> Result, ) -> Result { unimplemented!() } #[cfg(feature = "runtime-benchmarks")] fn create_ongoing(_: Self::Class) -> Result { unimplemented!() } #[cfg(feature = "runtime-benchmarks")] fn end_ongoing(_: Self::Index, _: bool) -> Result<(), ()> { unimplemented!() } } /// Convert the tally class into the minimum rank required to vote on the poll. /// MinRank(Class) = Class - Delta pub struct MinRankOfClass(PhantomData); impl> Convert for MinRankOfClass { fn convert(a: Class) -> Rank { a.saturating_sub(Delta::get()) } } impl pallet_ranked_collective::Config for Test { type WeightInfo = (); type RuntimeEvent = RuntimeEvent; type PromoteOrigin = EitherOf< // Root can promote arbitrarily. frame_system::EnsureRootWithSuccess>, // Members can promote up to the rank of 2 below them. MapSuccess, ReduceBy>>, >; type AddOrigin = MapSuccess>; type DemoteOrigin = EitherOf< // Root can demote arbitrarily. frame_system::EnsureRootWithSuccess>, // Members can demote up to the rank of 3 below them. MapSuccess, ReduceBy>>, >; type RemoveOrigin = Self::DemoteOrigin; type ExchangeOrigin = EitherOf< // Root can exchange arbitrarily. frame_system::EnsureRootWithSuccess>, // Members can exchange up to the rank of 2 below them. MapSuccess, ReduceBy>>, >; type Polls = TestPolls; type MinRankOfClass = MinRankOfClass; type MemberSwappedHandler = CoreFellowship; type VoteWeight = Geometric; #[cfg(feature = "runtime-benchmarks")] type BenchmarkSetup = CoreFellowship; } pub fn new_test_ext() -> sp_io::TestExternalities { let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| { let params = ParamsType { active_salary: [10, 20, 30, 40, 50, 60, 70, 80, 90], passive_salary: [1, 2, 3, 4, 5, 6, 7, 8, 9], demotion_period: [2, 4, 6, 8, 10, 12, 14, 16, 18], min_promotion_period: [3, 6, 9, 12, 15, 18, 21, 24, 27], offboard_timeout: 1, }; assert_ok!(CoreFellowship::set_params(signed(1), Box::new(params))); System::set_block_number(1); }); ext } fn promote_n_times(acc: u64, r: u16) { for _ in 0..r { assert_ok!(Club::promote_member(RuntimeOrigin::root(), acc)); } } fn signed(who: u64) -> RuntimeOrigin { RuntimeOrigin::signed(who) } fn assert_last_event(generic_event: ::RuntimeEvent) { let events = frame_system::Pallet::::events(); let system_event: ::RuntimeEvent = generic_event.into(); let frame_system::EventRecord { event, .. } = events.last().expect("Event expected"); assert_eq!(event, &system_event.into()); } fn evidence(e: u32) -> Evidence { e.encode() .into_iter() .cycle() .take(1024) .collect::>() .try_into() .expect("Static length matches") } #[test] fn swap_simple_works() { new_test_ext().execute_with(|| { for i in 0u16..9 { let acc = i as u64; assert_ok!(Club::add_member(RuntimeOrigin::root(), acc)); promote_n_times(acc, i); assert_ok!(CoreFellowship::import(signed(acc))); // Swapping normally works: assert_ok!(Club::exchange_member(RuntimeOrigin::root(), acc, acc + 10)); assert_last_event(Event::Swapped { who: acc, new_who: acc + 10 }.into()); } }); } /// Exhaustively test that adding member `1` is equivalent to adding member `0` and then swapping. /// /// The member also submits evidence before the swap. #[test] fn swap_exhaustive_works() { new_test_ext().execute_with(|| { let root_add = hypothetically!({ assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); promote_n_times(1, 4); assert_ok!(CoreFellowship::import(signed(1))); assert_ok!(CoreFellowship::submit_evidence(signed(1), Wish::Retention, evidence(1))); // The events mess up the storage root: System::reset_events(); sp_io::storage::root(sp_runtime::StateVersion::V1) }); let root_swap = hypothetically!({ assert_ok!(Club::add_member(RuntimeOrigin::root(), 0)); promote_n_times(0, 4); assert_ok!(CoreFellowship::import(signed(0))); assert_ok!(CoreFellowship::submit_evidence(signed(0), Wish::Retention, evidence(1))); // Now we swap: assert_ok!(Club::exchange_member(RuntimeOrigin::root(), 0, 1)); System::reset_events(); sp_io::storage::root(sp_runtime::StateVersion::V1) }); assert_eq!(root_add, root_swap); // Ensure that we don't compare trivial stuff like `()` from a type error above. assert_eq!(root_add.len(), 32); }); } #[test] fn swap_bad_noops() { new_test_ext().execute_with(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 0)); promote_n_times(0, 0); assert_ok!(CoreFellowship::import(signed(0))); assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); promote_n_times(1, 1); assert_ok!(CoreFellowship::import(signed(1))); // Swapping for another member is a noop: assert_noop!( Club::exchange_member(RuntimeOrigin::root(), 0, 1), pallet_ranked_collective::Error::::AlreadyMember ); // Swapping for the same member is a noop: assert_noop!( Club::exchange_member(RuntimeOrigin::root(), 0, 0), pallet_ranked_collective::Error::::SameMember ); }); }