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};
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, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen,
105)]
106pub struct DecidingStatus<BlockNumber> {
107 pub since: BlockNumber,
110 pub confirming: Option<BlockNumber>,
113}
114
115#[derive(
116 Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen,
117)]
118pub struct Deposit<AccountId, Balance> {
119 pub who: AccountId,
120 pub amount: Balance,
121}
122
123pub const DEFAULT_MAX_TRACK_NAME_LEN: usize = 25;
124
125#[derive(Clone, Eq, DecodeWithMemTracking, PartialEq, Debug)]
130pub struct StringLike<const N: usize>(pub [u8; N]);
131
132impl<const N: usize> TypeInfo for StringLike<N> {
133 type Identity = <&'static str as TypeInfo>::Identity;
134
135 fn type_info() -> Type {
136 <&str as TypeInfo>::type_info()
137 }
138}
139
140impl<const N: usize> MaxEncodedLen for StringLike<N> {
141 fn max_encoded_len() -> usize {
142 <Compact<u32> as MaxEncodedLen>::max_encoded_len().saturating_add(N)
143 }
144}
145
146impl<const N: usize> Encode for StringLike<N> {
147 fn encode(&self) -> Vec<u8> {
148 use codec::Compact;
149 (Compact(N as u32), self.0).encode()
150 }
151}
152
153impl<const N: usize> Decode for StringLike<N> {
154 fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
155 let Compact(size): Compact<u32> = Decode::decode(input)?;
156 if size != N as u32 {
157 return Err("Invalid size".into());
158 }
159
160 let bytes: [u8; N] = Decode::decode(input)?;
161 Ok(Self(bytes))
162 }
163}
164
165pub type TrackInfo<Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN> =
167 TrackDetails<Balance, Moment, [u8; N]>;
168
169pub type ConstTrackInfo<Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN> =
171 TrackDetails<Balance, Moment, StringLike<N>>;
172
173#[derive(
175 Clone, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Eq, PartialEq, Debug,
176)]
177pub struct TrackDetails<Balance, Moment, Name> {
178 pub name: Name,
180 pub max_deciding: u32,
183 pub decision_deposit: Balance,
185 pub prepare_period: Moment,
187 pub decision_period: Moment,
189 pub confirm_period: Moment,
191 pub min_enactment_period: Moment,
193 pub min_approval: Curve,
196 pub min_support: Curve,
199}
200
201#[derive(
203 Clone, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Eq, PartialEq, Debug,
204)]
205pub struct Track<Id, Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN> {
206 pub id: Id,
207 pub info: TrackInfo<Balance, Moment, N>,
208}
209
210pub trait TracksInfo<Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN>
212where
213 Balance: Clone + Debug + Eq + 'static,
214 Moment: Clone + Debug + Eq + 'static,
215{
216 type Id: Copy + Parameter + Ord + PartialOrd + Send + Sync + 'static + MaxEncodedLen;
218
219 type RuntimeOrigin;
221
222 fn tracks() -> impl Iterator<Item = Cow<'static, Track<Self::Id, Balance, Moment, N>>>;
227
228 fn track_for(origin: &Self::RuntimeOrigin) -> Result<Self::Id, ()>;
230
231 fn track_ids() -> impl Iterator<Item = Self::Id> {
233 Self::tracks().map(|x| x.id)
234 }
235
236 fn info(id: Self::Id) -> Option<Cow<'static, TrackInfo<Balance, Moment, N>>> {
238 Self::tracks().find(|x| x.id == id).map(|t| match t {
239 Cow::Borrowed(x) => Cow::Borrowed(&x.info),
240 Cow::Owned(x) => Cow::Owned(x.info),
241 })
242 }
243
244 fn check_integrity() -> Result<(), &'static str> {
246 use core::cmp::Ordering;
247 let mut iter = Self::tracks();
250 let mut last = match iter.next() {
251 Some(ref e) => e.id,
252 None => return Ok(()),
253 };
254 iter.all(|curr| {
255 let curr = curr.as_ref().id;
256 if let Ordering::Greater = last.cmp(&curr) {
257 return false;
258 }
259 last = curr;
260 true
261 })
262 .then_some(())
263 .ok_or("The tracks that were returned by `tracks` were not sorted by `Id`")
264 }
265}
266
267#[derive(
269 Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen,
270)]
271pub struct ReferendumStatus<
272 TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
273 RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
274 Moment: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike,
275 Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
276 Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
277 Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
278 AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
279 ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
280> {
281 pub track: TrackId,
283 pub origin: RuntimeOrigin,
285 pub proposal: Call,
287 pub enactment: DispatchTime<Moment>,
289 pub submitted: Moment,
292 pub submission_deposit: Deposit<AccountId, Balance>,
294 pub decision_deposit: Option<Deposit<AccountId, Balance>>,
296 pub deciding: Option<DecidingStatus<Moment>>,
298 pub tally: Tally,
300 pub in_queue: bool,
302 pub alarm: Option<(Moment, ScheduleAddress)>,
304}
305
306#[derive(
308 Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen,
309)]
310pub enum ReferendumInfo<
311 TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
312 RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
313 Moment: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike,
314 Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
315 Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
316 Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
317 AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
318 ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
319> {
320 Ongoing(
322 ReferendumStatus<
323 TrackId,
324 RuntimeOrigin,
325 Moment,
326 Call,
327 Balance,
328 Tally,
329 AccountId,
330 ScheduleAddress,
331 >,
332 ),
333 Approved(Moment, Option<Deposit<AccountId, Balance>>, Option<Deposit<AccountId, Balance>>),
335 Rejected(Moment, Option<Deposit<AccountId, Balance>>, Option<Deposit<AccountId, Balance>>),
337 Cancelled(Moment, Option<Deposit<AccountId, Balance>>, Option<Deposit<AccountId, Balance>>),
339 TimedOut(Moment, Option<Deposit<AccountId, Balance>>, Option<Deposit<AccountId, Balance>>),
341 Killed(Moment),
343}
344
345impl<
346 TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
347 RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
348 Moment: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike,
349 Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
350 Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
351 Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
352 AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
353 ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
354 >
355 ReferendumInfo<TrackId, RuntimeOrigin, Moment, Call, Balance, Tally, AccountId, ScheduleAddress>
356{
357 pub fn take_decision_deposit(&mut self) -> Result<Option<Deposit<AccountId, Balance>>, ()> {
360 use ReferendumInfo::*;
361 match self {
362 Ongoing(x) if x.decision_deposit.is_none() => Ok(None),
363 Ongoing(_) => Err(()),
365 Approved(_, _, d) | Rejected(_, _, d) | TimedOut(_, _, d) | Cancelled(_, _, d) => {
366 Ok(d.take())
367 },
368 Killed(_) => Ok(None),
369 }
370 }
371
372 pub fn take_submission_deposit(&mut self) -> Result<Option<Deposit<AccountId, Balance>>, ()> {
376 use ReferendumInfo::*;
377 match self {
378 Approved(_, s, _) | Cancelled(_, s, _) => Ok(s.take()),
380 Ongoing(..) | Rejected(..) | TimedOut(..) | Killed(..) => Err(()),
382 }
383 }
384}
385
386#[derive(Clone, Eq, PartialEq, Encode, Decode, DecodeWithMemTracking, TypeInfo, MaxEncodedLen)]
389#[cfg_attr(not(feature = "std"), derive(Debug))]
390pub enum Curve {
391 LinearDecreasing { length: Perbill, floor: Perbill, ceil: Perbill },
394 SteppedDecreasing { begin: Perbill, end: Perbill, step: Perbill, period: Perbill },
399 Reciprocal { factor: FixedI64, x_offset: FixedI64, y_offset: FixedI64 },
401}
402
403const fn pos_quad_solution(a: FixedI64, b: FixedI64, c: FixedI64) -> FixedI64 {
408 const TWO: FixedI64 = FixedI64::from_u32(2);
409 const FOUR: FixedI64 = FixedI64::from_u32(4);
410 b.neg().add(b.mul(b).sub(FOUR.mul(a).mul(c)).sqrt()).div(TWO.mul(a))
411}
412
413impl Curve {
414 pub const fn make_linear(length: u128, period: u128, floor: FixedI64, ceil: FixedI64) -> Curve {
419 let length = FixedI64::from_rational(length, period).into_perbill();
420 let floor = floor.into_perbill();
421 let ceil = ceil.into_perbill();
422 Curve::LinearDecreasing { length, floor, ceil }
423 }
424
425 pub const fn make_reciprocal(
430 delay: u128,
431 period: u128,
432 level: FixedI64,
433 floor: FixedI64,
434 ceil: FixedI64,
435 ) -> Curve {
436 let delay = FixedI64::from_rational(delay, period).into_perbill();
437 let mut bounds = (
438 (
439 FixedI64::from_u32(0),
440 Self::reciprocal_from_parts(FixedI64::from_u32(0), floor, ceil),
441 FixedI64::from_inner(i64::max_value()),
442 ),
443 (
444 FixedI64::from_u32(1),
445 Self::reciprocal_from_parts(FixedI64::from_u32(1), floor, ceil),
446 FixedI64::from_inner(i64::max_value()),
447 ),
448 );
449 const TWO: FixedI64 = FixedI64::from_u32(2);
450 while (bounds.1).0.sub((bounds.0).0).into_inner() > 1 {
451 let factor = (bounds.0).0.add((bounds.1).0).div(TWO);
452 let curve = Self::reciprocal_from_parts(factor, floor, ceil);
453 let curve_level = FixedI64::from_perbill(curve.const_threshold(delay));
454 if curve_level.into_inner() > level.into_inner() {
455 bounds = (bounds.0, (factor, curve, curve_level.sub(level)));
456 } else {
457 bounds = ((factor, curve, level.sub(curve_level)), bounds.1);
458 }
459 }
460 if (bounds.0).2.into_inner() < (bounds.1).2.into_inner() {
461 (bounds.0).1
462 } else {
463 (bounds.1).1
464 }
465 }
466
467 const fn reciprocal_from_parts(factor: FixedI64, floor: FixedI64, ceil: FixedI64) -> Self {
472 let delta = ceil.sub(floor);
473 let x_offset = pos_quad_solution(delta, delta, factor.neg());
474 let y_offset = floor.sub(factor.div(FixedI64::from_u32(1).add(x_offset)));
475 Curve::Reciprocal { factor, x_offset, y_offset }
476 }
477
478 #[cfg(feature = "std")]
480 pub fn info(&self, days: u32, name: impl std::fmt::Display) {
481 let hours = days * 24;
482 println!("Curve {} := {:?}:", name, self);
483 println!(" t + 0h: {:?}", self.threshold(Perbill::zero()));
484 println!(" t + 1h: {:?}", self.threshold(Perbill::from_rational(1, hours)));
485 println!(" t + 2h: {:?}", self.threshold(Perbill::from_rational(2, hours)));
486 println!(" t + 3h: {:?}", self.threshold(Perbill::from_rational(3, hours)));
487 println!(" t + 6h: {:?}", self.threshold(Perbill::from_rational(6, hours)));
488 println!(" t + 12h: {:?}", self.threshold(Perbill::from_rational(12, hours)));
489 println!(" t + 24h: {:?}", self.threshold(Perbill::from_rational(24, hours)));
490 let mut l = 0;
491 for &(n, d) in [(1, 12), (1, 8), (1, 4), (1, 2), (3, 4), (1, 1)].iter() {
492 let t = days * n / d;
493 if t != l {
494 println!(" t + {}d: {:?}", t, self.threshold(Perbill::from_rational(t, days)));
495 l = t;
496 }
497 }
498 let t = |p: Perbill| -> std::string::String {
499 if p.is_one() {
500 "never".into()
501 } else {
502 let minutes = p * (hours * 60);
503 if minutes < 60 {
504 format!("{} minutes", minutes)
505 } else if minutes < 8 * 60 && !minutes.is_multiple_of(60) {
506 format!("{} hours {} minutes", minutes / 60, minutes % 60)
507 } else if minutes < 72 * 60 {
508 format!("{} hours", minutes / 60)
509 } else if (minutes / 60).is_multiple_of(24) {
510 format!("{} days", minutes / 60 / 24)
511 } else {
512 format!("{} days {} hours", minutes / 60 / 24, minutes / 60 % 24)
513 }
514 }
515 };
516 if self.delay(Perbill::from_percent(49)) < Perbill::one() {
517 println!(" 30% threshold: {}", t(self.delay(Perbill::from_percent(30))));
518 println!(" 10% threshold: {}", t(self.delay(Perbill::from_percent(10))));
519 println!(" 3% threshold: {}", t(self.delay(Perbill::from_percent(3))));
520 println!(" 1% threshold: {}", t(self.delay(Perbill::from_percent(1))));
521 println!(" 0.1% threshold: {}", t(self.delay(Perbill::from_rational(1u32, 1_000))));
522 println!(" 0.01% threshold: {}", t(self.delay(Perbill::from_rational(1u32, 10_000))));
523 } else {
524 println!(
525 " 99.9% threshold: {}",
526 t(self.delay(Perbill::from_rational(999u32, 1_000)))
527 );
528 println!(" 99% threshold: {}", t(self.delay(Perbill::from_percent(99))));
529 println!(" 95% threshold: {}", t(self.delay(Perbill::from_percent(95))));
530 println!(" 90% threshold: {}", t(self.delay(Perbill::from_percent(90))));
531 println!(" 75% threshold: {}", t(self.delay(Perbill::from_percent(75))));
532 println!(" 60% threshold: {}", t(self.delay(Perbill::from_percent(60))));
533 }
534 }
535
536 pub fn threshold(&self, x: Perbill) -> Perbill {
538 match self {
539 Self::LinearDecreasing { length, floor, ceil } => {
540 *ceil - (x.min(*length).saturating_div(*length, Down) * (*ceil - *floor))
541 },
542 Self::SteppedDecreasing { begin, end, step, period } => {
543 (*begin - (step.int_mul(x.int_div(*period))).min(*begin)).max(*end)
544 },
545 Self::Reciprocal { factor, x_offset, y_offset } => factor
546 .checked_rounding_div(FixedI64::from(x) + *x_offset, Low)
547 .map(|yp| (yp + *y_offset).into_clamped_perthing())
548 .unwrap_or_else(Perbill::one),
549 }
550 }
551
552 const fn const_threshold(&self, x: Perbill) -> Perbill {
556 match self {
557 Self::Reciprocal { factor, x_offset, y_offset } => {
558 match factor.checked_rounding_div(FixedI64::from_perbill(x).add(*x_offset), Low) {
559 Some(yp) => (yp.add(*y_offset)).into_perbill(),
560 None => Perbill::one(),
561 }
562 },
563 _ => panic!("const_threshold cannot be used on this curve"),
564 }
565 }
566
567 pub fn delay(&self, y: Perbill) -> Perbill {
582 match self {
583 Self::LinearDecreasing { length, floor, ceil } => {
584 if y < *floor {
585 Perbill::one()
586 } else if y > *ceil {
587 Perbill::zero()
588 } else {
589 (*ceil - y).saturating_div(*ceil - *floor, Up).saturating_mul(*length)
590 }
591 },
592 Self::SteppedDecreasing { begin, end, step, period } => {
593 if y < *end {
594 Perbill::one()
595 } else {
596 period.int_mul((*begin - y.min(*begin) + step.less_epsilon()).int_div(*step))
597 }
598 },
599 Self::Reciprocal { factor, x_offset, y_offset } => {
600 let y = FixedI64::from(y);
601 let maybe_term = factor.checked_rounding_div(y - *y_offset, High);
602 maybe_term
603 .and_then(|term| (term - *x_offset).try_into_perthing().ok())
604 .unwrap_or_else(Perbill::one)
605 },
606 }
607 }
608
609 pub fn passing(&self, x: Perbill, y: Perbill) -> bool {
611 y >= self.threshold(x)
612 }
613}
614
615#[cfg(feature = "std")]
616impl Debug for Curve {
617 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
618 match self {
619 Self::LinearDecreasing { length, floor, ceil } => {
620 write!(
621 f,
622 "Linear[(0%, {:?}) -> ({:?}, {:?}) -> (100%, {:?})]",
623 ceil, length, floor, floor,
624 )
625 },
626 Self::SteppedDecreasing { begin, end, step, period } => {
627 write!(
628 f,
629 "Stepped[(0%, {:?}) -> (100%, {:?}) by ({:?}, {:?})]",
630 begin, end, period, step,
631 )
632 },
633 Self::Reciprocal { factor, x_offset, y_offset } => {
634 write!(
635 f,
636 "Reciprocal[factor of {:?}, x_offset of {:?}, y_offset of {:?}]",
637 factor, x_offset, y_offset,
638 )
639 },
640 }
641 }
642}
643
644#[cfg(test)]
645mod tests {
646 use super::*;
647 use frame_support::traits::ConstU32;
648 use sp_runtime::{str_array as s, PerThing};
649
650 const fn percent(x: u128) -> FixedI64 {
651 FixedI64::from_rational(x, 100)
652 }
653
654 const TIP_APP: Curve = Curve::make_linear(10, 28, percent(50), percent(100));
655 const TIP_SUP: Curve = Curve::make_reciprocal(1, 28, percent(4), percent(0), percent(50));
656 const ROOT_APP: Curve = Curve::make_reciprocal(4, 28, percent(80), percent(50), percent(100));
657 const ROOT_SUP: Curve = Curve::make_linear(28, 28, percent(0), percent(50));
658 const WHITE_APP: Curve =
659 Curve::make_reciprocal(16, 28 * 24, percent(96), percent(50), percent(100));
660 const WHITE_SUP: Curve = Curve::make_reciprocal(1, 28, percent(20), percent(10), percent(50));
661 const SMALL_APP: Curve = Curve::make_linear(10, 28, percent(50), percent(100));
662 const SMALL_SUP: Curve = Curve::make_reciprocal(8, 28, percent(1), percent(0), percent(50));
663 const MID_APP: Curve = Curve::make_linear(17, 28, percent(50), percent(100));
664 const MID_SUP: Curve = Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50));
665 const BIG_APP: Curve = Curve::make_linear(23, 28, percent(50), percent(100));
666 const BIG_SUP: Curve = Curve::make_reciprocal(16, 28, percent(1), percent(0), percent(50));
667 const HUGE_APP: Curve = Curve::make_linear(28, 28, percent(50), percent(100));
668 const HUGE_SUP: Curve = Curve::make_reciprocal(20, 28, percent(1), percent(0), percent(50));
669 const PARAM_APP: Curve = Curve::make_reciprocal(4, 28, percent(80), percent(50), percent(100));
670 const PARAM_SUP: Curve = Curve::make_reciprocal(7, 28, percent(10), percent(0), percent(50));
671 const ADMIN_APP: Curve = Curve::make_linear(17, 28, percent(50), percent(100));
672 const ADMIN_SUP: Curve = Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50));
673
674 #[test]
677 #[should_panic]
678 fn check_curves() {
679 TIP_APP.info(28u32, "Tip Approval");
680 TIP_SUP.info(28u32, "Tip Support");
681 ROOT_APP.info(28u32, "Root Approval");
682 ROOT_SUP.info(28u32, "Root Support");
683 WHITE_APP.info(28u32, "Whitelist Approval");
684 WHITE_SUP.info(28u32, "Whitelist Support");
685 SMALL_APP.info(28u32, "Small Spend Approval");
686 SMALL_SUP.info(28u32, "Small Spend Support");
687 MID_APP.info(28u32, "Mid Spend Approval");
688 MID_SUP.info(28u32, "Mid Spend Support");
689 BIG_APP.info(28u32, "Big Spend Approval");
690 BIG_SUP.info(28u32, "Big Spend Support");
691 HUGE_APP.info(28u32, "Huge Spend Approval");
692 HUGE_SUP.info(28u32, "Huge Spend Support");
693 PARAM_APP.info(28u32, "Mid-tier Parameter Change Approval");
694 PARAM_SUP.info(28u32, "Mid-tier Parameter Change Support");
695 ADMIN_APP.info(28u32, "Admin (e.g. Cancel Slash) Approval");
696 ADMIN_SUP.info(28u32, "Admin (e.g. Cancel Slash) Support");
697 assert!(false);
698 }
699
700 #[test]
701 fn insert_sorted_works() {
702 let mut b: BoundedVec<u32, ConstU32<6>> = vec![20, 30, 40].try_into().unwrap();
703 assert!(b.insert_sorted_by_key(10, |&x| x));
704 assert_eq!(&b[..], &[10, 20, 30, 40][..]);
705
706 assert!(b.insert_sorted_by_key(60, |&x| x));
707 assert_eq!(&b[..], &[10, 20, 30, 40, 60][..]);
708
709 assert!(b.insert_sorted_by_key(50, |&x| x));
710 assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]);
711
712 assert!(!b.insert_sorted_by_key(9, |&x| x));
713 assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]);
714
715 assert!(b.insert_sorted_by_key(11, |&x| x));
716 assert_eq!(&b[..], &[11, 20, 30, 40, 50, 60][..]);
717
718 assert!(b.insert_sorted_by_key(21, |&x| x));
719 assert_eq!(&b[..], &[20, 21, 30, 40, 50, 60][..]);
720
721 assert!(b.insert_sorted_by_key(61, |&x| x));
722 assert_eq!(&b[..], &[21, 30, 40, 50, 60, 61][..]);
723
724 assert!(b.insert_sorted_by_key(51, |&x| x));
725 assert_eq!(&b[..], &[30, 40, 50, 51, 60, 61][..]);
726 }
727
728 #[test]
729 fn translated_reciprocal_works() {
730 let c: Curve = Curve::Reciprocal {
731 factor: FixedI64::from_float(0.03125),
732 x_offset: FixedI64::from_float(0.0363306838226),
733 y_offset: FixedI64::from_float(0.139845532427),
734 };
735 c.info(28u32, "Test");
736
737 for i in 0..9_696_969u32 {
738 let query = Perbill::from_rational(i, 9_696_969);
739 let delay_needed = c.delay(query);
741 assert!(delay_needed.is_one() || c.passing(delay_needed, query));
743 }
744 }
745
746 #[test]
747 fn stepped_decreasing_works() {
748 fn pc(x: u32) -> Perbill {
749 Perbill::from_percent(x)
750 }
751
752 let c =
753 Curve::SteppedDecreasing { begin: pc(80), end: pc(30), step: pc(10), period: pc(15) };
754
755 for i in 0..9_696_969u32 {
756 let query = Perbill::from_rational(i, 9_696_969);
757 let delay_needed = c.delay(query);
759 assert!(delay_needed.is_one() || c.passing(delay_needed, query));
761 }
762
763 assert_eq!(c.threshold(pc(0)), pc(80));
764 assert_eq!(c.threshold(pc(15).less_epsilon()), pc(80));
765 assert_eq!(c.threshold(pc(15)), pc(70));
766 assert_eq!(c.threshold(pc(30).less_epsilon()), pc(70));
767 assert_eq!(c.threshold(pc(30)), pc(60));
768 assert_eq!(c.threshold(pc(45).less_epsilon()), pc(60));
769 assert_eq!(c.threshold(pc(45)), pc(50));
770 assert_eq!(c.threshold(pc(60).less_epsilon()), pc(50));
771 assert_eq!(c.threshold(pc(60)), pc(40));
772 assert_eq!(c.threshold(pc(75).less_epsilon()), pc(40));
773 assert_eq!(c.threshold(pc(75)), pc(30));
774 assert_eq!(c.threshold(pc(100)), pc(30));
775
776 assert_eq!(c.delay(pc(100)), pc(0));
777 assert_eq!(c.delay(pc(80)), pc(0));
778 assert_eq!(c.delay(pc(80).less_epsilon()), pc(15));
779 assert_eq!(c.delay(pc(70)), pc(15));
780 assert_eq!(c.delay(pc(70).less_epsilon()), pc(30));
781 assert_eq!(c.delay(pc(60)), pc(30));
782 assert_eq!(c.delay(pc(60).less_epsilon()), pc(45));
783 assert_eq!(c.delay(pc(50)), pc(45));
784 assert_eq!(c.delay(pc(50).less_epsilon()), pc(60));
785 assert_eq!(c.delay(pc(40)), pc(60));
786 assert_eq!(c.delay(pc(40).less_epsilon()), pc(75));
787 assert_eq!(c.delay(pc(30)), pc(75));
788 assert_eq!(c.delay(pc(30).less_epsilon()), pc(100));
789 assert_eq!(c.delay(pc(0)), pc(100));
790 }
791
792 #[test]
793 fn tracks_integrity_check_detects_unsorted() {
794 use crate::mock::RuntimeOrigin;
795
796 pub struct BadTracksInfo;
797 impl TracksInfo<u64, u64> for BadTracksInfo {
798 type Id = u8;
799 type RuntimeOrigin = <RuntimeOrigin as OriginTrait>::PalletsOrigin;
800 fn tracks() -> impl Iterator<Item = Cow<'static, Track<Self::Id, u64, u64>>> {
801 static DATA: [Track<u8, u64, u64>; 2] = [
802 Track {
803 id: 1u8,
804 info: TrackInfo {
805 name: s("root"),
806 max_deciding: 1,
807 decision_deposit: 10,
808 prepare_period: 4,
809 decision_period: 4,
810 confirm_period: 2,
811 min_enactment_period: 4,
812 min_approval: Curve::LinearDecreasing {
813 length: Perbill::from_percent(100),
814 floor: Perbill::from_percent(50),
815 ceil: Perbill::from_percent(100),
816 },
817 min_support: Curve::LinearDecreasing {
818 length: Perbill::from_percent(100),
819 floor: Perbill::from_percent(0),
820 ceil: Perbill::from_percent(100),
821 },
822 },
823 },
824 Track {
825 id: 0u8,
826 info: TrackInfo {
827 name: s("none"),
828 max_deciding: 3,
829 decision_deposit: 1,
830 prepare_period: 2,
831 decision_period: 2,
832 confirm_period: 1,
833 min_enactment_period: 2,
834 min_approval: Curve::LinearDecreasing {
835 length: Perbill::from_percent(100),
836 floor: Perbill::from_percent(95),
837 ceil: Perbill::from_percent(100),
838 },
839 min_support: Curve::LinearDecreasing {
840 length: Perbill::from_percent(100),
841 floor: Perbill::from_percent(90),
842 ceil: Perbill::from_percent(100),
843 },
844 },
845 },
846 ];
847 DATA.iter().map(Cow::Borrowed)
848 }
849 fn track_for(_: &Self::RuntimeOrigin) -> Result<Self::Id, ()> {
850 unimplemented!()
851 }
852 }
853
854 assert_eq!(
855 BadTracksInfo::check_integrity(),
856 Err("The tracks that were returned by `tracks` were not sorted by `Id`")
857 );
858 }
859
860 #[test]
861 fn encoding_and_decoding_of_string_like_structure_works() {
862 let string_like = StringLike::<13>(*b"hello, world!");
863 let encoded: Vec<u8> = string_like.encode();
864
865 let decoded_as_vec: Vec<u8> =
866 Decode::decode(&mut &encoded.clone()[..]).expect("decoding as Vec<u8> should work");
867 assert_eq!(decoded_as_vec.len(), 13);
868 let decoded_as_str: alloc::string::String =
869 Decode::decode(&mut &encoded.clone()[..]).expect("decoding as str should work");
870 assert_eq!(decoded_as_str.len(), 13);
871 let decoded_as_string_like: StringLike<13> =
872 Decode::decode(&mut &encoded.clone()[..]).expect("decoding as StringLike should work");
873 assert_eq!(decoded_as_string_like.0.len(), 13);
874 }
875}