sp_phragmen_compact/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) 2020 Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// 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.
17
18//! Proc macro for phragmen compact assignment.
19
20use proc_macro::TokenStream;
21use proc_macro2::{TokenStream as TokenStream2, Span, Ident};
22use proc_macro_crate::crate_name;
23use quote::quote;
24use syn::{GenericArgument, Type, parse::{Parse, ParseStream, Result}};
25
26mod assignment;
27mod staked;
28
29// prefix used for struct fields in compact.
30const PREFIX: &'static str = "votes";
31
32/// Generates a struct to store the phragmen assignments in a compact way. The struct can only store
33/// distributions up to the given input count. The given count must be greater than 2.
34///
35/// ```ignore
36/// // generate a struct with nominator and edge weight u128, with maximum supported
37/// // edge per voter of 16.
38/// generate_compact_solution_type(pub TestCompact, 16)
39/// ```
40///
41/// This generates:
42///
43/// ```ignore
44/// pub struct TestCompact<V, T, W> {
45/// 	votes1: Vec<(V, T)>,
46/// 	votes2: Vec<(V, (T, W), T)>,
47/// 	votes3: Vec<(V, [(T, W); 2usize], T)>,
48/// 	votes4: Vec<(V, [(T, W); 3usize], T)>,
49/// 	votes5: Vec<(V, [(T, W); 4usize], T)>,
50/// 	votes6: Vec<(V, [(T, W); 5usize], T)>,
51/// 	votes7: Vec<(V, [(T, W); 6usize], T)>,
52/// 	votes8: Vec<(V, [(T, W); 7usize], T)>,
53/// 	votes9: Vec<(V, [(T, W); 8usize], T)>,
54/// 	votes10: Vec<(V, [(T, W); 9usize], T)>,
55/// 	votes11: Vec<(V, [(T, W); 10usize], T)>,
56/// 	votes12: Vec<(V, [(T, W); 11usize], T)>,
57/// 	votes13: Vec<(V, [(T, W); 12usize], T)>,
58/// 	votes14: Vec<(V, [(T, W); 13usize], T)>,
59/// 	votes15: Vec<(V, [(T, W); 14usize], T)>,
60/// 	votes16: Vec<(V, [(T, W); 15usize], T)>,
61/// }
62/// ```
63///
64/// The generic arguments are:
65/// - `V`: identifier/index for voter (nominator) types.
66/// - `T` identifier/index for candidate (validator) types.
67/// - `W` weight type.
68///
69/// Some conversion implementations are provided by default if
70/// - `W` is u128, or
71/// - `W` is anything that implements `PerThing` (such as `Perbill`)
72///
73/// The ideas behind the structure are as follows:
74///
75/// - For single distribution, no weight is stored. The weight is known to be 100%.
76/// - For all the rest, the weight if the last distribution is omitted. This value can be computed
77///   from the rest.
78///
79#[proc_macro]
80pub fn generate_compact_solution_type(item: TokenStream) -> TokenStream {
81	let CompactSolutionDef {
82		vis,
83		ident,
84		count,
85	} = syn::parse_macro_input!(item as CompactSolutionDef);
86
87	let voter_type = GenericArgument::Type(Type::Verbatim(quote!(V)));
88	let target_type = GenericArgument::Type(Type::Verbatim(quote!(T)));
89	let weight_type = GenericArgument::Type(Type::Verbatim(quote!(W)));
90
91	let imports = imports().unwrap_or_else(|e| e.to_compile_error());
92
93	let compact_def = struct_def(
94		vis,
95		ident.clone(),
96		count,
97		voter_type.clone(),
98		target_type.clone(),
99		weight_type,
100	).unwrap_or_else(|e| e.to_compile_error());
101
102	let assignment_impls = assignment::assignment(
103		ident.clone(),
104		voter_type.clone(),
105		target_type.clone(),
106		count,
107	);
108
109	let staked_impls = staked::staked(
110		ident,
111		voter_type,
112		target_type,
113		count,
114	);
115
116	quote!(
117		#imports
118		#compact_def
119		#assignment_impls
120		#staked_impls
121	).into()
122}
123
124fn struct_def(
125	vis: syn::Visibility,
126	ident: syn::Ident,
127	count: usize,
128	voter_type: GenericArgument,
129	target_type: GenericArgument,
130	weight_type: GenericArgument,
131) -> Result<TokenStream2> {
132	if count <= 2 {
133		Err(syn::Error::new(
134			Span::call_site(),
135			"cannot build compact solution struct with capacity less than 2."
136		))?
137	}
138
139	let singles = {
140		let name = field_name_for(1);
141		quote!(#name: Vec<(#voter_type, #target_type)>,)
142	};
143
144	let doubles = {
145		let name = field_name_for(2);
146		quote!(#name: Vec<(#voter_type, (#target_type, #weight_type), #target_type)>,)
147	};
148
149	let rest = (3..=count).map(|c| {
150		let field_name = field_name_for(c);
151		let array_len = c - 1;
152		quote!(
153			#field_name: Vec<(
154				#voter_type,
155				[(#target_type, #weight_type); #array_len],
156				#target_type
157			)>,
158		)
159	}).collect::<TokenStream2>();
160
161
162	let len_impl = (1..=count).map(|c| {
163		let field_name = field_name_for(c);
164		quote!(
165			all_len = all_len.saturating_add(self.#field_name.len());
166		)
167	}).collect::<TokenStream2>();
168
169	let edge_count_impl = (1..count).map(|c| {
170		let field_name = field_name_for(c);
171		quote!(
172			all_edges = all_edges.saturating_add(
173				self.#field_name.len().saturating_mul(#c as usize)
174			);
175		)
176	}).collect::<TokenStream2>();
177
178	Ok(quote! (
179		/// A struct to encode a Phragmen assignment in a compact way.
180		#[derive(
181			Default,
182			PartialEq,
183			Eq,
184			Clone,
185			Debug,
186			_phragmen::codec::Encode,
187			_phragmen::codec::Decode,
188		)]
189		#vis struct #ident<#voter_type, #target_type, #weight_type> {
190			// _marker: sp_std::marker::PhantomData<A>,
191			#singles
192			#doubles
193			#rest
194		}
195
196		impl<#voter_type, #target_type, #weight_type> _phragmen::VotingLimit
197		for #ident<#voter_type, #target_type, #weight_type>
198		{
199			const LIMIT: usize = #count;
200		}
201
202		impl<#voter_type, #target_type, #weight_type> #ident<#voter_type, #target_type, #weight_type> {
203			/// Get the length of all the assignments that this type is encoding. This is basically
204			/// the same as the number of assignments, or the number of voters in total.
205			pub fn len(&self) -> usize {
206				let mut all_len = 0usize;
207				#len_impl
208				all_len
209			}
210
211			/// Get the total count of edges.
212			pub fn edge_count(&self) -> usize {
213				let mut all_edges = 0usize;
214				#edge_count_impl
215				all_edges
216			}
217
218			/// Get the average edge count.
219			pub fn average_edge_count(&self) -> usize {
220				self.edge_count().checked_div(self.len()).unwrap_or(0)
221			}
222		}
223	))
224}
225
226fn imports() -> Result<TokenStream2> {
227	let sp_phragmen_imports = match crate_name("sp-phragmen") {
228		Ok(sp_phragmen) => {
229			let ident = syn::Ident::new(&sp_phragmen, Span::call_site());
230			quote!( extern crate #ident as _phragmen; )
231		}
232		Err(e) => return Err(syn::Error::new(Span::call_site(), &e)),
233	};
234
235	Ok(quote!(
236		#sp_phragmen_imports
237	))
238}
239
240struct CompactSolutionDef {
241	vis: syn::Visibility,
242	ident: syn::Ident,
243	count: usize,
244}
245
246impl Parse for CompactSolutionDef {
247	fn parse(input: ParseStream) -> syn::Result<Self> {
248		let vis: syn::Visibility = input.parse()?;
249		let ident: syn::Ident = input.parse()?;
250		let _ = <syn::Token![,]>::parse(input)?;
251		let count_literal: syn::LitInt = input.parse()?;
252		let count = count_literal.base10_parse::<usize>()?;
253		Ok(Self { vis, ident, count } )
254	}
255}
256
257fn field_name_for(n: usize) -> Ident {
258	Ident::new(&format!("{}{}", PREFIX, n), Span::call_site())
259}