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

            
19
use super::*;
20
use crate::currency::{GLMR, KILOGLMR, SUPPLY_FACTOR};
21
use core::str::from_utf8;
22
use sp_std::str::FromStr;
23

            
24
const fn percent(x: i32) -> sp_runtime::FixedI64 {
25
	sp_runtime::FixedI64::from_rational(x as u128, 100)
26
}
27
const fn permill(x: i32) -> sp_runtime::FixedI64 {
28
	sp_runtime::FixedI64::from_rational(x as u128, 1000)
29
}
30

            
31
use pallet_referenda::{Curve, Track};
32
use sp_runtime::str_array as s;
33

            
34
const 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: 20 * KILOGLMR * 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: 2 * KILOGLMR * 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: 100 * GLMR * 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: 2 * KILOGLMR * 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: 4 * KILOGLMR * 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: 100 * GLMR * 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

            
133
pub struct TracksInfo;
134
impl pallet_referenda::TracksInfo<Balance, BlockNumber> for TracksInfo {
135
	type Id = u16;
136
	type RuntimeOrigin = <RuntimeOrigin as frame_support::traits::OriginTrait>::PalletsOrigin;
137
7
	fn tracks() -> impl Iterator<Item = Cow<'static, Track<Self::Id, Balance, BlockNumber>>> {
138
7
		TRACKS_DATA.iter().map(Cow::Borrowed)
139
7
	}
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
179
1
fn vote_locking_always_longer_than_enactment_period() {
180
7
	for track in TRACKS_DATA {
181
6
		assert!(
182
6
			<Runtime as pallet_conviction_voting::Config>::VoteLockingPeriod::get()
183
6
				>= 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
1
}
191

            
192
#[test]
193
1
fn all_tracks_have_origins() {
194
7
	for track in TRACKS_DATA {
195
		// check name.into() is successful either converts into "root" or custom origin
196
6
		let track_is_root = track.info.name == s("root");
197
6
		let track_name = from_utf8(&track.info.name)
198
6
			.expect("Track name is valid UTF-8")
199
6
			.trim_end_matches('\0');
200
6
		let track_has_custom_origin = custom_origins::Origin::from_str(track_name).is_ok();
201
6
		println!("{:?}", from_utf8(&track.info.name).unwrap());
202
6
		assert!(track_is_root || track_has_custom_origin);
203
	}
204
1
}