moonbase_runtime/governance/
tracks.rs

1// Copyright 2022 Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
16
17//! Track configurations for governance.
18
19use super::*;
20use crate::currency::{KILOUNIT, SUPPLY_FACTOR, UNIT};
21use core::str::from_utf8;
22use sp_std::str::FromStr;
23
24const fn percent(x: i32) -> sp_runtime::FixedI64 {
25	sp_runtime::FixedI64::from_rational(x as u128, 100)
26}
27const fn permill(x: i32) -> sp_runtime::FixedI64 {
28	sp_runtime::FixedI64::from_rational(x as u128, 1000)
29}
30
31use pallet_referenda::{Curve, Track};
32use sp_runtime::str_array as s;
33
34const TRACKS_DATA: [Track<u16, Balance, BlockNumber>; 6] = [
35	Track {
36		id: 0,
37		info: pallet_referenda::TrackInfo {
38			// Name of this track.
39			name: s("root"),
40			// A limit for the number of referenda on this track that can be being decided at once.
41			// For Root origin this should generally be just one.
42			max_deciding: 5,
43			// Amount that must be placed on deposit before a decision can be made.
44			decision_deposit: 100 * KILOUNIT * SUPPLY_FACTOR,
45			// Amount of time this must be submitted for before a decision can be made.
46			prepare_period: 1 * DAYS,
47			// Amount of time that a decision may take to be approved prior to cancellation.
48			decision_period: 14 * DAYS,
49			// Amount of time that the approval criteria must hold before it can be approved.
50			confirm_period: 1 * DAYS,
51			// Minimum amount of time that an approved proposal must be in the dispatch queue.
52			min_enactment_period: 1 * DAYS,
53			// Minimum aye votes as percentage of overall conviction-weighted votes needed for
54			// approval as a function of time into decision period.
55			min_approval: Curve::make_reciprocal(4, 14, percent(80), percent(50), percent(100)),
56			// Minimum pre-conviction aye-votes ("support") as percentage of overall population that
57			// is needed for approval as a function of time into decision period.
58			min_support: Curve::make_linear(14, 14, permill(5), percent(25)),
59		},
60	},
61	Track {
62		id: 1,
63		info: pallet_referenda::TrackInfo {
64			name: s("whitelisted_caller"),
65			max_deciding: 100,
66			decision_deposit: 10 * KILOUNIT * SUPPLY_FACTOR,
67			prepare_period: 10 * MINUTES,
68			decision_period: 14 * DAYS,
69			confirm_period: 10 * MINUTES,
70			min_enactment_period: 30 * MINUTES,
71			min_approval: Curve::make_reciprocal(1, 14, percent(96), percent(50), percent(100)),
72			min_support: Curve::make_reciprocal(1, 14 * 24, percent(1), percent(0), percent(2)),
73		},
74	},
75	Track {
76		id: 2,
77		info: pallet_referenda::TrackInfo {
78			name: s("general_admin"),
79			max_deciding: 10,
80			decision_deposit: 500 * UNIT * SUPPLY_FACTOR,
81			prepare_period: 1 * HOURS,
82			decision_period: 14 * DAYS,
83			confirm_period: 1 * DAYS,
84			min_enactment_period: 1 * DAYS,
85			min_approval: Curve::make_reciprocal(4, 14, percent(80), percent(50), percent(100)),
86			min_support: Curve::make_reciprocal(7, 14, percent(10), percent(0), percent(50)),
87		},
88	},
89	Track {
90		id: 3,
91		info: pallet_referenda::TrackInfo {
92			name: s("referendum_canceller"),
93			max_deciding: 20,
94			decision_deposit: 10 * KILOUNIT * SUPPLY_FACTOR,
95			prepare_period: 1 * HOURS,
96			decision_period: 14 * DAYS,
97			confirm_period: 3 * HOURS,
98			min_enactment_period: 10 * MINUTES,
99			min_approval: Curve::make_reciprocal(1, 14, percent(96), percent(50), percent(100)),
100			min_support: Curve::make_reciprocal(1, 14, percent(1), percent(0), percent(50)),
101		},
102	},
103	Track {
104		id: 4,
105		info: pallet_referenda::TrackInfo {
106			name: s("referendum_killer"),
107			max_deciding: 100,
108			decision_deposit: 20 * KILOUNIT * SUPPLY_FACTOR,
109			prepare_period: 1 * HOURS,
110			decision_period: 14 * DAYS,
111			confirm_period: 3 * HOURS,
112			min_enactment_period: 10 * MINUTES,
113			min_approval: Curve::make_reciprocal(1, 14, percent(96), percent(50), percent(100)),
114			min_support: Curve::make_reciprocal(1, 14, percent(1), percent(0), percent(10)),
115		},
116	},
117	Track {
118		id: 5,
119		info: pallet_referenda::TrackInfo {
120			name: s("fast_general_admin"),
121			max_deciding: 10,
122			decision_deposit: 500 * UNIT * SUPPLY_FACTOR,
123			prepare_period: 1 * HOURS,
124			decision_period: 14 * DAYS,
125			confirm_period: 3 * HOURS,
126			min_enactment_period: 10 * MINUTES,
127			min_approval: Curve::make_reciprocal(4, 14, percent(80), percent(50), percent(100)),
128			min_support: Curve::make_reciprocal(5, 14, percent(1), percent(0), percent(50)),
129		},
130	},
131];
132
133pub struct TracksInfo;
134impl pallet_referenda::TracksInfo<Balance, BlockNumber> for TracksInfo {
135	type Id = u16;
136	type RuntimeOrigin = <RuntimeOrigin as frame_support::traits::OriginTrait>::PalletsOrigin;
137
138	fn tracks() -> impl Iterator<Item = Cow<'static, Track<Self::Id, Balance, BlockNumber>>> {
139		TRACKS_DATA.iter().map(Cow::Borrowed)
140	}
141	fn track_for(id: &Self::RuntimeOrigin) -> Result<Self::Id, ()> {
142		if let Ok(system_origin) = frame_system::RawOrigin::try_from(id.clone()) {
143			match system_origin {
144				frame_system::RawOrigin::Root => {
145					if let Some(track) = Self::tracks()
146						.into_iter()
147						.find(|track| track.info.name == s("root"))
148					{
149						Ok(track.id)
150					} else {
151						Err(())
152					}
153				}
154				_ => Err(()),
155			}
156		} else if let Ok(custom_origin) = custom_origins::Origin::try_from(id.clone()) {
157			if let Some(track) = Self::tracks().into_iter().find(|track| {
158				let Ok(track_name) = from_utf8(&track.info.name) else {
159					return false;
160				};
161				let track_name = track_name.trim_end_matches('\0');
162				if let Ok(track_custom_origin) = custom_origins::Origin::from_str(track_name) {
163					track_custom_origin == custom_origin
164				} else {
165					false
166				}
167			}) {
168				Ok(track.id)
169			} else {
170				Err(())
171			}
172		} else {
173			Err(())
174		}
175	}
176}
177
178#[test]
179/// To ensure voters are always locked into their vote
180fn vote_locking_always_longer_than_enactment_period() {
181	for track in TRACKS_DATA {
182		assert!(
183			<Runtime as pallet_conviction_voting::Config>::VoteLockingPeriod::get()
184				>= track.info.min_enactment_period,
185			"Track {} has enactment period {} < vote locking period {}",
186			from_utf8(&track.info.name).expect("Track name is valid UTF-8"),
187			track.info.min_enactment_period,
188			<Runtime as pallet_conviction_voting::Config>::VoteLockingPeriod::get(),
189		);
190	}
191}
192
193#[test]
194fn all_tracks_have_origins() {
195	for track in TRACKS_DATA {
196		// check name.into() is successful either converts into "root" or custom origin
197		let track_is_root = track.info.name == s("root");
198		let track_name = from_utf8(&track.info.name)
199			.expect("Track name is valid UTF-8")
200			.trim_end_matches('\0');
201		let track_has_custom_origin = custom_origins::Origin::from_str(track_name).is_ok();
202		println!("{:?}", from_utf8(&track.info.name).unwrap());
203		assert!(track_is_root || track_has_custom_origin);
204	}
205}