1use frame_election_provider_support::VoteWeight;
57use frame_support::traits::Get;
58use std::{
59 io::Write,
60 path::{Path, PathBuf},
61};
62
63fn existential_weight<T: pallet_staking::Config>(
69 total_issuance: u128,
70 minimum_balance: u128,
71) -> VoteWeight {
72 use sp_staking::currency_to_vote::CurrencyToVote;
73
74 T::CurrencyToVote::to_vote(
75 minimum_balance
76 .try_into()
77 .map_err(|_| "failed to convert minimum_balance to type Balance")
78 .unwrap(),
79 total_issuance
80 .try_into()
81 .map_err(|_| "failed to convert total_issuance to type Balance")
82 .unwrap(),
83 )
84}
85
86fn path_to_header_file() -> Option<PathBuf> {
91 let mut workdir: &Path = &std::env::current_dir().ok()?;
92 while !workdir.join(".git").exists() {
93 workdir = workdir.parent()?;
94 }
95
96 for file_name in &["HEADER-APACHE2", "HEADER-GPL3", "HEADER", "file_header.txt"] {
97 let path = workdir.join(file_name);
98 if path.exists() {
99 return Some(path)
100 }
101 }
102 None
103}
104
105fn underscore_formatter() -> num_format::CustomFormat {
107 num_format::CustomFormat::builder()
108 .grouping(num_format::Grouping::Standard)
109 .separator("_")
110 .build()
111 .expect("format described here meets all constraints")
112}
113
114pub fn constant_ratio(existential_weight: VoteWeight, n_bags: usize) -> f64 {
120 ((VoteWeight::MAX as f64 / existential_weight as f64).ln() / ((n_bags - 1) as f64)).exp()
121}
122
123pub fn thresholds(
132 existential_weight: VoteWeight,
133 constant_ratio: f64,
134 n_bags: usize,
135) -> Vec<VoteWeight> {
136 const WEIGHT_LIMIT: f64 = VoteWeight::MAX as f64;
137
138 let mut thresholds = Vec::with_capacity(n_bags);
139
140 if n_bags > 1 {
141 thresholds.push(existential_weight);
142 }
143
144 while n_bags > 0 && thresholds.len() < n_bags - 1 {
145 let last = thresholds.last().copied().unwrap_or(existential_weight);
146 let successor = (last as f64 * constant_ratio).round().max(last as f64 + 1.0);
147 if successor < WEIGHT_LIMIT {
148 thresholds.push(successor as VoteWeight);
149 } else {
150 eprintln!("unexpectedly exceeded weight limit; breaking threshold generation loop");
151 break
152 }
153 }
154
155 thresholds.push(VoteWeight::MAX);
156
157 debug_assert_eq!(thresholds.len(), n_bags);
158 debug_assert!(n_bags == 0 || thresholds[0] == existential_weight);
159 debug_assert!(n_bags == 0 || thresholds[thresholds.len() - 1] == VoteWeight::MAX);
160
161 thresholds
162}
163
164pub fn generate_thresholds<T: pallet_staking::Config>(
181 n_bags: usize,
182 output: &Path,
183 total_issuance: u128,
184 minimum_balance: u128,
185) -> Result<(), std::io::Error> {
186 if let Some(parent) = output.parent() {
188 if !parent.exists() {
189 std::fs::create_dir_all(parent)?;
190 }
191 }
192
193 if let Some(header_path) = path_to_header_file() {
195 std::fs::copy(header_path, output)?;
196 }
197
198 let file = std::fs::OpenOptions::new().create(true).append(true).open(output)?;
200 let mut buf = std::io::BufWriter::new(file);
201
202 let mut num_buf = num_format::Buffer::new();
204 let format = underscore_formatter();
205
206 let now = chrono::Utc::now();
208 writeln!(buf)?;
209 writeln!(buf, "//! Autogenerated bag thresholds.")?;
210 writeln!(buf, "//!")?;
211 writeln!(buf, "//! Generated on {}", now.to_rfc3339())?;
212 writeln!(buf, "//! Arguments")?;
213 writeln!(buf, "//! Total issuance: {}", &total_issuance)?;
214 writeln!(buf, "//! Minimum balance: {}", &minimum_balance)?;
215
216 writeln!(
217 buf,
218 "//! for the {} runtime.",
219 <T as frame_system::Config>::Version::get().spec_name,
220 )?;
221
222 let existential_weight = existential_weight::<T>(total_issuance, minimum_balance);
223 num_buf.write_formatted(&existential_weight, &format);
224 writeln!(buf)?;
225 writeln!(buf, "/// Existential weight for this runtime.")?;
226 writeln!(buf, "#[cfg(any(test, feature = \"std\"))]")?;
227 writeln!(buf, "#[allow(unused)]")?;
228 writeln!(buf, "pub const EXISTENTIAL_WEIGHT: u64 = {};", num_buf.as_str())?;
229
230 let constant_ratio = constant_ratio(existential_weight, n_bags);
232 writeln!(buf)?;
233 writeln!(buf, "/// Constant ratio between bags for this runtime.")?;
234 writeln!(buf, "#[cfg(any(test, feature = \"std\"))]")?;
235 writeln!(buf, "#[allow(unused)]")?;
236 writeln!(buf, "pub const CONSTANT_RATIO: f64 = {:.16};", constant_ratio)?;
237
238 let thresholds = thresholds(existential_weight, constant_ratio, n_bags);
240 writeln!(buf)?;
241 writeln!(buf, "/// Upper thresholds delimiting the bag list.")?;
242 writeln!(buf, "pub const THRESHOLDS: [u64; {}] = [", thresholds.len())?;
243 for threshold in &thresholds {
244 num_buf.write_formatted(threshold, &format);
245 writeln!(buf, " {:>26},", num_buf.as_str())?;
247 }
248 writeln!(buf, "];")?;
249
250 writeln!(buf)?;
252 writeln!(buf, "/// Upper thresholds delimiting the bag list.")?;
253 writeln!(buf, "pub const THRESHOLDS_BALANCES: [u128; {}] = [", thresholds.len())?;
254 for threshold in thresholds {
255 num_buf.write_formatted(&threshold, &format);
256 writeln!(buf, " {:>26},", num_buf.as_str())?;
258 }
259 writeln!(buf, "];")?;
260
261 Ok(())
262}