1// This file is part of Substrate.
23// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
56// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
1718//! Support code to ease the process of generating bag thresholds.
19//!
20//! NOTE: this assume the runtime implements [`pallet_staking::Config`], as it requires an
21//! implementation of the traits [`frame_support::traits::Currency`] and `CurrencyToVote`.
22//!
23//! The process of adding bags to a runtime requires only four steps.
24//!
25//! 1. Update the runtime definition.
26//!
27//! ```ignore
28//! parameter_types!{
29//! pub const BagThresholds: &'static [u64] = &[];
30//! }
31//!
32//! impl pallet_bags_list::Config for Runtime {
33//! // <snip>
34//! type BagThresholds = BagThresholds;
35//! }
36//! ```
37//!
38//! 2. Write a little program to generate the definitions. This program exists only to hook together
39//! the runtime definitions with the various calculations here. Take a look at
40//! _utils/frame/generate_bags/node-runtime_ for an example.
41//!
42//! 3. Run that program:
43//!
44//! ```sh,notrust
45//! $ cargo run -p node-runtime-generate-bags -- --total-issuance 1234 --minimum-balance 1
46//! output.rs ```
47//!
48//! 4. Update the runtime definition.
49//!
50//! ```diff,notrust
51//! + mod output;
52//! - pub const BagThresholds: &'static [u64] = &[];
53//! + pub const BagThresholds: &'static [u64] = &output::THRESHOLDS;
54//! ```
5556use frame_election_provider_support::VoteWeight;
57use frame_support::traits::Get;
58use std::{
59 io::Write,
60 path::{Path, PathBuf},
61};
6263/// Compute the existential weight for the specified configuration.
64///
65/// Note that this value depends on the current issuance, a quantity known to change over time.
66/// This makes the project of computing a static value suitable for inclusion in a static,
67/// generated file _excitingly unstable_.
68fn existential_weight<T: pallet_staking::Config>(
69 total_issuance: u128,
70 minimum_balance: u128,
71) -> VoteWeight {
72use sp_staking::currency_to_vote::CurrencyToVote;
7374 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}
8586/// Return the path to a header file used in this repository if is exists.
87///
88/// Just searches the git working directory root for files matching certain patterns; it's
89/// pretty naive.
90fn path_to_header_file() -> Option<PathBuf> {
91let mut workdir: &Path = &std::env::current_dir().ok()?;
92while !workdir.join(".git").exists() {
93 workdir = workdir.parent()?;
94 }
9596for file_name in &["HEADER-APACHE2", "HEADER-GPL3", "HEADER", "file_header.txt"] {
97let path = workdir.join(file_name);
98if path.exists() {
99return Some(path)
100 }
101 }
102None
103}
104105/// Create an underscore formatter: a formatter which inserts `_` every 3 digits of a number.
106fn 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}
113114/// Compute the constant ratio for the thresholds.
115///
116/// This ratio ensures that each bag, with the possible exceptions of certain small ones and the
117/// final one, is a constant multiple of the previous, while fully occupying the `VoteWeight`
118/// space.
119pub 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}
122123/// Compute the list of bag thresholds.
124///
125/// Returns a list of exactly `n_bags` elements, except in the case of overflow.
126/// The first element is always `existential_weight`.
127/// The last element is always `VoteWeight::MAX`.
128///
129/// All other elements are computed from the previous according to the formula
130/// `threshold[k + 1] = (threshold[k] * ratio).max(threshold[k] + 1);`
131pub fn thresholds(
132 existential_weight: VoteWeight,
133 constant_ratio: f64,
134 n_bags: usize,
135) -> Vec<VoteWeight> {
136const WEIGHT_LIMIT: f64 = VoteWeight::MAX as f64;
137138let mut thresholds = Vec::with_capacity(n_bags);
139140if n_bags > 1 {
141 thresholds.push(existential_weight);
142 }
143144while n_bags > 0 && thresholds.len() < n_bags - 1 {
145let last = thresholds.last().copied().unwrap_or(existential_weight);
146let successor = (last as f64 * constant_ratio).round().max(last as f64 + 1.0);
147if successor < WEIGHT_LIMIT {
148 thresholds.push(successor as VoteWeight);
149 } else {
150eprintln!("unexpectedly exceeded weight limit; breaking threshold generation loop");
151break
152}
153 }
154155 thresholds.push(VoteWeight::MAX);
156157debug_assert_eq!(thresholds.len(), n_bags);
158debug_assert!(n_bags == 0 || thresholds[0] == existential_weight);
159debug_assert!(n_bags == 0 || thresholds[thresholds.len() - 1] == VoteWeight::MAX);
160161 thresholds
162}
163164/// Write a thresholds module to the path specified.
165///
166/// Parameters:
167/// - `n_bags` the number of bags to generate.
168/// - `output` the path to write to; should terminate with a Rust module name, i.e.
169/// `foo/bar/thresholds.rs`.
170/// - `total_issuance` the total amount of the currency in the network.
171/// - `minimum_balance` the minimum balance of the currency required for an account to exist (i.e.
172/// existential deposit).
173///
174/// This generated module contains, in order:
175///
176/// - The contents of the header file in this repository's root, if found.
177/// - Module documentation noting that this is autogenerated and when.
178/// - Some associated constants.
179/// - The constant array of thresholds.
180pub 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// ensure the file is accessible
187if let Some(parent) = output.parent() {
188if !parent.exists() {
189 std::fs::create_dir_all(parent)?;
190 }
191 }
192193// copy the header file
194if let Some(header_path) = path_to_header_file() {
195 std::fs::copy(header_path, output)?;
196 }
197198// open an append buffer
199let file = std::fs::OpenOptions::new().create(true).append(true).open(output)?;
200let mut buf = std::io::BufWriter::new(file);
201202// create underscore formatter and format buffer
203let mut num_buf = num_format::Buffer::new();
204let format = underscore_formatter();
205206// module docs
207let now = chrono::Utc::now();
208writeln!(buf)?;
209writeln!(buf, "//! Autogenerated bag thresholds.")?;
210writeln!(buf, "//!")?;
211writeln!(buf, "//! Generated on {}", now.to_rfc3339())?;
212writeln!(buf, "//! Arguments")?;
213writeln!(buf, "//! Total issuance: {}", &total_issuance)?;
214writeln!(buf, "//! Minimum balance: {}", &minimum_balance)?;
215216writeln!(
217 buf,
218"//! for the {} runtime.",
219 <T as frame_system::Config>::Version::get().spec_name,
220 )?;
221222let existential_weight = existential_weight::<T>(total_issuance, minimum_balance);
223 num_buf.write_formatted(&existential_weight, &format);
224writeln!(buf)?;
225writeln!(buf, "/// Existential weight for this runtime.")?;
226writeln!(buf, "#[cfg(any(test, feature = \"std\"))]")?;
227writeln!(buf, "#[allow(unused)]")?;
228writeln!(buf, "pub const EXISTENTIAL_WEIGHT: u64 = {};", num_buf.as_str())?;
229230// constant ratio
231let constant_ratio = constant_ratio(existential_weight, n_bags);
232writeln!(buf)?;
233writeln!(buf, "/// Constant ratio between bags for this runtime.")?;
234writeln!(buf, "#[cfg(any(test, feature = \"std\"))]")?;
235writeln!(buf, "#[allow(unused)]")?;
236writeln!(buf, "pub const CONSTANT_RATIO: f64 = {:.16};", constant_ratio)?;
237238// thresholds
239let thresholds = thresholds(existential_weight, constant_ratio, n_bags);
240writeln!(buf)?;
241writeln!(buf, "/// Upper thresholds delimiting the bag list.")?;
242writeln!(buf, "pub const THRESHOLDS: [u64; {}] = [", thresholds.len())?;
243for threshold in &thresholds {
244 num_buf.write_formatted(threshold, &format);
245// u64::MAX, with spacers every 3 digits, is 26 characters wide
246writeln!(buf, " {:>26},", num_buf.as_str())?;
247 }
248writeln!(buf, "];")?;
249250// thresholds balance
251writeln!(buf)?;
252writeln!(buf, "/// Upper thresholds delimiting the bag list.")?;
253writeln!(buf, "pub const THRESHOLDS_BALANCES: [u128; {}] = [", thresholds.len())?;
254for threshold in thresholds {
255 num_buf.write_formatted(&threshold, &format);
256// u64::MAX, with spacers every 3 digits, is 26 characters wide
257writeln!(buf, " {:>26},", num_buf.as_str())?;
258 }
259writeln!(buf, "];")?;
260261Ok(())
262}