1use super::*;
21use alloc::borrow::Cow;
22use codec::{Compact, Decode, DecodeWithMemTracking, Encode, EncodeLike, Input, MaxEncodedLen};
23use core::fmt::Debug;
24use frame_support::{
25 traits::{schedule::v3::Anon, Bounded},
26 Parameter,
27};
28use scale_info::{Type, TypeInfo};
29use sp_arithmetic::{Rounding::*, SignedRounding::*};
30use sp_runtime::{FixedI64, PerThing, RuntimeDebug};
31
32pub type BalanceOf<T, I = ()> =
33 <<T as Config<I>>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
34
35pub type BlockNumberFor<T, I> =
36 <<T as Config<I>>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
37
38pub type NegativeImbalanceOf<T, I> = <<T as Config<I>>::Currency as Currency<
39 <T as frame_system::Config>::AccountId,
40>>::NegativeImbalance;
41pub type CallOf<T, I> = <T as Config<I>>::RuntimeCall;
42pub type BoundedCallOf<T, I> =
43 Bounded<<T as Config<I>>::RuntimeCall, <T as frame_system::Config>::Hashing>;
44pub type VotesOf<T, I> = <T as Config<I>>::Votes;
45pub type TallyOf<T, I> = <T as Config<I>>::Tally;
46pub type PalletsOriginOf<T> =
47 <<T as frame_system::Config>::RuntimeOrigin as OriginTrait>::PalletsOrigin;
48pub type ReferendumInfoOf<T, I> = ReferendumInfo<
49 TrackIdOf<T, I>,
50 PalletsOriginOf<T>,
51 BlockNumberFor<T, I>,
52 BoundedCallOf<T, I>,
53 BalanceOf<T, I>,
54 TallyOf<T, I>,
55 <T as frame_system::Config>::AccountId,
56 ScheduleAddressOf<T, I>,
57>;
58pub type ReferendumStatusOf<T, I> = ReferendumStatus<
59 TrackIdOf<T, I>,
60 PalletsOriginOf<T>,
61 BlockNumberFor<T, I>,
62 BoundedCallOf<T, I>,
63 BalanceOf<T, I>,
64 TallyOf<T, I>,
65 <T as frame_system::Config>::AccountId,
66 ScheduleAddressOf<T, I>,
67>;
68pub type DecidingStatusOf<T, I> = DecidingStatus<BlockNumberFor<T, I>>;
69pub type TrackInfoOf<T, I = ()> = TrackInfo<BalanceOf<T, I>, BlockNumberFor<T, I>>;
70pub type TrackIdOf<T, I> =
71 <<T as Config<I>>::Tracks as TracksInfo<BalanceOf<T, I>, BlockNumberFor<T, I>>>::Id;
72pub type ScheduleAddressOf<T, I> = <<T as Config<I>>::Scheduler as Anon<
73 BlockNumberFor<T, I>,
74 CallOf<T, I>,
75 PalletsOriginOf<T>,
76>>::Address;
77
78pub type ReferendumIndex = u32;
80
81pub trait InsertSorted<T> {
82 fn insert_sorted_by_key<F: FnMut(&T) -> K, K: PartialOrd<K> + Ord>(
87 &mut self,
88 t: T,
89 f: F,
90 ) -> bool;
91}
92impl<T: Ord, S: Get<u32>> InsertSorted<T> for BoundedVec<T, S> {
93 fn insert_sorted_by_key<F: FnMut(&T) -> K, K: PartialOrd<K> + Ord>(
94 &mut self,
95 t: T,
96 mut f: F,
97 ) -> bool {
98 let index = self.binary_search_by_key::<K, F>(&f(&t), f).unwrap_or_else(|x| x);
99 self.force_insert_keep_right(index, t).is_ok()
100 }
101}
102
103#[derive(
104 Encode,
105 Decode,
106 DecodeWithMemTracking,
107 Clone,
108 PartialEq,
109 Eq,
110 RuntimeDebug,
111 TypeInfo,
112 MaxEncodedLen,
113)]
114pub struct DecidingStatus<BlockNumber> {
115 pub since: BlockNumber,
118 pub confirming: Option<BlockNumber>,
121}
122
123#[derive(
124 Encode,
125 Decode,
126 DecodeWithMemTracking,
127 Clone,
128 PartialEq,
129 Eq,
130 RuntimeDebug,
131 TypeInfo,
132 MaxEncodedLen,
133)]
134pub struct Deposit<AccountId, Balance> {
135 pub who: AccountId,
136 pub amount: Balance,
137}
138
139pub const DEFAULT_MAX_TRACK_NAME_LEN: usize = 25;
140
141#[derive(Clone, Eq, DecodeWithMemTracking, PartialEq, Debug)]
146pub struct StringLike<const N: usize>(pub [u8; N]);
147
148impl<const N: usize> TypeInfo for StringLike<N> {
149 type Identity = <&'static str as TypeInfo>::Identity;
150
151 fn type_info() -> Type {
152 <&str as TypeInfo>::type_info()
153 }
154}
155
156impl<const N: usize> MaxEncodedLen for StringLike<N> {
157 fn max_encoded_len() -> usize {
158 <Compact<u32> as MaxEncodedLen>::max_encoded_len().saturating_add(N)
159 }
160}
161
162impl<const N: usize> Encode for StringLike<N> {
163 fn encode(&self) -> Vec<u8> {
164 use codec::Compact;
165 (Compact(N as u32), self.0).encode()
166 }
167}
168
169impl<const N: usize> Decode for StringLike<N> {
170 fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
171 let Compact(size): Compact<u32> = Decode::decode(input)?;
172 if size != N as u32 {
173 return Err("Invalid size".into());
174 }
175
176 let bytes: [u8; N] = Decode::decode(input)?;
177 Ok(Self(bytes))
178 }
179}
180
181pub type TrackInfo<Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN> =
183 TrackDetails<Balance, Moment, [u8; N]>;
184
185pub type ConstTrackInfo<Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN> =
187 TrackDetails<Balance, Moment, StringLike<N>>;
188
189#[derive(
191 Clone, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Eq, PartialEq, Debug,
192)]
193pub struct TrackDetails<Balance, Moment, Name> {
194 pub name: Name,
196 pub max_deciding: u32,
199 pub decision_deposit: Balance,
201 pub prepare_period: Moment,
203 pub decision_period: Moment,
205 pub confirm_period: Moment,
207 pub min_enactment_period: Moment,
209 pub min_approval: Curve,
212 pub min_support: Curve,
215}
216
217#[derive(
219 Clone, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Eq, PartialEq, Debug,
220)]
221pub struct Track<Id, Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN> {
222 pub id: Id,
223 pub info: TrackInfo<Balance, Moment, N>,
224}
225
226pub trait TracksInfo<Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN>
228where
229 Balance: Clone + Debug + Eq + 'static,
230 Moment: Clone + Debug + Eq + 'static,
231{
232 type Id: Copy + Parameter + Ord + PartialOrd + Send + Sync + 'static + MaxEncodedLen;
234
235 type RuntimeOrigin;
237
238 fn tracks() -> impl Iterator<Item = Cow<'static, Track<Self::Id, Balance, Moment, N>>>;
243
244 fn track_for(origin: &Self::RuntimeOrigin) -> Result<Self::Id, ()>;
246
247 fn track_ids() -> impl Iterator<Item = Self::Id> {
249 Self::tracks().map(|x| x.id)
250 }
251
252 fn info(id: Self::Id) -> Option<Cow<'static, TrackInfo<Balance, Moment, N>>> {
254 Self::tracks().find(|x| x.id == id).map(|t| match t {
255 Cow::Borrowed(x) => Cow::Borrowed(&x.info),
256 Cow::Owned(x) => Cow::Owned(x.info),
257 })
258 }
259
260 fn check_integrity() -> Result<(), &'static str> {
262 use core::cmp::Ordering;
263 let mut iter = Self::tracks();
266 let mut last = match iter.next() {
267 Some(ref e) => e.id,
268 None => return Ok(()),
269 };
270 iter.all(|curr| {
271 let curr = curr.as_ref().id;
272 if let Ordering::Greater = last.cmp(&curr) {
273 return false;
274 }
275 last = curr;
276 true
277 })
278 .then_some(())
279 .ok_or("The tracks that were returned by `tracks` were not sorted by `Id`")
280 }
281}
282
283#[derive(
285 Encode,
286 Decode,
287 DecodeWithMemTracking,
288 Clone,
289 PartialEq,
290 Eq,
291 RuntimeDebug,
292 TypeInfo,
293 MaxEncodedLen,
294)]
295pub struct ReferendumStatus<
296 TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
297 RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
298 Moment: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike,
299 Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
300 Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
301 Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
302 AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
303 ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
304> {
305 pub track: TrackId,
307 pub origin: RuntimeOrigin,
309 pub proposal: Call,
311 pub enactment: DispatchTime<Moment>,
313 pub submitted: Moment,
316 pub submission_deposit: Deposit<AccountId, Balance>,
318 pub decision_deposit: Option<Deposit<AccountId, Balance>>,
320 pub deciding: Option<DecidingStatus<Moment>>,
322 pub tally: Tally,
324 pub in_queue: bool,
326 pub alarm: Option<(Moment, ScheduleAddress)>,
328}
329
330#[derive(
332 Encode,
333 Decode,
334 DecodeWithMemTracking,
335 Clone,
336 PartialEq,
337 Eq,
338 RuntimeDebug,
339 TypeInfo,
340 MaxEncodedLen,
341)]
342pub enum ReferendumInfo<
343 TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
344 RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
345 Moment: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike,
346 Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
347 Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
348 Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
349 AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
350 ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
351> {
352 Ongoing(
354 ReferendumStatus<
355 TrackId,
356 RuntimeOrigin,
357 Moment,
358 Call,
359 Balance,
360 Tally,
361 AccountId,
362 ScheduleAddress,
363 >,
364 ),
365 Approved(Moment, Option<Deposit<AccountId, Balance>>, Option<Deposit<AccountId, Balance>>),
367 Rejected(Moment, Option<Deposit<AccountId, Balance>>, Option<Deposit<AccountId, Balance>>),
369 Cancelled(Moment, Option<Deposit<AccountId, Balance>>, Option<Deposit<AccountId, Balance>>),
371 TimedOut(Moment, Option<Deposit<AccountId, Balance>>, Option<Deposit<AccountId, Balance>>),
373 Killed(Moment),
375}
376
377impl<
378 TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
379 RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
380 Moment: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike,
381 Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
382 Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
383 Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
384 AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
385 ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
386 >
387 ReferendumInfo<TrackId, RuntimeOrigin, Moment, Call, Balance, Tally, AccountId, ScheduleAddress>
388{
389 pub fn take_decision_deposit(&mut self) -> Result<Option<Deposit<AccountId, Balance>>, ()> {
392 use ReferendumInfo::*;
393 match self {
394 Ongoing(x) if x.decision_deposit.is_none() => Ok(None),
395 Ongoing(_) => Err(()),
397 Approved(_, _, d) | Rejected(_, _, d) | TimedOut(_, _, d) | Cancelled(_, _, d) =>
398 Ok(d.take()),
399 Killed(_) => Ok(None),
400 }
401 }
402
403 pub fn take_submission_deposit(&mut self) -> Result<Option<Deposit<AccountId, Balance>>, ()> {
407 use ReferendumInfo::*;
408 match self {
409 Approved(_, s, _) | Cancelled(_, s, _) => Ok(s.take()),
411 Ongoing(..) | Rejected(..) | TimedOut(..) | Killed(..) => Err(()),
413 }
414 }
415}
416
417#[derive(Clone, Eq, PartialEq, Encode, Decode, DecodeWithMemTracking, TypeInfo, MaxEncodedLen)]
420#[cfg_attr(not(feature = "std"), derive(RuntimeDebug))]
421pub enum Curve {
422 LinearDecreasing { length: Perbill, floor: Perbill, ceil: Perbill },
425 SteppedDecreasing { begin: Perbill, end: Perbill, step: Perbill, period: Perbill },
430 Reciprocal { factor: FixedI64, x_offset: FixedI64, y_offset: FixedI64 },
432}
433
434const fn pos_quad_solution(a: FixedI64, b: FixedI64, c: FixedI64) -> FixedI64 {
439 const TWO: FixedI64 = FixedI64::from_u32(2);
440 const FOUR: FixedI64 = FixedI64::from_u32(4);
441 b.neg().add(b.mul(b).sub(FOUR.mul(a).mul(c)).sqrt()).div(TWO.mul(a))
442}
443
444impl Curve {
445 pub const fn make_linear(length: u128, period: u128, floor: FixedI64, ceil: FixedI64) -> Curve {
450 let length = FixedI64::from_rational(length, period).into_perbill();
451 let floor = floor.into_perbill();
452 let ceil = ceil.into_perbill();
453 Curve::LinearDecreasing { length, floor, ceil }
454 }
455
456 pub const fn make_reciprocal(
461 delay: u128,
462 period: u128,
463 level: FixedI64,
464 floor: FixedI64,
465 ceil: FixedI64,
466 ) -> Curve {
467 let delay = FixedI64::from_rational(delay, period).into_perbill();
468 let mut bounds = (
469 (
470 FixedI64::from_u32(0),
471 Self::reciprocal_from_parts(FixedI64::from_u32(0), floor, ceil),
472 FixedI64::from_inner(i64::max_value()),
473 ),
474 (
475 FixedI64::from_u32(1),
476 Self::reciprocal_from_parts(FixedI64::from_u32(1), floor, ceil),
477 FixedI64::from_inner(i64::max_value()),
478 ),
479 );
480 const TWO: FixedI64 = FixedI64::from_u32(2);
481 while (bounds.1).0.sub((bounds.0).0).into_inner() > 1 {
482 let factor = (bounds.0).0.add((bounds.1).0).div(TWO);
483 let curve = Self::reciprocal_from_parts(factor, floor, ceil);
484 let curve_level = FixedI64::from_perbill(curve.const_threshold(delay));
485 if curve_level.into_inner() > level.into_inner() {
486 bounds = (bounds.0, (factor, curve, curve_level.sub(level)));
487 } else {
488 bounds = ((factor, curve, level.sub(curve_level)), bounds.1);
489 }
490 }
491 if (bounds.0).2.into_inner() < (bounds.1).2.into_inner() {
492 (bounds.0).1
493 } else {
494 (bounds.1).1
495 }
496 }
497
498 const fn reciprocal_from_parts(factor: FixedI64, floor: FixedI64, ceil: FixedI64) -> Self {
503 let delta = ceil.sub(floor);
504 let x_offset = pos_quad_solution(delta, delta, factor.neg());
505 let y_offset = floor.sub(factor.div(FixedI64::from_u32(1).add(x_offset)));
506 Curve::Reciprocal { factor, x_offset, y_offset }
507 }
508
509 #[cfg(feature = "std")]
511 pub fn info(&self, days: u32, name: impl std::fmt::Display) {
512 let hours = days * 24;
513 println!("Curve {} := {:?}:", name, self);
514 println!(" t + 0h: {:?}", self.threshold(Perbill::zero()));
515 println!(" t + 1h: {:?}", self.threshold(Perbill::from_rational(1, hours)));
516 println!(" t + 2h: {:?}", self.threshold(Perbill::from_rational(2, hours)));
517 println!(" t + 3h: {:?}", self.threshold(Perbill::from_rational(3, hours)));
518 println!(" t + 6h: {:?}", self.threshold(Perbill::from_rational(6, hours)));
519 println!(" t + 12h: {:?}", self.threshold(Perbill::from_rational(12, hours)));
520 println!(" t + 24h: {:?}", self.threshold(Perbill::from_rational(24, hours)));
521 let mut l = 0;
522 for &(n, d) in [(1, 12), (1, 8), (1, 4), (1, 2), (3, 4), (1, 1)].iter() {
523 let t = days * n / d;
524 if t != l {
525 println!(" t + {}d: {:?}", t, self.threshold(Perbill::from_rational(t, days)));
526 l = t;
527 }
528 }
529 let t = |p: Perbill| -> std::string::String {
530 if p.is_one() {
531 "never".into()
532 } else {
533 let minutes = p * (hours * 60);
534 if minutes < 60 {
535 format!("{} minutes", minutes)
536 } else if minutes < 8 * 60 && minutes % 60 != 0 {
537 format!("{} hours {} minutes", minutes / 60, minutes % 60)
538 } else if minutes < 72 * 60 {
539 format!("{} hours", minutes / 60)
540 } else if minutes / 60 % 24 == 0 {
541 format!("{} days", minutes / 60 / 24)
542 } else {
543 format!("{} days {} hours", minutes / 60 / 24, minutes / 60 % 24)
544 }
545 }
546 };
547 if self.delay(Perbill::from_percent(49)) < Perbill::one() {
548 println!(" 30% threshold: {}", t(self.delay(Perbill::from_percent(30))));
549 println!(" 10% threshold: {}", t(self.delay(Perbill::from_percent(10))));
550 println!(" 3% threshold: {}", t(self.delay(Perbill::from_percent(3))));
551 println!(" 1% threshold: {}", t(self.delay(Perbill::from_percent(1))));
552 println!(" 0.1% threshold: {}", t(self.delay(Perbill::from_rational(1u32, 1_000))));
553 println!(" 0.01% threshold: {}", t(self.delay(Perbill::from_rational(1u32, 10_000))));
554 } else {
555 println!(
556 " 99.9% threshold: {}",
557 t(self.delay(Perbill::from_rational(999u32, 1_000)))
558 );
559 println!(" 99% threshold: {}", t(self.delay(Perbill::from_percent(99))));
560 println!(" 95% threshold: {}", t(self.delay(Perbill::from_percent(95))));
561 println!(" 90% threshold: {}", t(self.delay(Perbill::from_percent(90))));
562 println!(" 75% threshold: {}", t(self.delay(Perbill::from_percent(75))));
563 println!(" 60% threshold: {}", t(self.delay(Perbill::from_percent(60))));
564 }
565 }
566
567 pub fn threshold(&self, x: Perbill) -> Perbill {
569 match self {
570 Self::LinearDecreasing { length, floor, ceil } =>
571 *ceil - (x.min(*length).saturating_div(*length, Down) * (*ceil - *floor)),
572 Self::SteppedDecreasing { begin, end, step, period } =>
573 (*begin - (step.int_mul(x.int_div(*period))).min(*begin)).max(*end),
574 Self::Reciprocal { factor, x_offset, y_offset } => factor
575 .checked_rounding_div(FixedI64::from(x) + *x_offset, Low)
576 .map(|yp| (yp + *y_offset).into_clamped_perthing())
577 .unwrap_or_else(Perbill::one),
578 }
579 }
580
581 const fn const_threshold(&self, x: Perbill) -> Perbill {
585 match self {
586 Self::Reciprocal { factor, x_offset, y_offset } => {
587 match factor.checked_rounding_div(FixedI64::from_perbill(x).add(*x_offset), Low) {
588 Some(yp) => (yp.add(*y_offset)).into_perbill(),
589 None => Perbill::one(),
590 }
591 },
592 _ => panic!("const_threshold cannot be used on this curve"),
593 }
594 }
595
596 pub fn delay(&self, y: Perbill) -> Perbill {
611 match self {
612 Self::LinearDecreasing { length, floor, ceil } =>
613 if y < *floor {
614 Perbill::one()
615 } else if y > *ceil {
616 Perbill::zero()
617 } else {
618 (*ceil - y).saturating_div(*ceil - *floor, Up).saturating_mul(*length)
619 },
620 Self::SteppedDecreasing { begin, end, step, period } =>
621 if y < *end {
622 Perbill::one()
623 } else {
624 period.int_mul((*begin - y.min(*begin) + step.less_epsilon()).int_div(*step))
625 },
626 Self::Reciprocal { factor, x_offset, y_offset } => {
627 let y = FixedI64::from(y);
628 let maybe_term = factor.checked_rounding_div(y - *y_offset, High);
629 maybe_term
630 .and_then(|term| (term - *x_offset).try_into_perthing().ok())
631 .unwrap_or_else(Perbill::one)
632 },
633 }
634 }
635
636 pub fn passing(&self, x: Perbill, y: Perbill) -> bool {
638 y >= self.threshold(x)
639 }
640}
641
642#[cfg(feature = "std")]
643impl Debug for Curve {
644 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
645 match self {
646 Self::LinearDecreasing { length, floor, ceil } => {
647 write!(
648 f,
649 "Linear[(0%, {:?}) -> ({:?}, {:?}) -> (100%, {:?})]",
650 ceil, length, floor, floor,
651 )
652 },
653 Self::SteppedDecreasing { begin, end, step, period } => {
654 write!(
655 f,
656 "Stepped[(0%, {:?}) -> (100%, {:?}) by ({:?}, {:?})]",
657 begin, end, period, step,
658 )
659 },
660 Self::Reciprocal { factor, x_offset, y_offset } => {
661 write!(
662 f,
663 "Reciprocal[factor of {:?}, x_offset of {:?}, y_offset of {:?}]",
664 factor, x_offset, y_offset,
665 )
666 },
667 }
668 }
669}
670
671#[cfg(test)]
672mod tests {
673 use super::*;
674 use frame_support::traits::ConstU32;
675 use sp_runtime::{str_array as s, PerThing};
676
677 const fn percent(x: u128) -> FixedI64 {
678 FixedI64::from_rational(x, 100)
679 }
680
681 const TIP_APP: Curve = Curve::make_linear(10, 28, percent(50), percent(100));
682 const TIP_SUP: Curve = Curve::make_reciprocal(1, 28, percent(4), percent(0), percent(50));
683 const ROOT_APP: Curve = Curve::make_reciprocal(4, 28, percent(80), percent(50), percent(100));
684 const ROOT_SUP: Curve = Curve::make_linear(28, 28, percent(0), percent(50));
685 const WHITE_APP: Curve =
686 Curve::make_reciprocal(16, 28 * 24, percent(96), percent(50), percent(100));
687 const WHITE_SUP: Curve = Curve::make_reciprocal(1, 28, percent(20), percent(10), percent(50));
688 const SMALL_APP: Curve = Curve::make_linear(10, 28, percent(50), percent(100));
689 const SMALL_SUP: Curve = Curve::make_reciprocal(8, 28, percent(1), percent(0), percent(50));
690 const MID_APP: Curve = Curve::make_linear(17, 28, percent(50), percent(100));
691 const MID_SUP: Curve = Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50));
692 const BIG_APP: Curve = Curve::make_linear(23, 28, percent(50), percent(100));
693 const BIG_SUP: Curve = Curve::make_reciprocal(16, 28, percent(1), percent(0), percent(50));
694 const HUGE_APP: Curve = Curve::make_linear(28, 28, percent(50), percent(100));
695 const HUGE_SUP: Curve = Curve::make_reciprocal(20, 28, percent(1), percent(0), percent(50));
696 const PARAM_APP: Curve = Curve::make_reciprocal(4, 28, percent(80), percent(50), percent(100));
697 const PARAM_SUP: Curve = Curve::make_reciprocal(7, 28, percent(10), percent(0), percent(50));
698 const ADMIN_APP: Curve = Curve::make_linear(17, 28, percent(50), percent(100));
699 const ADMIN_SUP: Curve = Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50));
700
701 #[test]
704 #[should_panic]
705 fn check_curves() {
706 TIP_APP.info(28u32, "Tip Approval");
707 TIP_SUP.info(28u32, "Tip Support");
708 ROOT_APP.info(28u32, "Root Approval");
709 ROOT_SUP.info(28u32, "Root Support");
710 WHITE_APP.info(28u32, "Whitelist Approval");
711 WHITE_SUP.info(28u32, "Whitelist Support");
712 SMALL_APP.info(28u32, "Small Spend Approval");
713 SMALL_SUP.info(28u32, "Small Spend Support");
714 MID_APP.info(28u32, "Mid Spend Approval");
715 MID_SUP.info(28u32, "Mid Spend Support");
716 BIG_APP.info(28u32, "Big Spend Approval");
717 BIG_SUP.info(28u32, "Big Spend Support");
718 HUGE_APP.info(28u32, "Huge Spend Approval");
719 HUGE_SUP.info(28u32, "Huge Spend Support");
720 PARAM_APP.info(28u32, "Mid-tier Parameter Change Approval");
721 PARAM_SUP.info(28u32, "Mid-tier Parameter Change Support");
722 ADMIN_APP.info(28u32, "Admin (e.g. Cancel Slash) Approval");
723 ADMIN_SUP.info(28u32, "Admin (e.g. Cancel Slash) Support");
724 assert!(false);
725 }
726
727 #[test]
728 fn insert_sorted_works() {
729 let mut b: BoundedVec<u32, ConstU32<6>> = vec![20, 30, 40].try_into().unwrap();
730 assert!(b.insert_sorted_by_key(10, |&x| x));
731 assert_eq!(&b[..], &[10, 20, 30, 40][..]);
732
733 assert!(b.insert_sorted_by_key(60, |&x| x));
734 assert_eq!(&b[..], &[10, 20, 30, 40, 60][..]);
735
736 assert!(b.insert_sorted_by_key(50, |&x| x));
737 assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]);
738
739 assert!(!b.insert_sorted_by_key(9, |&x| x));
740 assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]);
741
742 assert!(b.insert_sorted_by_key(11, |&x| x));
743 assert_eq!(&b[..], &[11, 20, 30, 40, 50, 60][..]);
744
745 assert!(b.insert_sorted_by_key(21, |&x| x));
746 assert_eq!(&b[..], &[20, 21, 30, 40, 50, 60][..]);
747
748 assert!(b.insert_sorted_by_key(61, |&x| x));
749 assert_eq!(&b[..], &[21, 30, 40, 50, 60, 61][..]);
750
751 assert!(b.insert_sorted_by_key(51, |&x| x));
752 assert_eq!(&b[..], &[30, 40, 50, 51, 60, 61][..]);
753 }
754
755 #[test]
756 fn translated_reciprocal_works() {
757 let c: Curve = Curve::Reciprocal {
758 factor: FixedI64::from_float(0.03125),
759 x_offset: FixedI64::from_float(0.0363306838226),
760 y_offset: FixedI64::from_float(0.139845532427),
761 };
762 c.info(28u32, "Test");
763
764 for i in 0..9_696_969u32 {
765 let query = Perbill::from_rational(i, 9_696_969);
766 let delay_needed = c.delay(query);
768 assert!(delay_needed.is_one() || c.passing(delay_needed, query));
770 }
771 }
772
773 #[test]
774 fn stepped_decreasing_works() {
775 fn pc(x: u32) -> Perbill {
776 Perbill::from_percent(x)
777 }
778
779 let c =
780 Curve::SteppedDecreasing { begin: pc(80), end: pc(30), step: pc(10), period: pc(15) };
781
782 for i in 0..9_696_969u32 {
783 let query = Perbill::from_rational(i, 9_696_969);
784 let delay_needed = c.delay(query);
786 assert!(delay_needed.is_one() || c.passing(delay_needed, query));
788 }
789
790 assert_eq!(c.threshold(pc(0)), pc(80));
791 assert_eq!(c.threshold(pc(15).less_epsilon()), pc(80));
792 assert_eq!(c.threshold(pc(15)), pc(70));
793 assert_eq!(c.threshold(pc(30).less_epsilon()), pc(70));
794 assert_eq!(c.threshold(pc(30)), pc(60));
795 assert_eq!(c.threshold(pc(45).less_epsilon()), pc(60));
796 assert_eq!(c.threshold(pc(45)), pc(50));
797 assert_eq!(c.threshold(pc(60).less_epsilon()), pc(50));
798 assert_eq!(c.threshold(pc(60)), pc(40));
799 assert_eq!(c.threshold(pc(75).less_epsilon()), pc(40));
800 assert_eq!(c.threshold(pc(75)), pc(30));
801 assert_eq!(c.threshold(pc(100)), pc(30));
802
803 assert_eq!(c.delay(pc(100)), pc(0));
804 assert_eq!(c.delay(pc(80)), pc(0));
805 assert_eq!(c.delay(pc(80).less_epsilon()), pc(15));
806 assert_eq!(c.delay(pc(70)), pc(15));
807 assert_eq!(c.delay(pc(70).less_epsilon()), pc(30));
808 assert_eq!(c.delay(pc(60)), pc(30));
809 assert_eq!(c.delay(pc(60).less_epsilon()), pc(45));
810 assert_eq!(c.delay(pc(50)), pc(45));
811 assert_eq!(c.delay(pc(50).less_epsilon()), pc(60));
812 assert_eq!(c.delay(pc(40)), pc(60));
813 assert_eq!(c.delay(pc(40).less_epsilon()), pc(75));
814 assert_eq!(c.delay(pc(30)), pc(75));
815 assert_eq!(c.delay(pc(30).less_epsilon()), pc(100));
816 assert_eq!(c.delay(pc(0)), pc(100));
817 }
818
819 #[test]
820 fn tracks_integrity_check_detects_unsorted() {
821 use crate::mock::RuntimeOrigin;
822
823 pub struct BadTracksInfo;
824 impl TracksInfo<u64, u64> for BadTracksInfo {
825 type Id = u8;
826 type RuntimeOrigin = <RuntimeOrigin as OriginTrait>::PalletsOrigin;
827 fn tracks() -> impl Iterator<Item = Cow<'static, Track<Self::Id, u64, u64>>> {
828 static DATA: [Track<u8, u64, u64>; 2] = [
829 Track {
830 id: 1u8,
831 info: TrackInfo {
832 name: s("root"),
833 max_deciding: 1,
834 decision_deposit: 10,
835 prepare_period: 4,
836 decision_period: 4,
837 confirm_period: 2,
838 min_enactment_period: 4,
839 min_approval: Curve::LinearDecreasing {
840 length: Perbill::from_percent(100),
841 floor: Perbill::from_percent(50),
842 ceil: Perbill::from_percent(100),
843 },
844 min_support: Curve::LinearDecreasing {
845 length: Perbill::from_percent(100),
846 floor: Perbill::from_percent(0),
847 ceil: Perbill::from_percent(100),
848 },
849 },
850 },
851 Track {
852 id: 0u8,
853 info: TrackInfo {
854 name: s("none"),
855 max_deciding: 3,
856 decision_deposit: 1,
857 prepare_period: 2,
858 decision_period: 2,
859 confirm_period: 1,
860 min_enactment_period: 2,
861 min_approval: Curve::LinearDecreasing {
862 length: Perbill::from_percent(100),
863 floor: Perbill::from_percent(95),
864 ceil: Perbill::from_percent(100),
865 },
866 min_support: Curve::LinearDecreasing {
867 length: Perbill::from_percent(100),
868 floor: Perbill::from_percent(90),
869 ceil: Perbill::from_percent(100),
870 },
871 },
872 },
873 ];
874 DATA.iter().map(Cow::Borrowed)
875 }
876 fn track_for(_: &Self::RuntimeOrigin) -> Result<Self::Id, ()> {
877 unimplemented!()
878 }
879 }
880
881 assert_eq!(
882 BadTracksInfo::check_integrity(),
883 Err("The tracks that were returned by `tracks` were not sorted by `Id`")
884 );
885 }
886
887 #[test]
888 fn encoding_and_decoding_of_string_like_structure_works() {
889 let string_like = StringLike::<13>(*b"hello, world!");
890 let encoded: Vec<u8> = string_like.encode();
891
892 let decoded_as_vec: Vec<u8> =
893 Decode::decode(&mut &encoded.clone()[..]).expect("decoding as Vec<u8> should work");
894 assert_eq!(decoded_as_vec.len(), 13);
895 let decoded_as_str: alloc::string::String =
896 Decode::decode(&mut &encoded.clone()[..]).expect("decoding as str should work");
897 assert_eq!(decoded_as_str.len(), 13);
898 let decoded_as_string_like: StringLike<13> =
899 Decode::decode(&mut &encoded.clone()[..]).expect("decoding as StringLike should work");
900 assert_eq!(decoded_as_string_like.0.len(), 13);
901 }
902}