1use frame_election_provider_support::VoteWeight;
58use frame_support::traits::Get;
59use std::{
60 io::Write,
61 path::{Path, PathBuf},
62};
63
64fn existential_weight<T: pallet_staking::Config>(
70 total_issuance: u128,
71 minimum_balance: u128,
72) -> VoteWeight {
73 use sp_staking::currency_to_vote::CurrencyToVote;
74
75 T::CurrencyToVote::to_vote(
76 minimum_balance
77 .try_into()
78 .map_err(|_| "failed to convert minimum_balance to type Balance")
79 .unwrap(),
80 total_issuance
81 .try_into()
82 .map_err(|_| "failed to convert total_issuance to type Balance")
83 .unwrap(),
84 )
85}
86
87fn existential_weight_async<T: pallet_staking_async::Config>(
88 total_issuance: u128,
89 minimum_balance: u128,
90) -> VoteWeight {
91 use sp_staking::currency_to_vote::CurrencyToVote;
92
93 T::CurrencyToVote::to_vote(
94 minimum_balance
95 .try_into()
96 .map_err(|_| "failed to convert minimum_balance to type Balance")
97 .unwrap(),
98 total_issuance
99 .try_into()
100 .map_err(|_| "failed to convert total_issuance to type Balance")
101 .unwrap(),
102 )
103}
104
105fn path_to_header_file() -> Option<PathBuf> {
110 let mut workdir: &Path = &std::env::current_dir().ok()?;
111 while !workdir.join(".git").exists() {
112 workdir = workdir.parent()?;
113 }
114
115 for file_name in &["HEADER-APACHE2", "HEADER-GPL3", "HEADER", "file_header.txt"] {
116 let path = workdir.join(file_name);
117 if path.exists() {
118 return Some(path);
119 }
120 }
121 None
122}
123
124fn underscore_formatter() -> num_format::CustomFormat {
126 num_format::CustomFormat::builder()
127 .grouping(num_format::Grouping::Standard)
128 .separator("_")
129 .build()
130 .expect("format described here meets all constraints")
131}
132
133pub fn constant_ratio(existential_weight: VoteWeight, n_bags: usize) -> f64 {
139 ((VoteWeight::MAX as f64 / existential_weight as f64).ln() / ((n_bags - 1) as f64)).exp()
140}
141
142pub fn thresholds(
151 existential_weight: VoteWeight,
152 constant_ratio: f64,
153 n_bags: usize,
154) -> Vec<VoteWeight> {
155 const WEIGHT_LIMIT: f64 = VoteWeight::MAX as f64;
156
157 let mut thresholds = Vec::with_capacity(n_bags);
158
159 if n_bags > 1 {
160 thresholds.push(existential_weight);
161 }
162
163 while n_bags > 0 && thresholds.len() < n_bags - 1 {
164 let last = thresholds.last().copied().unwrap_or(existential_weight);
165 let successor = (last as f64 * constant_ratio).round().max(last as f64 + 1.0);
166 if successor < WEIGHT_LIMIT {
167 thresholds.push(successor as VoteWeight);
168 } else {
169 eprintln!("unexpectedly exceeded weight limit; breaking threshold generation loop");
170 break;
171 }
172 }
173
174 thresholds.push(VoteWeight::MAX);
175
176 debug_assert_eq!(thresholds.len(), n_bags);
177 debug_assert!(n_bags == 0 || thresholds[0] == existential_weight);
178 debug_assert!(n_bags == 0 || thresholds[thresholds.len() - 1] == VoteWeight::MAX);
179
180 thresholds
181}
182
183fn generate_thresholds_inner<T: frame_system::Config>(
200 n_bags: usize,
201 output: &Path,
202 total_issuance: u128,
203 minimum_balance: u128,
204 existential_weight: VoteWeight,
205) -> Result<(), std::io::Error> {
206 if let Some(parent) = output.parent() {
208 if !parent.exists() {
209 std::fs::create_dir_all(parent)?;
210 }
211 }
212
213 if let Some(header_path) = path_to_header_file() {
215 std::fs::copy(header_path, output)?;
216 }
217
218 let file = std::fs::OpenOptions::new().create(true).append(true).open(output)?;
220 let mut buf = std::io::BufWriter::new(file);
221
222 let mut num_buf = num_format::Buffer::new();
224 let format = underscore_formatter();
225
226 let now = chrono::Utc::now();
228 writeln!(buf)?;
229 writeln!(buf, "//! Autogenerated bag thresholds.")?;
230 writeln!(buf, "//!")?;
231 writeln!(buf, "//! Generated on {}", now.to_rfc3339())?;
232 writeln!(buf, "//! Arguments")?;
233 writeln!(buf, "//! Total issuance: {}", &total_issuance)?;
234 writeln!(buf, "//! Minimum balance: {}", &minimum_balance)?;
235
236 writeln!(buf, "//! for the {} runtime.", T::Version::get().spec_name,)?;
237
238 num_buf.write_formatted(&existential_weight, &format);
239 writeln!(buf)?;
240 writeln!(buf, "/// Existential weight for this runtime.")?;
241 writeln!(buf, "#[cfg(any(test, feature = \"std\"))]")?;
242 writeln!(buf, "#[allow(unused)]")?;
243 writeln!(buf, "pub const EXISTENTIAL_WEIGHT: u64 = {};", num_buf.as_str())?;
244
245 let constant_ratio = constant_ratio(existential_weight, n_bags);
247 writeln!(buf)?;
248 writeln!(buf, "/// Constant ratio between bags for this runtime.")?;
249 writeln!(buf, "#[cfg(any(test, feature = \"std\"))]")?;
250 writeln!(buf, "#[allow(unused)]")?;
251 writeln!(buf, "pub const CONSTANT_RATIO: f64 = {:.16};", constant_ratio)?;
252
253 let thresholds = thresholds(existential_weight, constant_ratio, n_bags);
255 writeln!(buf)?;
256 writeln!(buf, "/// Upper thresholds delimiting the bag list.")?;
257 writeln!(buf, "pub const THRESHOLDS: [u64; {}] = [", thresholds.len())?;
258 for threshold in &thresholds {
259 num_buf.write_formatted(threshold, &format);
260 writeln!(buf, " {:>26},", num_buf.as_str())?;
262 }
263 writeln!(buf, "];")?;
264
265 writeln!(buf)?;
267 writeln!(buf, "/// Upper thresholds delimiting the bag list.")?;
268 writeln!(buf, "pub const THRESHOLDS_BALANCES: [u128; {}] = [", thresholds.len())?;
269 for threshold in thresholds {
270 num_buf.write_formatted(&threshold, &format);
271 writeln!(buf, " {:>26},", num_buf.as_str())?;
273 }
274 writeln!(buf, "];")?;
275
276 Ok(())
277}
278
279pub fn generate_thresholds<T: pallet_staking::Config>(
283 n_bags: usize,
284 output: &Path,
285 total_issuance: u128,
286 minimum_balance: u128,
287) -> Result<(), std::io::Error> {
288 let ew = existential_weight::<T>(total_issuance, minimum_balance);
289 generate_thresholds_inner::<T>(n_bags, output, total_issuance, minimum_balance, ew)
290}
291
292pub fn generate_thresholds_async<T: pallet_staking_async::Config>(
296 n_bags: usize,
297 output: &Path,
298 total_issuance: u128,
299 minimum_balance: u128,
300) -> Result<(), std::io::Error> {
301 let ew = existential_weight_async::<T>(total_issuance, minimum_balance);
302 generate_thresholds_inner::<T>(n_bags, output, total_issuance, minimum_balance, ew)
303}