moonriver_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::{KILOMOVR, MOVR, SUPPLY_FACTOR};
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 * KILOMOVR * 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 * KILOMOVR * 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 * MOVR * 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 * KILOMOVR * 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(10)),
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 * KILOMOVR * 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 * MOVR * 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	fn tracks() -> impl Iterator<Item = Cow<'static, Track<Self::Id, Balance, BlockNumber>>> {
138		TRACKS_DATA.iter().map(Cow::Borrowed)
139	}
140	fn track_for(id: &Self::RuntimeOrigin) -> Result<Self::Id, ()> {
141		if let Ok(system_origin) = frame_system::RawOrigin::try_from(id.clone()) {
142			match system_origin {
143				frame_system::RawOrigin::Root => {
144					if let Some(track) = Self::tracks()
145						.into_iter()
146						.find(|track| track.info.name == s("root"))
147					{
148						Ok(track.id)
149					} else {
150						Err(())
151					}
152				}
153				_ => Err(()),
154			}
155		} else if let Ok(custom_origin) = custom_origins::Origin::try_from(id.clone()) {
156			if let Some(track) = Self::tracks().into_iter().find(|track| {
157				let Ok(track_name) = from_utf8(&track.info.name) else {
158					return false;
159				};
160				let track_name = track_name.trim_end_matches('\0');
161				if let Ok(track_custom_origin) = custom_origins::Origin::from_str(track_name) {
162					track_custom_origin == custom_origin
163				} else {
164					false
165				}
166			}) {
167				Ok(track.id)
168			} else {
169				Err(())
170			}
171		} else {
172			Err(())
173		}
174	}
175}
176
177#[test]
178/// To ensure voters are always locked into their vote
179fn vote_locking_always_longer_than_enactment_period() {
180	for track in TRACKS_DATA {
181		assert!(
182			<Runtime as pallet_conviction_voting::Config>::VoteLockingPeriod::get()
183				>= track.info.min_enactment_period,
184			"Track {} has enactment period {} < vote locking period {}",
185			from_utf8(&track.info.name).expect("Track name is valid UTF-8"),
186			track.info.min_enactment_period,
187			<Runtime as pallet_conviction_voting::Config>::VoteLockingPeriod::get(),
188		);
189	}
190}
191
192#[test]
193fn all_tracks_have_origins() {
194	for track in TRACKS_DATA {
195		// check name.into() is successful either converts into "root" or custom origin
196		let track_is_root = track.info.name == s("root");
197		let track_name = from_utf8(&track.info.name)
198			.expect("Track name is valid UTF-8")
199			.trim_end_matches('\0');
200		let track_has_custom_origin = custom_origins::Origin::from_str(track_name).is_ok();
201		println!("{:?}", from_utf8(&track.info.name).unwrap());
202		assert!(track_is_root || track_has_custom_origin);
203	}
204}