1#![recursion_limit = "256"]
65#![cfg_attr(not(feature = "std"), no_std)]
66
67extern crate alloc;
68
69use alloc::boxed::Box;
70use codec::{Codec, Encode};
71use core::fmt::Debug;
72use frame_support::{
73 dispatch::DispatchResult,
74 ensure,
75 traits::{
76 schedule::{
77 v3::{Anon as ScheduleAnon, Named as ScheduleNamed},
78 DispatchTime,
79 },
80 Currency, LockIdentifier, OnUnbalanced, OriginTrait, PollStatus, Polling, QueryPreimage,
81 ReservableCurrency, StorePreimage, VoteTally,
82 },
83 BoundedVec,
84};
85use scale_info::TypeInfo;
86use sp_runtime::{
87 traits::{AtLeast32BitUnsigned, Bounded, Dispatchable, One, Saturating, Zero},
88 DispatchError, Perbill,
89};
90
91mod branch;
92pub mod migration;
93mod types;
94pub mod weights;
95
96use self::branch::{BeginDecidingBranch, OneFewerDecidingBranch, ServiceBranch};
97pub use self::{
98 pallet::*,
99 types::{
100 BalanceOf, BlockNumberFor, BoundedCallOf, CallOf, ConstTrackInfo, Curve, DecidingStatus,
101 DecidingStatusOf, Deposit, InsertSorted, NegativeImbalanceOf, PalletsOriginOf,
102 ReferendumIndex, ReferendumInfo, ReferendumInfoOf, ReferendumStatus, ReferendumStatusOf,
103 ScheduleAddressOf, StringLike, TallyOf, Track, TrackIdOf, TrackInfo, TrackInfoOf,
104 TracksInfo, VotesOf,
105 },
106 weights::WeightInfo,
107};
108pub use alloc::vec::Vec;
109use sp_runtime::traits::BlockNumberProvider;
110
111#[cfg(test)]
112mod mock;
113#[cfg(test)]
114mod tests;
115
116#[cfg(feature = "runtime-benchmarks")]
117pub mod benchmarking;
118
119pub use frame_support::traits::Get;
120
121const ASSEMBLY_ID: LockIdentifier = *b"assembly";
122
123#[frame_support::pallet]
124pub mod pallet {
125 use super::*;
126 use frame_support::{pallet_prelude::*, traits::EnsureOriginWithArg};
127 use frame_system::pallet_prelude::{
128 ensure_root, ensure_signed, ensure_signed_or_root, BlockNumberFor as SystemBlockNumberFor,
129 OriginFor,
130 };
131
132 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
134
135 #[pallet::pallet]
136 #[pallet::storage_version(STORAGE_VERSION)]
137 pub struct Pallet<T, I = ()>(_);
138
139 #[pallet::config]
140 pub trait Config<I: 'static = ()>: frame_system::Config + Sized {
141 type RuntimeCall: Parameter
143 + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
144 + From<Call<Self, I>>
145 + IsType<<Self as frame_system::Config>::RuntimeCall>
146 + From<frame_system::Call<Self>>;
147 #[allow(deprecated)]
148 type RuntimeEvent: From<Event<Self, I>>
149 + IsType<<Self as frame_system::Config>::RuntimeEvent>;
150 type WeightInfo: WeightInfo;
152 type Scheduler: ScheduleAnon<
154 BlockNumberFor<Self, I>,
155 CallOf<Self, I>,
156 PalletsOriginOf<Self>,
157 Hasher = Self::Hashing,
158 > + ScheduleNamed<
159 BlockNumberFor<Self, I>,
160 CallOf<Self, I>,
161 PalletsOriginOf<Self>,
162 Hasher = Self::Hashing,
163 >;
164 type Currency: ReservableCurrency<Self::AccountId>;
166 type SubmitOrigin: EnsureOriginWithArg<
169 Self::RuntimeOrigin,
170 PalletsOriginOf<Self>,
171 Success = Self::AccountId,
172 >;
173 type CancelOrigin: EnsureOrigin<Self::RuntimeOrigin>;
175 type KillOrigin: EnsureOrigin<Self::RuntimeOrigin>;
177 type Slash: OnUnbalanced<NegativeImbalanceOf<Self, I>>;
179 type Votes: AtLeast32BitUnsigned + Copy + Parameter + Member + MaxEncodedLen;
181 type Tally: VoteTally<Self::Votes, TrackIdOf<Self, I>>
183 + Clone
184 + Codec
185 + Eq
186 + Debug
187 + TypeInfo
188 + MaxEncodedLen;
189
190 #[pallet::constant]
193 type SubmissionDeposit: Get<BalanceOf<Self, I>>;
194
195 #[pallet::constant]
197 type MaxQueued: Get<u32>;
198
199 #[pallet::constant]
202 type UndecidingTimeout: Get<BlockNumberFor<Self, I>>;
203
204 #[pallet::constant]
208 type AlarmInterval: Get<BlockNumberFor<Self, I>>;
209
210 type Tracks: TracksInfo<
213 BalanceOf<Self, I>,
214 BlockNumberFor<Self, I>,
215 RuntimeOrigin = <Self::RuntimeOrigin as OriginTrait>::PalletsOrigin,
216 >;
217
218 type Preimages: QueryPreimage<H = Self::Hashing> + StorePreimage;
220
221 type BlockNumberProvider: BlockNumberProvider;
225 }
226
227 #[pallet::extra_constants]
228 impl<T: Config<I>, I: 'static> Pallet<T, I> {
229 #[pallet::constant_name(Tracks)]
233 fn tracks() -> Vec<(TrackIdOf<T, I>, ConstTrackInfo<BalanceOf<T, I>, BlockNumberFor<T, I>>)>
234 {
235 T::Tracks::tracks()
236 .map(|t| t.into_owned())
237 .map(|Track { id, info }| {
238 (
239 id,
240 ConstTrackInfo {
241 name: StringLike(info.name),
242 max_deciding: info.max_deciding,
243 decision_deposit: info.decision_deposit,
244 prepare_period: info.prepare_period,
245 decision_period: info.decision_period,
246 confirm_period: info.confirm_period,
247 min_enactment_period: info.min_enactment_period,
248 min_approval: info.min_approval,
249 min_support: info.min_support,
250 },
251 )
252 })
253 .collect()
254 }
255 }
256
257 #[pallet::storage]
259 pub type ReferendumCount<T, I = ()> = StorageValue<_, ReferendumIndex, ValueQuery>;
260
261 #[pallet::storage]
263 pub type ReferendumInfoFor<T: Config<I>, I: 'static = ()> =
264 StorageMap<_, Blake2_128Concat, ReferendumIndex, ReferendumInfoOf<T, I>>;
265
266 #[pallet::storage]
271 pub type TrackQueue<T: Config<I>, I: 'static = ()> = StorageMap<
272 _,
273 Twox64Concat,
274 TrackIdOf<T, I>,
275 BoundedVec<(ReferendumIndex, T::Votes), T::MaxQueued>,
276 ValueQuery,
277 >;
278
279 #[pallet::storage]
281 pub type DecidingCount<T: Config<I>, I: 'static = ()> =
282 StorageMap<_, Twox64Concat, TrackIdOf<T, I>, u32, ValueQuery>;
283
284 #[pallet::storage]
291 pub type MetadataOf<T: Config<I>, I: 'static = ()> =
292 StorageMap<_, Blake2_128Concat, ReferendumIndex, T::Hash>;
293
294 #[pallet::event]
295 #[pallet::generate_deposit(pub(super) fn deposit_event)]
296 pub enum Event<T: Config<I>, I: 'static = ()> {
297 Submitted {
299 index: ReferendumIndex,
301 track: TrackIdOf<T, I>,
303 proposal: BoundedCallOf<T, I>,
305 },
306 DecisionDepositPlaced {
308 index: ReferendumIndex,
310 who: T::AccountId,
312 amount: BalanceOf<T, I>,
314 },
315 DecisionDepositRefunded {
317 index: ReferendumIndex,
319 who: T::AccountId,
321 amount: BalanceOf<T, I>,
323 },
324 DepositSlashed {
326 who: T::AccountId,
328 amount: BalanceOf<T, I>,
330 },
331 DecisionStarted {
333 index: ReferendumIndex,
335 track: TrackIdOf<T, I>,
337 proposal: BoundedCallOf<T, I>,
339 tally: T::Tally,
341 },
342 ConfirmStarted {
343 index: ReferendumIndex,
345 },
346 ConfirmAborted {
347 index: ReferendumIndex,
349 },
350 Confirmed {
352 index: ReferendumIndex,
354 tally: T::Tally,
356 },
357 Approved {
359 index: ReferendumIndex,
361 },
362 Rejected {
364 index: ReferendumIndex,
366 tally: T::Tally,
368 },
369 TimedOut {
371 index: ReferendumIndex,
373 tally: T::Tally,
375 },
376 Cancelled {
378 index: ReferendumIndex,
380 tally: T::Tally,
382 },
383 Killed {
385 index: ReferendumIndex,
387 tally: T::Tally,
389 },
390 SubmissionDepositRefunded {
392 index: ReferendumIndex,
394 who: T::AccountId,
396 amount: BalanceOf<T, I>,
398 },
399 MetadataSet {
401 index: ReferendumIndex,
403 hash: T::Hash,
405 },
406 MetadataCleared {
408 index: ReferendumIndex,
410 hash: T::Hash,
412 },
413 }
414
415 #[pallet::error]
416 pub enum Error<T, I = ()> {
417 NotOngoing,
419 HasDeposit,
421 BadTrack,
423 Full,
425 QueueEmpty,
427 BadReferendum,
429 NothingToDo,
431 NoTrack,
433 Unfinished,
435 NoPermission,
437 NoDeposit,
439 BadStatus,
441 PreimageNotExist,
443 PreimageStoredWithDifferentLength,
445 }
446
447 #[pallet::hooks]
448 impl<T: Config<I>, I: 'static> Hooks<SystemBlockNumberFor<T>> for Pallet<T, I> {
449 #[cfg(feature = "try-runtime")]
450 fn try_state(_n: SystemBlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
451 Self::do_try_state()?;
452 Ok(())
453 }
454
455 #[cfg(any(feature = "std", test))]
456 fn integrity_test() {
457 T::Tracks::check_integrity().expect("Static tracks configuration is valid.");
458 }
459 }
460
461 #[pallet::call]
462 impl<T: Config<I>, I: 'static> Pallet<T, I> {
463 #[pallet::call_index(0)]
473 #[pallet::weight(T::WeightInfo::submit())]
474 pub fn submit(
475 origin: OriginFor<T>,
476 proposal_origin: Box<PalletsOriginOf<T>>,
477 proposal: BoundedCallOf<T, I>,
478 enactment_moment: DispatchTime<BlockNumberFor<T, I>>,
479 ) -> DispatchResult {
480 let proposal_origin = *proposal_origin;
481 let who = T::SubmitOrigin::ensure_origin(origin, &proposal_origin)?;
482
483 if let (Some(preimage_len), Some(proposal_len)) =
486 (proposal.lookup_hash().and_then(|h| T::Preimages::len(&h)), proposal.lookup_len())
487 {
488 if preimage_len != proposal_len {
489 return Err(Error::<T, I>::PreimageStoredWithDifferentLength.into())
490 }
491 }
492
493 let track =
494 T::Tracks::track_for(&proposal_origin).map_err(|_| Error::<T, I>::NoTrack)?;
495 let submission_deposit = Self::take_deposit(who, T::SubmissionDeposit::get())?;
496 let index = ReferendumCount::<T, I>::mutate(|x| {
497 let r = *x;
498 *x += 1;
499 r
500 });
501 let now = T::BlockNumberProvider::current_block_number();
502 let nudge_call =
503 T::Preimages::bound(CallOf::<T, I>::from(Call::nudge_referendum { index }))?;
504 let status = ReferendumStatus {
505 track,
506 origin: proposal_origin,
507 proposal: proposal.clone(),
508 enactment: enactment_moment,
509 submitted: now,
510 submission_deposit,
511 decision_deposit: None,
512 deciding: None,
513 tally: TallyOf::<T, I>::new(track),
514 in_queue: false,
515 alarm: Self::set_alarm(nudge_call, now.saturating_add(T::UndecidingTimeout::get())),
516 };
517 ReferendumInfoFor::<T, I>::insert(index, ReferendumInfo::Ongoing(status));
518
519 Self::deposit_event(Event::<T, I>::Submitted { index, track, proposal });
520 Ok(())
521 }
522
523 #[pallet::call_index(1)]
532 #[pallet::weight(ServiceBranch::max_weight_of_deposit::<T, I>())]
533 pub fn place_decision_deposit(
534 origin: OriginFor<T>,
535 index: ReferendumIndex,
536 ) -> DispatchResultWithPostInfo {
537 let who = ensure_signed(origin)?;
538 let mut status = Self::ensure_ongoing(index)?;
539 ensure!(status.decision_deposit.is_none(), Error::<T, I>::HasDeposit);
540 let track = T::Tracks::info(status.track).ok_or(Error::<T, I>::NoTrack)?;
541 status.decision_deposit =
542 Some(Self::take_deposit(who.clone(), track.decision_deposit)?);
543 let now = T::BlockNumberProvider::current_block_number();
544 let (info, _, branch) = Self::service_referendum(now, index, status);
545 ReferendumInfoFor::<T, I>::insert(index, info);
546 let e =
547 Event::<T, I>::DecisionDepositPlaced { index, who, amount: track.decision_deposit };
548 Self::deposit_event(e);
549 Ok(branch.weight_of_deposit::<T, I>().into())
550 }
551
552 #[pallet::call_index(2)]
560 #[pallet::weight(T::WeightInfo::refund_decision_deposit())]
561 pub fn refund_decision_deposit(
562 origin: OriginFor<T>,
563 index: ReferendumIndex,
564 ) -> DispatchResult {
565 ensure_signed_or_root(origin)?;
566 let mut info =
567 ReferendumInfoFor::<T, I>::get(index).ok_or(Error::<T, I>::BadReferendum)?;
568 let deposit = info
569 .take_decision_deposit()
570 .map_err(|_| Error::<T, I>::Unfinished)?
571 .ok_or(Error::<T, I>::NoDeposit)?;
572 Self::refund_deposit(Some(deposit.clone()));
573 ReferendumInfoFor::<T, I>::insert(index, info);
574 let e = Event::<T, I>::DecisionDepositRefunded {
575 index,
576 who: deposit.who,
577 amount: deposit.amount,
578 };
579 Self::deposit_event(e);
580 Ok(())
581 }
582
583 #[pallet::call_index(3)]
590 #[pallet::weight(T::WeightInfo::cancel())]
591 pub fn cancel(origin: OriginFor<T>, index: ReferendumIndex) -> DispatchResult {
592 T::CancelOrigin::ensure_origin(origin)?;
593 let status = Self::ensure_ongoing(index)?;
594 if let Some((_, last_alarm)) = status.alarm {
595 let _ = T::Scheduler::cancel(last_alarm);
596 }
597 Self::note_one_fewer_deciding(status.track);
598 Self::deposit_event(Event::<T, I>::Cancelled { index, tally: status.tally });
599 let info = ReferendumInfo::Cancelled(
600 T::BlockNumberProvider::current_block_number(),
601 Some(status.submission_deposit),
602 status.decision_deposit,
603 );
604 ReferendumInfoFor::<T, I>::insert(index, info);
605 Ok(())
606 }
607
608 #[pallet::call_index(4)]
615 #[pallet::weight(T::WeightInfo::kill())]
616 pub fn kill(origin: OriginFor<T>, index: ReferendumIndex) -> DispatchResult {
617 T::KillOrigin::ensure_origin(origin)?;
618 let status = Self::ensure_ongoing(index)?;
619 if let Some((_, last_alarm)) = status.alarm {
620 let _ = T::Scheduler::cancel(last_alarm);
621 }
622 Self::note_one_fewer_deciding(status.track);
623 Self::deposit_event(Event::<T, I>::Killed { index, tally: status.tally });
624 Self::slash_deposit(Some(status.submission_deposit.clone()));
625 Self::slash_deposit(status.decision_deposit.clone());
626 Self::do_clear_metadata(index);
627 let info = ReferendumInfo::Killed(T::BlockNumberProvider::current_block_number());
628 ReferendumInfoFor::<T, I>::insert(index, info);
629 Ok(())
630 }
631
632 #[pallet::call_index(5)]
637 #[pallet::weight(ServiceBranch::max_weight_of_nudge::<T, I>())]
638 pub fn nudge_referendum(
639 origin: OriginFor<T>,
640 index: ReferendumIndex,
641 ) -> DispatchResultWithPostInfo {
642 ensure_root(origin)?;
643 let now = T::BlockNumberProvider::current_block_number();
644 let mut status = Self::ensure_ongoing(index)?;
645 status.alarm = None;
647 let (info, dirty, branch) = Self::service_referendum(now, index, status);
648 if dirty {
649 ReferendumInfoFor::<T, I>::insert(index, info);
650 }
651 Ok(Some(branch.weight_of_nudge::<T, I>()).into())
652 }
653
654 #[pallet::call_index(6)]
664 #[pallet::weight(OneFewerDecidingBranch::max_weight::<T, I>())]
665 pub fn one_fewer_deciding(
666 origin: OriginFor<T>,
667 track: TrackIdOf<T, I>,
668 ) -> DispatchResultWithPostInfo {
669 ensure_root(origin)?;
670 let track_info = T::Tracks::info(track).ok_or(Error::<T, I>::BadTrack)?;
671 let mut track_queue = TrackQueue::<T, I>::get(track);
672 let branch =
673 if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) {
674 let now = T::BlockNumberProvider::current_block_number();
675 let (maybe_alarm, branch) =
676 Self::begin_deciding(&mut status, index, now, &track_info);
677 if let Some(set_alarm) = maybe_alarm {
678 Self::ensure_alarm_at(&mut status, index, set_alarm);
679 }
680 ReferendumInfoFor::<T, I>::insert(index, ReferendumInfo::Ongoing(status));
681 TrackQueue::<T, I>::insert(track, track_queue);
682 branch.into()
683 } else {
684 DecidingCount::<T, I>::mutate(track, |x| x.saturating_dec());
685 OneFewerDecidingBranch::QueueEmpty
686 };
687 Ok(Some(branch.weight::<T, I>()).into())
688 }
689
690 #[pallet::call_index(7)]
698 #[pallet::weight(T::WeightInfo::refund_submission_deposit())]
699 pub fn refund_submission_deposit(
700 origin: OriginFor<T>,
701 index: ReferendumIndex,
702 ) -> DispatchResult {
703 ensure_signed_or_root(origin)?;
704 let mut info =
705 ReferendumInfoFor::<T, I>::get(index).ok_or(Error::<T, I>::BadReferendum)?;
706 let deposit = info
707 .take_submission_deposit()
708 .map_err(|_| Error::<T, I>::BadStatus)?
709 .ok_or(Error::<T, I>::NoDeposit)?;
710 Self::refund_deposit(Some(deposit.clone()));
711 ReferendumInfoFor::<T, I>::insert(index, info);
712 let e = Event::<T, I>::SubmissionDepositRefunded {
713 index,
714 who: deposit.who,
715 amount: deposit.amount,
716 };
717 Self::deposit_event(e);
718 Ok(())
719 }
720
721 #[pallet::call_index(8)]
729 #[pallet::weight(
730 maybe_hash.map_or(
731 T::WeightInfo::clear_metadata(), |_| T::WeightInfo::set_some_metadata())
732 )]
733 pub fn set_metadata(
734 origin: OriginFor<T>,
735 index: ReferendumIndex,
736 maybe_hash: Option<T::Hash>,
737 ) -> DispatchResult {
738 let who = ensure_signed(origin)?;
739 if let Some(hash) = maybe_hash {
740 let status = Self::ensure_ongoing(index)?;
741 ensure!(status.submission_deposit.who == who, Error::<T, I>::NoPermission);
742 ensure!(T::Preimages::len(&hash).is_some(), Error::<T, I>::PreimageNotExist);
743 MetadataOf::<T, I>::insert(index, hash);
744 Self::deposit_event(Event::<T, I>::MetadataSet { index, hash });
745 Ok(())
746 } else {
747 if let Some(status) = Self::ensure_ongoing(index).ok() {
748 ensure!(status.submission_deposit.who == who, Error::<T, I>::NoPermission);
749 }
750 Self::do_clear_metadata(index);
751 Ok(())
752 }
753 }
754 }
755}
756
757impl<T: Config<I>, I: 'static> Polling<T::Tally> for Pallet<T, I> {
758 type Index = ReferendumIndex;
759 type Votes = VotesOf<T, I>;
760 type Moment = BlockNumberFor<T, I>;
761 type Class = TrackIdOf<T, I>;
762
763 fn classes() -> Vec<Self::Class> {
764 T::Tracks::track_ids().collect()
765 }
766
767 fn access_poll<R>(
768 index: Self::Index,
769 f: impl FnOnce(PollStatus<&mut T::Tally, BlockNumberFor<T, I>, TrackIdOf<T, I>>) -> R,
770 ) -> R {
771 match ReferendumInfoFor::<T, I>::get(index) {
772 Some(ReferendumInfo::Ongoing(mut status)) => {
773 let result = f(PollStatus::Ongoing(&mut status.tally, status.track));
774 let now = T::BlockNumberProvider::current_block_number();
775 Self::ensure_alarm_at(&mut status, index, now + One::one());
776 ReferendumInfoFor::<T, I>::insert(index, ReferendumInfo::Ongoing(status));
777 result
778 },
779 Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Completed(end, true)),
780 Some(ReferendumInfo::Rejected(end, ..)) => f(PollStatus::Completed(end, false)),
781 _ => f(PollStatus::None),
782 }
783 }
784
785 fn try_access_poll<R>(
786 index: Self::Index,
787 f: impl FnOnce(
788 PollStatus<&mut T::Tally, BlockNumberFor<T, I>, TrackIdOf<T, I>>,
789 ) -> Result<R, DispatchError>,
790 ) -> Result<R, DispatchError> {
791 match ReferendumInfoFor::<T, I>::get(index) {
792 Some(ReferendumInfo::Ongoing(mut status)) => {
793 let result = f(PollStatus::Ongoing(&mut status.tally, status.track))?;
794 let now = T::BlockNumberProvider::current_block_number();
795 Self::ensure_alarm_at(&mut status, index, now + One::one());
796 ReferendumInfoFor::<T, I>::insert(index, ReferendumInfo::Ongoing(status));
797 Ok(result)
798 },
799 Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Completed(end, true)),
800 Some(ReferendumInfo::Rejected(end, ..)) => f(PollStatus::Completed(end, false)),
801 _ => f(PollStatus::None),
802 }
803 }
804
805 fn as_ongoing(index: Self::Index) -> Option<(T::Tally, TrackIdOf<T, I>)> {
806 Self::ensure_ongoing(index).ok().map(|x| (x.tally, x.track))
807 }
808
809 #[cfg(feature = "runtime-benchmarks")]
810 fn create_ongoing(class: Self::Class) -> Result<Self::Index, ()> {
811 let index = ReferendumCount::<T, I>::mutate(|x| {
812 let r = *x;
813 *x += 1;
814 r
815 });
816 let now = T::BlockNumberProvider::current_block_number();
817 let dummy_account_id =
818 codec::Decode::decode(&mut sp_runtime::traits::TrailingZeroInput::new(&b"dummy"[..]))
819 .expect("infinite length input; no invalid inputs for type; qed");
820 let mut status = ReferendumStatusOf::<T, I> {
821 track: class,
822 origin: frame_support::dispatch::RawOrigin::Root.into(),
823 proposal: T::Preimages::bound(CallOf::<T, I>::from(Call::nudge_referendum { index }))
824 .map_err(|_| ())?,
825 enactment: DispatchTime::After(Zero::zero()),
826 submitted: now,
827 submission_deposit: Deposit { who: dummy_account_id, amount: Zero::zero() },
828 decision_deposit: None,
829 deciding: None,
830 tally: TallyOf::<T, I>::new(class),
831 in_queue: false,
832 alarm: None,
833 };
834
835 Self::ensure_alarm_at(&mut status, index, sp_runtime::traits::Bounded::max_value());
836 ReferendumInfoFor::<T, I>::insert(index, ReferendumInfo::Ongoing(status));
837 Ok(index)
838 }
839
840 #[cfg(feature = "runtime-benchmarks")]
841 fn end_ongoing(index: Self::Index, approved: bool) -> Result<(), ()> {
842 let mut status = Self::ensure_ongoing(index).map_err(|_| ())?;
843 Self::ensure_no_alarm(&mut status);
844 Self::note_one_fewer_deciding(status.track);
845 let now = T::BlockNumberProvider::current_block_number();
846 let info = if approved {
847 ReferendumInfo::Approved(now, Some(status.submission_deposit), status.decision_deposit)
848 } else {
849 ReferendumInfo::Rejected(now, Some(status.submission_deposit), status.decision_deposit)
850 };
851 ReferendumInfoFor::<T, I>::insert(index, info);
852 Ok(())
853 }
854
855 #[cfg(feature = "runtime-benchmarks")]
856 fn max_ongoing() -> (Self::Class, u32) {
857 let r = T::Tracks::tracks()
858 .max_by_key(|t| t.info.max_deciding)
859 .expect("Always one class");
860 (r.id, r.info.max_deciding)
861 }
862}
863
864impl<T: Config<I>, I: 'static> Pallet<T, I> {
865 pub fn ensure_ongoing(
868 index: ReferendumIndex,
869 ) -> Result<ReferendumStatusOf<T, I>, DispatchError> {
870 match ReferendumInfoFor::<T, I>::get(index) {
871 Some(ReferendumInfo::Ongoing(status)) => Ok(status),
872 _ => Err(Error::<T, I>::NotOngoing.into()),
873 }
874 }
875
876 pub fn is_referendum_passing(ref_index: ReferendumIndex) -> Result<bool, DispatchError> {
879 let info = ReferendumInfoFor::<T, I>::get(ref_index).ok_or(Error::<T, I>::BadReferendum)?;
880 match info {
881 ReferendumInfo::Ongoing(status) => {
882 let track = T::Tracks::info(status.track).ok_or(Error::<T, I>::NoTrack)?;
883 let elapsed = if let Some(deciding) = status.deciding {
884 T::BlockNumberProvider::current_block_number().saturating_sub(deciding.since)
885 } else {
886 Zero::zero()
887 };
888 Ok(Self::is_passing(
889 &status.tally,
890 elapsed,
891 track.decision_period,
892 &track.min_support,
893 &track.min_approval,
894 status.track,
895 ))
896 },
897 _ => Err(Error::<T, I>::NotOngoing.into()),
898 }
899 }
900
901 fn schedule_enactment(
903 index: ReferendumIndex,
904 track: &TrackInfoOf<T, I>,
905 desired: DispatchTime<BlockNumberFor<T, I>>,
906 origin: PalletsOriginOf<T>,
907 call: BoundedCallOf<T, I>,
908 ) {
909 let now = T::BlockNumberProvider::current_block_number();
910 let earliest_allowed = now.saturating_add(track.min_enactment_period.max(One::one()));
912 let desired = desired.evaluate(now);
913 let ok = T::Scheduler::schedule_named(
914 (ASSEMBLY_ID, "enactment", index).using_encoded(sp_io::hashing::blake2_256),
915 DispatchTime::At(desired.max(earliest_allowed)),
916 None,
917 63,
918 origin,
919 call,
920 )
921 .is_ok();
922 debug_assert!(ok, "LOGIC ERROR: bake_referendum/schedule_named failed");
923 }
924
925 fn set_alarm(
927 call: BoundedCallOf<T, I>,
928 when: BlockNumberFor<T, I>,
929 ) -> Option<(BlockNumberFor<T, I>, ScheduleAddressOf<T, I>)> {
930 let alarm_interval = T::AlarmInterval::get().max(One::one());
931 let when = (when.saturating_add(alarm_interval.saturating_sub(One::one())) /
934 alarm_interval)
935 .saturating_mul(alarm_interval);
936 let result = T::Scheduler::schedule(
937 DispatchTime::At(when),
938 None,
939 128u8,
940 frame_system::RawOrigin::Root.into(),
941 call,
942 );
943 debug_assert!(
944 result.is_ok(),
945 "Unable to schedule a new alarm at #{:?} (now: #{:?}), scheduler error: `{:?}`",
946 when,
947 T::BlockNumberProvider::current_block_number(),
948 result.unwrap_err(),
949 );
950 result.ok().map(|x| (when, x))
951 }
952
953 fn begin_deciding(
960 status: &mut ReferendumStatusOf<T, I>,
961 index: ReferendumIndex,
962 now: BlockNumberFor<T, I>,
963 track: &TrackInfoOf<T, I>,
964 ) -> (Option<BlockNumberFor<T, I>>, BeginDecidingBranch) {
965 let is_passing = Self::is_passing(
966 &status.tally,
967 Zero::zero(),
968 track.decision_period,
969 &track.min_support,
970 &track.min_approval,
971 status.track,
972 );
973 status.in_queue = false;
974 Self::deposit_event(Event::<T, I>::DecisionStarted {
975 index,
976 tally: status.tally.clone(),
977 proposal: status.proposal.clone(),
978 track: status.track,
979 });
980 let confirming = if is_passing {
981 Self::deposit_event(Event::<T, I>::ConfirmStarted { index });
982 Some(now.saturating_add(track.confirm_period))
983 } else {
984 None
985 };
986 let deciding_status = DecidingStatus { since: now, confirming };
987 let alarm = Self::decision_time(&deciding_status, &status.tally, status.track, track)
988 .max(now.saturating_add(One::one()));
989 status.deciding = Some(deciding_status);
990 let branch =
991 if is_passing { BeginDecidingBranch::Passing } else { BeginDecidingBranch::Failing };
992 (Some(alarm), branch)
993 }
994
995 fn ready_for_deciding(
1000 now: BlockNumberFor<T, I>,
1001 track: &TrackInfoOf<T, I>,
1002 index: ReferendumIndex,
1003 status: &mut ReferendumStatusOf<T, I>,
1004 ) -> (Option<BlockNumberFor<T, I>>, ServiceBranch) {
1005 let deciding_count = DecidingCount::<T, I>::get(status.track);
1006 if deciding_count < track.max_deciding {
1007 DecidingCount::<T, I>::insert(status.track, deciding_count.saturating_add(1));
1009 let r = Self::begin_deciding(status, index, now, track);
1010 (r.0, r.1.into())
1011 } else {
1012 let item = (index, status.tally.ayes(status.track));
1014 status.in_queue = true;
1015 TrackQueue::<T, I>::mutate(status.track, |q| q.insert_sorted_by_key(item, |x| x.1));
1016 (None, ServiceBranch::Queued)
1017 }
1018 }
1019
1020 fn next_for_deciding(
1023 track_queue: &mut BoundedVec<(u32, VotesOf<T, I>), T::MaxQueued>,
1024 ) -> Option<(ReferendumIndex, ReferendumStatusOf<T, I>)> {
1025 loop {
1026 let (index, _) = track_queue.pop()?;
1027 match Self::ensure_ongoing(index) {
1028 Ok(s) => return Some((index, s)),
1029 Err(_) => {}, }
1031 }
1032 }
1033
1034 fn note_one_fewer_deciding(track: TrackIdOf<T, I>) {
1038 let now = T::BlockNumberProvider::current_block_number();
1040 let next_block = now + One::one();
1041 let call = match T::Preimages::bound(CallOf::<T, I>::from(Call::one_fewer_deciding {
1042 track,
1043 })) {
1044 Ok(c) => c,
1045 Err(_) => {
1046 debug_assert!(false, "Unable to create a bounded call from `one_fewer_deciding`??",);
1047 return
1048 },
1049 };
1050 Self::set_alarm(call, next_block);
1051 }
1052
1053 fn ensure_alarm_at(
1059 status: &mut ReferendumStatusOf<T, I>,
1060 index: ReferendumIndex,
1061 alarm: BlockNumberFor<T, I>,
1062 ) -> bool {
1063 if status.alarm.as_ref().map_or(true, |&(when, _)| when != alarm) {
1064 Self::ensure_no_alarm(status);
1066 let call =
1067 match T::Preimages::bound(CallOf::<T, I>::from(Call::nudge_referendum { index })) {
1068 Ok(c) => c,
1069 Err(_) => {
1070 debug_assert!(
1071 false,
1072 "Unable to create a bounded call from `nudge_referendum`??",
1073 );
1074 return false
1075 },
1076 };
1077 status.alarm = Self::set_alarm(call, alarm);
1078 true
1079 } else {
1080 false
1081 }
1082 }
1083
1084 fn service_referendum(
1106 now: BlockNumberFor<T, I>,
1107 index: ReferendumIndex,
1108 mut status: ReferendumStatusOf<T, I>,
1109 ) -> (ReferendumInfoOf<T, I>, bool, ServiceBranch) {
1110 let mut dirty = false;
1111 let track = match T::Tracks::info(status.track) {
1113 Some(x) => x,
1114 None => return (ReferendumInfo::Ongoing(status), false, ServiceBranch::Fail),
1115 };
1116 let timeout = status.submitted + T::UndecidingTimeout::get();
1118 let mut alarm = BlockNumberFor::<T, I>::max_value();
1119 let branch;
1120 match &mut status.deciding {
1121 None => {
1122 if status.in_queue {
1124 let ayes = status.tally.ayes(status.track);
1126 let mut queue = TrackQueue::<T, I>::get(status.track);
1127 let maybe_old_pos = queue.iter().position(|(x, _)| *x == index);
1128 let new_pos = queue.binary_search_by_key(&ayes, |x| x.1).unwrap_or_else(|x| x);
1129 branch = if maybe_old_pos.is_none() && new_pos > 0 {
1130 let _ = queue.force_insert_keep_right(new_pos, (index, ayes));
1132 ServiceBranch::RequeuedInsertion
1133 } else if let Some(old_pos) = maybe_old_pos {
1134 queue[old_pos].1 = ayes;
1136 queue.slide(old_pos, new_pos);
1137 ServiceBranch::RequeuedSlide
1138 } else {
1139 ServiceBranch::NotQueued
1140 };
1141 TrackQueue::<T, I>::insert(status.track, queue);
1142 } else {
1143 branch = if status.decision_deposit.is_some() {
1145 let prepare_end = status.submitted.saturating_add(track.prepare_period);
1146 if now >= prepare_end {
1147 let (maybe_alarm, branch) =
1148 Self::ready_for_deciding(now, &track, index, &mut status);
1149 if let Some(set_alarm) = maybe_alarm {
1150 alarm = alarm.min(set_alarm);
1151 }
1152 dirty = true;
1153 branch
1154 } else {
1155 alarm = alarm.min(prepare_end);
1156 ServiceBranch::Preparing
1157 }
1158 } else {
1159 alarm = timeout;
1160 ServiceBranch::NoDeposit
1161 }
1162 }
1163 if status.deciding.is_none() && now >= timeout && !status.in_queue {
1165 Self::ensure_no_alarm(&mut status);
1167 Self::deposit_event(Event::<T, I>::TimedOut { index, tally: status.tally });
1168 return (
1169 ReferendumInfo::TimedOut(
1170 now,
1171 Some(status.submission_deposit),
1172 status.decision_deposit,
1173 ),
1174 true,
1175 ServiceBranch::TimedOut,
1176 )
1177 }
1178 },
1179 Some(deciding) => {
1180 let is_passing = Self::is_passing(
1181 &status.tally,
1182 now.saturating_sub(deciding.since),
1183 track.decision_period,
1184 &track.min_support,
1185 &track.min_approval,
1186 status.track,
1187 );
1188 branch = if is_passing {
1189 match deciding.confirming {
1190 Some(t) if now >= t => {
1191 Self::ensure_no_alarm(&mut status);
1193 Self::note_one_fewer_deciding(status.track);
1194 let (desired, call) = (status.enactment, status.proposal);
1195 Self::schedule_enactment(index, &track, desired, status.origin, call);
1196 Self::deposit_event(Event::<T, I>::Confirmed {
1197 index,
1198 tally: status.tally,
1199 });
1200 return (
1201 ReferendumInfo::Approved(
1202 now,
1203 Some(status.submission_deposit),
1204 status.decision_deposit,
1205 ),
1206 true,
1207 ServiceBranch::Approved,
1208 )
1209 },
1210 Some(_) => ServiceBranch::ContinueConfirming,
1211 None => {
1212 dirty = true;
1214 deciding.confirming = Some(now.saturating_add(track.confirm_period));
1215 Self::deposit_event(Event::<T, I>::ConfirmStarted { index });
1216 ServiceBranch::BeginConfirming
1217 },
1218 }
1219 } else {
1220 if now >= deciding.since.saturating_add(track.decision_period) {
1221 Self::ensure_no_alarm(&mut status);
1223 Self::note_one_fewer_deciding(status.track);
1224 Self::deposit_event(Event::<T, I>::Rejected { index, tally: status.tally });
1225 return (
1226 ReferendumInfo::Rejected(
1227 now,
1228 Some(status.submission_deposit),
1229 status.decision_deposit,
1230 ),
1231 true,
1232 ServiceBranch::Rejected,
1233 )
1234 }
1235 if deciding.confirming.is_some() {
1236 dirty = true;
1238 deciding.confirming = None;
1239 Self::deposit_event(Event::<T, I>::ConfirmAborted { index });
1240 ServiceBranch::EndConfirming
1241 } else {
1242 ServiceBranch::ContinueNotConfirming
1243 }
1244 };
1245 alarm = Self::decision_time(deciding, &status.tally, status.track, &track);
1246 },
1247 }
1248
1249 let dirty_alarm = if alarm < BlockNumberFor::<T, I>::max_value() {
1250 Self::ensure_alarm_at(&mut status, index, alarm)
1251 } else {
1252 Self::ensure_no_alarm(&mut status)
1253 };
1254 (ReferendumInfo::Ongoing(status), dirty_alarm || dirty, branch)
1255 }
1256
1257 fn decision_time(
1260 deciding: &DecidingStatusOf<T, I>,
1261 tally: &T::Tally,
1262 track_id: TrackIdOf<T, I>,
1263 track: &TrackInfoOf<T, I>,
1264 ) -> BlockNumberFor<T, I> {
1265 deciding.confirming.unwrap_or_else(|| {
1266 let approval = tally.approval(track_id);
1268 let support = tally.support(track_id);
1269 let until_approval = track.min_approval.delay(approval);
1270 let until_support = track.min_support.delay(support);
1271 let offset = until_support.max(until_approval);
1272 deciding.since.saturating_add(offset.mul_ceil(track.decision_period))
1273 })
1274 }
1275
1276 fn ensure_no_alarm(status: &mut ReferendumStatusOf<T, I>) -> bool {
1278 if let Some((_, last_alarm)) = status.alarm.take() {
1279 let _ = T::Scheduler::cancel(last_alarm);
1281 true
1282 } else {
1283 false
1284 }
1285 }
1286
1287 fn take_deposit(
1289 who: T::AccountId,
1290 amount: BalanceOf<T, I>,
1291 ) -> Result<Deposit<T::AccountId, BalanceOf<T, I>>, DispatchError> {
1292 T::Currency::reserve(&who, amount)?;
1293 Ok(Deposit { who, amount })
1294 }
1295
1296 fn refund_deposit(deposit: Option<Deposit<T::AccountId, BalanceOf<T, I>>>) {
1298 if let Some(Deposit { who, amount }) = deposit {
1299 T::Currency::unreserve(&who, amount);
1300 }
1301 }
1302
1303 fn slash_deposit(deposit: Option<Deposit<T::AccountId, BalanceOf<T, I>>>) {
1305 if let Some(Deposit { who, amount }) = deposit {
1306 T::Slash::on_unbalanced(T::Currency::slash_reserved(&who, amount).0);
1307 Self::deposit_event(Event::<T, I>::DepositSlashed { who, amount });
1308 }
1309 }
1310
1311 fn is_passing(
1315 tally: &T::Tally,
1316 elapsed: BlockNumberFor<T, I>,
1317 period: BlockNumberFor<T, I>,
1318 support_needed: &Curve,
1319 approval_needed: &Curve,
1320 id: TrackIdOf<T, I>,
1321 ) -> bool {
1322 let x = Perbill::from_rational(elapsed.min(period), period);
1323 support_needed.passing(x, tally.support(id)) &&
1324 approval_needed.passing(x, tally.approval(id))
1325 }
1326
1327 fn do_clear_metadata(index: ReferendumIndex) {
1329 if let Some(hash) = MetadataOf::<T, I>::take(index) {
1330 Self::deposit_event(Event::<T, I>::MetadataCleared { index, hash });
1331 }
1332 }
1333
1334 #[cfg(any(feature = "try-runtime", test))]
1344 fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
1345 ensure!(
1346 ReferendumCount::<T, I>::get() as usize ==
1347 ReferendumInfoFor::<T, I>::iter_keys().count(),
1348 "Number of referenda in `ReferendumInfoFor` is different than `ReferendumCount`"
1349 );
1350
1351 MetadataOf::<T, I>::iter_keys().try_for_each(|referendum_index| -> DispatchResult {
1352 ensure!(
1353 ReferendumInfoFor::<T, I>::contains_key(referendum_index),
1354 "Referendum indices in `MetadataOf` must also be stored in `ReferendumInfoOf`"
1355 );
1356 Ok(())
1357 })?;
1358
1359 Self::try_state_referenda_info()?;
1360 Self::try_state_tracks()?;
1361
1362 Ok(())
1363 }
1364
1365 #[cfg(any(feature = "try-runtime", test))]
1374 fn try_state_referenda_info() -> Result<(), sp_runtime::TryRuntimeError> {
1375 ReferendumInfoFor::<T, I>::iter().try_for_each(|(_, referendum)| {
1376 match referendum {
1377 ReferendumInfo::Ongoing(status) => {
1378 ensure!(
1379 T::Tracks::info(status.track).is_some(),
1380 "No track info for the track of the referendum."
1381 );
1382
1383 if let Some(deciding) = status.deciding {
1384 ensure!(
1385 deciding.since <
1386 deciding
1387 .confirming
1388 .unwrap_or(BlockNumberFor::<T, I>::max_value()),
1389 "Deciding status cannot begin before confirming stage."
1390 )
1391 }
1392 },
1393 _ => {},
1394 }
1395 Ok(())
1396 })
1397 }
1398
1399 #[cfg(any(feature = "try-runtime", test))]
1404 fn try_state_tracks() -> Result<(), sp_runtime::TryRuntimeError> {
1405 T::Tracks::tracks().try_for_each(|track| {
1406 TrackQueue::<T, I>::get(track.id).iter().try_for_each(
1407 |(referendum_index, _)| -> Result<(), sp_runtime::TryRuntimeError> {
1408 ensure!(
1409 ReferendumInfoFor::<T, I>::contains_key(referendum_index),
1410 "`ReferendumIndex` inside the `TrackQueue` should be a key in `ReferendumInfoFor`"
1411 );
1412 Ok(())
1413 },
1414 )?;
1415 Ok(())
1416 })
1417 }
1418}