diff --git a/Cargo.lock b/Cargo.lock index 0932eacf..188f2789 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1553,7 +1553,7 @@ dependencies = [ [[package]] name = "encointer-primitives" -version = "11.1.0" +version = "11.2.0" dependencies = [ "approx", "bs58 0.5.1", @@ -4509,7 +4509,7 @@ dependencies = [ [[package]] name = "pallet-encointer-democracy" -version = "11.1.0" +version = "11.2.0" dependencies = [ "approx", "encointer-primitives", diff --git a/communities/src/lib.rs b/communities/src/lib.rs index 7a427cfc..2d700761 100644 --- a/communities/src/lib.rs +++ b/communities/src/lib.rs @@ -175,6 +175,11 @@ pub mod pallet { location: Location, ) -> DispatchResultWithPostInfo { T::TrustableForNonDestructiveAction::ensure_origin(origin)?; + ensure!( + >::current_phase() == + CeremonyPhaseType::Registering, + Error::::RegistrationPhaseRequired + ); Self::do_add_location(cid, location) } @@ -191,6 +196,11 @@ pub mod pallet { location: Location, ) -> DispatchResultWithPostInfo { T::CommunityMaster::ensure_origin(origin)?; + ensure!( + >::current_phase() == + CeremonyPhaseType::Registering, + Error::::RegistrationPhaseRequired + ); Self::do_remove_location(cid, location) } @@ -409,11 +419,6 @@ impl Pallet { cid: CommunityIdentifier, location: Location, ) -> DispatchResultWithPostInfo { - ensure!( - >::current_phase() == - CeremonyPhaseType::Registering, - Error::::RegistrationPhaseRequired - ); Self::ensure_cid_exists(&cid)?; Self::validate_location(&location)?; let geo_hash = GeoHash::try_from_params(location.lat, location.lon) @@ -450,11 +455,6 @@ impl Pallet { cid: CommunityIdentifier, location: Location, ) -> DispatchResultWithPostInfo { - ensure!( - >::current_phase() == - CeremonyPhaseType::Registering, - Error::::RegistrationPhaseRequired - ); Self::ensure_cid_exists(&cid)?; let geo_hash = GeoHash::try_from_params(location.lat, location.lon) diff --git a/democracy/Cargo.toml b/democracy/Cargo.toml index 42b641de..af468112 100644 --- a/democracy/Cargo.toml +++ b/democracy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-encointer-democracy" -version = "11.1.0" +version = "11.2.0" authors = ["Encointer Association "] edition = "2021" description = "Democracy pallet for the Encointer blockchain runtime" diff --git a/democracy/src/lib.rs b/democracy/src/lib.rs index 6e609255..2eac2483 100644 --- a/democracy/src/lib.rs +++ b/democracy/src/lib.rs @@ -75,7 +75,10 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData); #[pallet::config] @@ -192,9 +195,14 @@ pub mod pallet { StorageMap<_, Blake2_128Concat, ProposalIdType, Tally, OptionQuery>; #[pallet::storage] - #[pallet::getter(fn cancelled_at)] - pub(super) type CancelledAt = - StorageMap<_, Blake2_128Concat, ProposalActionIdentifier, T::Moment, OptionQuery>; + #[pallet::getter(fn last_approved_proposal_for_action)] + pub(super) type LastApprovedProposalForAction = StorageMap< + _, + Blake2_128Concat, + ProposalActionIdentifier, + (T::Moment, ProposalIdType), + OptionQuery, + >; #[pallet::storage] #[pallet::getter(fn enactment_queue)] @@ -273,6 +281,9 @@ pub mod pallet { let sender = ensure_signed(origin)?; let tally = >::get(proposal_id).ok_or(Error::::InexistentProposal)?; + // make sure we don't vote on proposal that can't update anymore + Self::do_update_proposal_state(proposal_id)?; + let num_votes = Self::validate_and_commit_reputations(proposal_id, &sender, &reputations)?; @@ -291,13 +302,7 @@ pub mod pallet { >::insert(proposal_id, new_tally); - match Self::do_update_proposal_state(proposal_id) { - Ok(_) => Ok(()), - Err(error) => match error { - Error::::ProposalCannotBeUpdated => Ok(()), - other_error => Err(other_error), - }, - }?; + Self::do_update_proposal_state(proposal_id)?; if num_votes > 0 { Self::deposit_event(Event::VotePlaced { proposal_id, vote, num_votes }) @@ -416,12 +421,14 @@ pub mod pallet { let old_proposal_state = proposal.state; let now = >::get(); let proposal_action_identifier = proposal.action.clone().get_identifier(); - let cancelled_at = Self::cancelled_at(proposal_action_identifier); - let proposal_cancelled = - cancelled_at.is_some() && proposal.start < cancelled_at.unwrap(); + let last_approved_proposal_for_action = + Self::last_approved_proposal_for_action(proposal_action_identifier); + let proposal_cancelled_by_other = last_approved_proposal_for_action.is_some() && + proposal.start < last_approved_proposal_for_action.unwrap().0; let proposal_too_old = now - proposal.start > T::ProposalLifetime::get(); - if proposal_cancelled || proposal_too_old { - proposal.state = ProposalState::Cancelled; + if proposal_cancelled_by_other { + proposal.state = + ProposalState::SupersededBy { id: last_approved_proposal_for_action.unwrap().1 } } else { // passing if Self::is_passing(proposal_id)? { @@ -433,19 +440,24 @@ pub mod pallet { { proposal.state = ProposalState::Approved; >::insert(proposal_action_identifier, proposal_id); - >::insert(proposal_action_identifier, now); + >::insert( + proposal_action_identifier, + (now, proposal_id), + ); approved = true; } - // not confirming + // not yet confirming + } else if proposal_too_old { + proposal.state = ProposalState::Rejected; } else { proposal.state = ProposalState::Confirming { since: now }; } + // not passing - } else { - // confirming - if let ProposalState::Confirming { since: _ } = proposal.state { - proposal.state = ProposalState::Ongoing; - } + } else if proposal_too_old { + proposal.state = ProposalState::Rejected; + } else if let ProposalState::Confirming { since: _ } = proposal.state { + proposal.state = ProposalState::Ongoing; } } >::insert(proposal_id, &proposal); @@ -519,7 +531,7 @@ pub mod pallet { let turnout_permill = (tally.turnout * 1000).checked_div(electorate).unwrap_or(0); if turnout_permill < T::MinTurnout::get() { - return Ok(false); + return Ok(false) } Self::positive_turnout_bias(electorate, tally.turnout, tally.ayes) @@ -561,9 +573,7 @@ pub mod pallet { impl OnCeremonyPhaseChange for Pallet { fn on_ceremony_phase_change(new_phase: CeremonyPhaseType) { match new_phase { - CeremonyPhaseType::Assigning => {}, - CeremonyPhaseType::Attesting => {}, - CeremonyPhaseType::Registering => { + CeremonyPhaseType::Assigning => { // safe as EnactmentQueue has one key per ProposalActionType and those are bounded >::iter().for_each(|p| { if let Err(e) = Self::enact_proposal(p.1) { @@ -573,10 +583,13 @@ impl OnCeremonyPhaseChange for Pallet { // remove all keys from the map >::translate::(|_, _| None); }, + CeremonyPhaseType::Attesting => {}, + CeremonyPhaseType::Registering => {}, } } } +mod migrations; #[cfg(test)] mod mock; #[cfg(test)] diff --git a/democracy/src/migrations.rs b/democracy/src/migrations.rs new file mode 100644 index 00000000..1b42fbf4 --- /dev/null +++ b/democracy/src/migrations.rs @@ -0,0 +1,136 @@ +use super::*; + +use frame_support::{pallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade}; + +/// The log target. +const TARGET: &str = "democracy::migration::v1"; + +mod v0 { + use super::*; + use encointer_primitives::democracy::ProposalActionIdentifier; + + #[storage_alias] + pub(super) type CancelledAt = + StorageMap, Blake2_128Concat, ProposalActionIdentifier, u64, OptionQuery>; +} + +pub mod v1 { + use super::*; + + #[allow(dead_code)] + pub struct MigrateV0toV1purging(sp_std::marker::PhantomData); + + impl OnRuntimeUpgrade for MigrateV0toV1purging { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::DispatchError> { + Ok(0u8.encode()) + } + + fn on_runtime_upgrade() -> Weight { + let current_version = Pallet::::in_code_storage_version(); + let onchain_version = Pallet::::on_chain_storage_version(); + + log::info!( + target: TARGET, + "Running migration with current storage version {:?} / onchain {:?}", + current_version, + onchain_version + ); + + if onchain_version >= 1 { + log::warn!( + target: TARGET, + "skipping on_runtime_upgrade: executed on wrong storage version." + ); + return T::DbWeight::get().reads(1) + } + + let mut purged_keys = 0u64; + // this has been refactored to LastApprovedProposalForAction + purged_keys += v0::CancelledAt::::clear(u32::MAX, None).unique as u64; + // ProposalState incompatible with new proposal struct + purged_keys += Proposals::::clear(u32::MAX, None).unique as u64; + // ProposalState incompatible with new proposal struct + purged_keys += EnactmentQueue::::clear(u32::MAX, None).unique as u64; + // we must keep ProposalCount and PurposeIds as we dont want to purge burnt rep + + StorageVersion::new(1).put::>(); + T::DbWeight::get().reads_writes(purged_keys, purged_keys + 1) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::DispatchError> { + assert_eq!(Pallet::::on_chain_storage_version(), 1, "must upgrade"); + Ok(()) + } + } +} + +#[cfg(test)] +#[cfg(feature = "try-runtime")] +mod test { + use super::*; + use encointer_primitives::democracy::{ProposalActionIdentifier, ProposalState}; + use frame_support::{assert_storage_noop, traits::OnRuntimeUpgrade}; + use mock::{new_test_ext, TestRuntime}; + + #[allow(deprecated)] + #[test] + fn migration_v0_to_v1_works() { + new_test_ext().execute_with(|| { + StorageVersion::new(0).put::>(); + + // Insert some values into the v0 storage: + + v0::CancelledAt::::insert( + ProposalActionIdentifier::SetInactivityTimeout, + 42, + ); + Proposals::::insert( + 1, + Proposal { + start: 0, + start_cindex: 0, + action: ProposalAction::SetInactivityTimeout(42), + state: ProposalState::Approved, + electorate_size: 0, + }, + ); + EnactmentQueue::::insert( + ProposalActionIdentifier::SetInactivityTimeout, + 1, + ); + + assert_eq!(v0::CancelledAt::::iter_keys().count(), 1); + assert_eq!(Proposals::::iter_keys().count(), 1); + assert_eq!(EnactmentQueue::::iter_keys().count(), 1); + + // Migrate V0 to V1. + let state = v1::MigrateV0toV1purging::::pre_upgrade().unwrap(); + let _weight = v1::MigrateV0toV1purging::::on_runtime_upgrade(); + v1::MigrateV0toV1purging::::post_upgrade(state).unwrap(); + + // Check that all values got migrated. + assert_eq!(v0::CancelledAt::::iter_keys().count(), 0); + assert_eq!(Proposals::::iter_keys().count(), 0); + assert_eq!(EnactmentQueue::::iter_keys().count(), 0); + }); + } + + #[allow(deprecated)] + #[test] + fn migration_v1_to_v1_is_noop() { + new_test_ext().execute_with(|| { + StorageVersion::new(1).put::>(); + + LastApprovedProposalForAction::::insert( + ProposalActionIdentifier::SetInactivityTimeout, + (42, 43), + ); + + let state = v1::MigrateV0toV1purging::::pre_upgrade().unwrap(); + assert_storage_noop!(v1::MigrateV0toV1purging::::on_runtime_upgrade()); + v1::MigrateV0toV1purging::::post_upgrade(state).unwrap(); + }); + } +} diff --git a/democracy/src/mock.rs b/democracy/src/mock.rs index 32aa8db4..a087d9e0 100644 --- a/democracy/src/mock.rs +++ b/democracy/src/mock.rs @@ -45,9 +45,9 @@ frame_support::construct_runtime!( impl dut::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type MaxReputationCount = ConstU32<10>; - // 10 blocks + // 10 6s blocks type ConfirmationPeriod = ConstU64<60000>; - // 40 blocks + // 40 6s blocks type ProposalLifetime = ConstU64<240000>; type MinTurnout = ConstU128<20>; // 2% type WeightInfo = (); // 2% diff --git a/democracy/src/tests.rs b/democracy/src/tests.rs index 361369b3..b29d8f7a 100644 --- a/democracy/src/tests.rs +++ b/democracy/src/tests.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Encointer. If not, see . -//! Unit tests for the tokens module. +//! Unit tests for the encointer democracy module. use super::*; use crate::mock::{ @@ -378,11 +378,11 @@ fn voting_works() { 1, Vote::Nay, BoundedVec::try_from(vec![ - (cid, 2), // invalid beacuse out of range - (cid, 4), // invalid beacuse already used - (cid2, 4), // invlaid because unverified + (cid, 2), // invalid because out of range + (cid, 4), // invalid because already used + (cid2, 4), // invalid because unverified (cid2, 5), // valid - (cid2, 6), // invlaid because out of range + (cid2, 6), // invalid because out of range (cid2, 3), // invlalid non-existent ]) .unwrap() @@ -412,7 +412,7 @@ fn do_update_proposal_state_fails_with_wrong_state() { start: Moment::from(1u64), start_cindex: 1, action: ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(100u32)), - state: ProposalState::Cancelled, + state: ProposalState::Rejected, electorate_size: 0, }; Proposals::::insert(1, proposal); @@ -426,6 +426,15 @@ fn do_update_proposal_state_fails_with_wrong_state() { }; Proposals::::insert(2, proposal2); + let proposal3: Proposal = Proposal { + start: Moment::from(1u64), + start_cindex: 1, + action: ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(100u32)), + state: ProposalState::SupersededBy { id: 2 }, + electorate_size: 0, + }; + Proposals::::insert(3, proposal3); + assert_err!( EncointerDemocracy::do_update_proposal_state(1), Error::::ProposalCannotBeUpdated @@ -435,11 +444,16 @@ fn do_update_proposal_state_fails_with_wrong_state() { EncointerDemocracy::do_update_proposal_state(2), Error::::ProposalCannotBeUpdated ); + + assert_err!( + EncointerDemocracy::do_update_proposal_state(3), + Error::::ProposalCannotBeUpdated + ); }); } #[test] -fn do_update_proposal_state_works_with_cancelled_proposal() { +fn do_update_proposal_state_cancels_superseded_proposal() { new_test_ext().execute_with(|| { let proposal_action = ProposalAction::SetInactivityTimeout(8); @@ -448,9 +462,10 @@ fn do_update_proposal_state_works_with_cancelled_proposal() { proposal_action )); - CancelledAt::::insert( + //another proposal of same action has been scheduled for enactment + LastApprovedProposalForAction::::insert( ProposalActionIdentifier::SetInactivityTimeout, - 3 * BLOCKTIME, + (3 * BLOCKTIME, 2), ); assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Ongoing); @@ -459,7 +474,10 @@ fn do_update_proposal_state_works_with_cancelled_proposal() { assert_ok!(EncointerDemocracy::do_update_proposal_state(1)); - assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Cancelled); + assert_eq!( + EncointerDemocracy::proposals(1).unwrap().state, + ProposalState::SupersededBy { id: 2 } + ); }); } @@ -482,7 +500,7 @@ fn do_update_proposal_state_works_with_too_old_proposal() { advance_n_blocks(1); assert_ok!(EncointerDemocracy::do_update_proposal_state(1)); - assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Cancelled); + assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Rejected); }); } @@ -544,7 +562,8 @@ fn do_update_proposal_state_works() { EncointerDemocracy::enactment_queue(proposal_action.clone().get_identifier()), None ); - advance_n_blocks(11); + // should even work if proposal is too old before update is called. + advance_n_blocks(41); // proposal is enacted assert_eq!(EncointerDemocracy::do_update_proposal_state(1).unwrap(), true); assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Approved); @@ -682,8 +701,7 @@ fn enactment_updates_proposal_metadata_and_enactment_queue() { EnactmentQueue::::insert(proposal_action2.clone().get_identifier(), 2); run_to_next_phase(); - run_to_next_phase(); - run_to_next_phase(); + // first assigning phase after proposal lifetime ended assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Enacted); @@ -737,21 +755,16 @@ fn proposal_happy_flow() { Some(Event::VotePlaced { proposal_id: 1, vote: Vote::Aye, num_votes: 3 }.into()) ); + // let pass the proposal lifetime advance_n_blocks(40); - assert_ok!(EncointerDemocracy::vote( + + assert_ok!(EncointerDemocracy::update_proposal_state( RuntimeOrigin::signed(alice.clone()), 1, - Vote::Aye, - BoundedVec::try_from(vec![(cid2, 3)]).unwrap() )); assert_eq!( last_event::(), - Some(Event::VoteFailed { proposal_id: 1, vote: Vote::Aye }.into()) - ); - - assert_eq!( - event_at_index::(get_num_events::() - 2), Some( Event::ProposalStateUpdated { proposal_id: 1, @@ -762,8 +775,7 @@ fn proposal_happy_flow() { ); run_to_next_phase(); - run_to_next_phase(); - run_to_next_phase(); + // first assigning phase after proposal lifetime ended assert_eq!( event_at_index::(get_num_events::() - 2), @@ -792,12 +804,14 @@ fn enact_add_location_works() { )); let geo_hash = GeoHash::try_from_params(location.lat, location.lon).unwrap(); + + // directly inject the proposal into the enactment queue EnactmentQueue::::insert(proposal_action.clone().get_identifier(), 1); + assert_eq!(EncointerCommunities::locations(cid, geo_hash.clone()).len(), 0); run_to_next_phase(); - run_to_next_phase(); - run_to_next_phase(); + // first assigning phase after proposal lifetime ended assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Enacted); assert_eq!(EncointerDemocracy::enactment_queue(proposal_action.get_identifier()), None); @@ -819,12 +833,13 @@ fn enact_remove_location_works() { )); let geo_hash = GeoHash::try_from_params(location.lat, location.lon).unwrap(); + + // directly inject the proposal into the enactment queue EnactmentQueue::::insert(proposal_action.clone().get_identifier(), 1); assert_eq!(EncointerCommunities::locations(cid, geo_hash.clone()).len(), 1); run_to_next_phase(); - run_to_next_phase(); - run_to_next_phase(); + // first assigning phase after proposal lifetime ended assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Enacted); assert_eq!(EncointerDemocracy::enactment_queue(proposal_action.get_identifier()), None); @@ -849,11 +864,11 @@ fn enact_update_community_metadata_works() { proposal_action.clone() )); + // directly inject the proposal into the enactment queue EnactmentQueue::::insert(proposal_action.clone().get_identifier(), 1); run_to_next_phase(); - run_to_next_phase(); - run_to_next_phase(); + // first assigning phase after proposal lifetime ended assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Enacted); assert_eq!(EncointerDemocracy::enactment_queue(proposal_action.get_identifier()), None); @@ -877,13 +892,13 @@ fn enact_update_demurrage_works() { proposal_action.clone() )); + // directly inject the proposal into the enactment queue EnactmentQueue::::insert(proposal_action.clone().get_identifier(), 1); assert!(EncointerBalances::demurrage_per_block(&cid) != demurrage); run_to_next_phase(); - run_to_next_phase(); - run_to_next_phase(); + // first assigning phase after proposal lifetime ended assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Enacted); assert_eq!(EncointerDemocracy::enactment_queue(proposal_action.get_identifier()), None); @@ -903,11 +918,11 @@ fn enact_update_nominal_income_works() { proposal_action.clone() )); + // directly inject the proposal into the enactment queue EnactmentQueue::::insert(proposal_action.clone().get_identifier(), 1); run_to_next_phase(); - run_to_next_phase(); - run_to_next_phase(); + // first assigning phase after proposal lifetime ended assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Enacted); assert_eq!(EncointerDemocracy::enactment_queue(proposal_action.get_identifier()), None); @@ -926,11 +941,11 @@ fn enact_set_inactivity_timeout_works() { proposal_action.clone() )); + // directly inject the proposal into the enactment queue EnactmentQueue::::insert(proposal_action.clone().get_identifier(), 1); run_to_next_phase(); - run_to_next_phase(); - run_to_next_phase(); + // first assigning phase after proposal lifetime ended assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Enacted); assert_eq!( @@ -948,7 +963,10 @@ fn enact_set_inactivity_timeout_works() { fn enactment_error_fires_event() { new_test_ext().execute_with(|| { System::set_block_number(System::block_number() + 1); // this is needed to assert events + + // use inexisting community to cause an enactment error let cid = CommunityIdentifier::from_str("u0qj944rhWE").unwrap(); + let alice = alice(); let proposal_action = ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(13037u32)); @@ -957,11 +975,11 @@ fn enactment_error_fires_event() { proposal_action.clone() )); + // directly inject the proposal into the enactment queue EnactmentQueue::::insert(proposal_action.clone().get_identifier(), 1); run_to_next_phase(); - run_to_next_phase(); - run_to_next_phase(); + // first assigning phase after proposal lifetime ended match event_at_index::(get_num_events::() - 2).unwrap() { mock::RuntimeEvent::EncointerDemocracy(Event::EnactmentFailed { diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 596f9d22..d02034b1 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "encointer-primitives" -version = "11.1.0" +version = "11.2.0" authors = ["Encointer Association "] edition = "2021" description = "Primitives for the Encointer blockchain runtime" diff --git a/primitives/src/democracy.rs b/primitives/src/democracy.rs index ac3bf7fe..8b6234d1 100644 --- a/primitives/src/democracy.rs +++ b/primitives/src/democracy.rs @@ -104,7 +104,8 @@ pub enum ProposalState { Ongoing, Confirming { since: Moment }, Approved, - Cancelled, + SupersededBy { id: ProposalIdType }, + Rejected, Enacted, } @@ -112,6 +113,10 @@ impl ProposalState { pub fn can_update(self) -> bool { matches!(self, Self::Confirming { since: _ } | Self::Ongoing) } + + pub fn has_failed(self) -> bool { + matches!(self, Self::SupersededBy { id: _ } | Self::Rejected) + } } #[derive(Encode, Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))] diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index baf26c7f..32579c6e 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -246,11 +246,12 @@ parameter_types! { #[macro_export] macro_rules! impl_encointer_scheduler { - ($t:ident, $ceremonies:ident, $reputationcommitments:ident) => { + ($t:ident, $ceremonies:ident, $othercallbackpallet:ident) => { impl pallet_encointer_scheduler::Config for $t { type RuntimeEvent = RuntimeEvent; type CeremonyMaster = EnsureAlice; - type OnCeremonyPhaseChange = ($ceremonies, $reputationcommitments); //OnCeremonyPhaseChange; + // democracy callback MUST be executed before ceremony callback! + type OnCeremonyPhaseChange = ($othercallbackpallet, $ceremonies); //OnCeremonyPhaseChange; type MomentsPerDay = MomentsPerDay; type WeightInfo = (); }