Moving NposSolution to frame (#11031)

* Move `sp-npos-elections-solution-type`
to `frame-election-provider-support`
First stab at it, will need to amend some more stuff

* Fixing tests

* Fixing tests

* Fixing cargo.toml for std configuration

* fmt

* Committing suggested changes
renaming, and re exporting macro.

* Removing unneeded imports

* Move `NposSolution` to frame

* Removing `npos_election` dependencies
Implementing _fpes better

* some feedback for moving NPoSSolution to frame

* fmt

* more formatting

* Fixed some imports and fmt

* Fixing docs

Co-authored-by: kianenigma <kian@parity.io>
This commit is contained in:
Georges
2022-03-16 21:27:19 +00:00
committed by GitHub
parent 7a54efd1f2
commit 26a8c7e6b2
19 changed files with 312 additions and 292 deletions
+2 -2
View File
@@ -2164,13 +2164,13 @@ dependencies = [
name = "frame-election-provider-solution-type"
version = "4.0.0-dev"
dependencies = [
"frame-election-provider-support",
"parity-scale-codec",
"proc-macro-crate 1.1.0",
"proc-macro2",
"quote",
"scale-info",
"sp-arithmetic",
"sp-npos-elections",
"syn",
"trybuild",
]
@@ -2198,6 +2198,7 @@ version = "2.0.0-alpha.5"
dependencies = [
"clap 3.1.6",
"frame-election-provider-solution-type",
"frame-election-provider-support",
"honggfuzz",
"parity-scale-codec",
"rand 0.8.4",
@@ -5065,7 +5066,6 @@ dependencies = [
"sp-core",
"sp-inherents",
"sp-io",
"sp-npos-elections",
"sp-offchain",
"sp-runtime",
"sp-sandbox",
-2
View File
@@ -38,7 +38,6 @@ sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../..
sp-session = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/session" }
sp-transaction-pool = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/transaction-pool" }
sp-version = { version = "5.0.0", default-features = false, path = "../../../primitives/version" }
sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/npos-elections" }
sp-io = { version = "6.0.0", default-features = false, path = "../../../primitives/io" }
sp-sandbox = { version = "0.10.0-dev", default-features = false, path = "../../../primitives/sandbox" }
@@ -178,7 +177,6 @@ std = [
"pallet-vesting/std",
"log/std",
"frame-try-runtime/std",
"sp-npos-elections/std",
"sp-io/std",
"pallet-child-bounties/std",
]
+8 -8
View File
@@ -23,9 +23,11 @@
#![recursion_limit = "256"]
use codec::{Decode, Encode, MaxEncodedLen};
use frame_election_provider_support::onchain;
use frame_election_provider_support::{onchain, ExtendedBalance, VoteWeight};
use frame_support::{
construct_runtime, parameter_types,
construct_runtime,
pallet_prelude::Get,
parameter_types,
traits::{
AsEnsureOriginWithArg, ConstU128, ConstU16, ConstU32, Currency, EnsureOneOf,
EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem,
@@ -598,7 +600,7 @@ frame_election_provider_support::generate_solution_type!(
);
parameter_types! {
pub MaxNominations: u32 = <NposSolution16 as sp_npos_elections::NposSolution>::LIMIT as u32;
pub MaxNominations: u32 = <NposSolution16 as frame_election_provider_support::NposSolution>::LIMIT as u32;
}
/// The numbers configured here could always be more than the the maximum limits of staking pallet
@@ -621,10 +623,8 @@ pub const MINER_MAX_ITERATIONS: u32 = 10;
/// A source of random balance for NposSolver, which is meant to be run by the OCW election miner.
pub struct OffchainRandomBalancing;
impl frame_support::pallet_prelude::Get<Option<(usize, sp_npos_elections::ExtendedBalance)>>
for OffchainRandomBalancing
{
fn get() -> Option<(usize, sp_npos_elections::ExtendedBalance)> {
impl Get<Option<(usize, ExtendedBalance)>> for OffchainRandomBalancing {
fn get() -> Option<(usize, ExtendedBalance)> {
use sp_runtime::traits::TrailingZeroInput;
let iters = match MINER_MAX_ITERATIONS {
0 => 0,
@@ -693,7 +693,7 @@ impl pallet_bags_list::Config for Runtime {
type ScoreProvider = Staking;
type WeightInfo = pallet_bags_list::weights::SubstrateWeight<Runtime>;
type BagThresholds = BagThresholds;
type Score = sp_npos_elections::VoteWeight;
type Score = VoteWeight;
}
parameter_types! {
@@ -28,7 +28,6 @@ use frame_support::{
use frame_system::RawOrigin;
use rand::{prelude::SliceRandom, rngs::SmallRng, SeedableRng};
use sp_arithmetic::{per_things::Percent, traits::One};
use sp_npos_elections::IndexAssignment;
use sp_runtime::InnerOf;
const SEED: u32 = 999;
@@ -461,6 +460,7 @@ frame_benchmarking::benchmarks! {
T::BenchmarkingConfig::DESIRED_TARGETS[1];
// Subtract this percentage from the actual encoded size
let f in 0 .. 95;
use frame_election_provider_support::IndexAssignment;
// Compute a random solution, then work backwards to get the lists of voters, targets, and
// assignments
@@ -231,7 +231,7 @@
use codec::{Decode, Encode};
use frame_election_provider_support::{
ElectionDataProvider, ElectionProvider, InstantElectionProvider,
ElectionDataProvider, ElectionProvider, InstantElectionProvider, NposSolution,
};
use frame_support::{
dispatch::DispatchResultWithPostInfo,
@@ -246,8 +246,7 @@ use sp_arithmetic::{
UpperOf,
};
use sp_npos_elections::{
assignment_ratio_to_staked_normalized, ElectionScore, EvaluateSupport, NposSolution, Supports,
VoteWeight,
assignment_ratio_to_staked_normalized, ElectionScore, EvaluateSupport, Supports, VoteWeight,
};
use sp_runtime::{
traits::Bounded,
@@ -18,7 +18,7 @@
use super::*;
use crate as multi_phase;
use frame_election_provider_support::{
data_provider, onchain, ElectionDataProvider, SequentialPhragmen,
data_provider, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen,
};
pub use frame_support::{assert_noop, assert_ok};
use frame_support::{
@@ -38,7 +38,7 @@ use sp_core::{
};
use sp_npos_elections::{
assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, ElectionResult,
EvaluateSupport, ExtendedBalance, NposSolution,
EvaluateSupport, ExtendedBalance,
};
use sp_runtime::{
testing::Header,
@@ -23,12 +23,13 @@ use crate::{
SolutionOrSnapshotSize, Weight, WeightInfo,
};
use codec::{Decode, Encode, HasCompact};
use frame_election_provider_support::NposSolution;
use frame_support::{
storage::bounded_btree_map::BoundedBTreeMap,
traits::{defensive_prelude::*, Currency, Get, OnUnbalanced, ReservableCurrency},
};
use sp_arithmetic::traits::SaturatedConversion;
use sp_npos_elections::{ElectionScore, NposSolution};
use sp_npos_elections::ElectionScore;
use sp_runtime::{
traits::{Saturating, Zero},
RuntimeDebug,
@@ -23,12 +23,11 @@ use crate::{
WeightInfo,
};
use codec::Encode;
use frame_election_provider_support::{NposSolver, PerThing128};
use frame_election_provider_support::{NposSolution, NposSolver, PerThing128};
use frame_support::{dispatch::DispatchResult, ensure, traits::Get};
use frame_system::offchain::SubmitTransaction;
use sp_npos_elections::{
assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, ElectionResult,
NposSolution,
};
use sp_runtime::{
offchain::storage::{MutateStorageError, StorageValueRef},
@@ -52,9 +51,9 @@ pub type VoterOf<T> = frame_election_provider_support::VoterOf<<T as Config>::Da
pub type Assignment<T> =
sp_npos_elections::Assignment<<T as frame_system::Config>::AccountId, SolutionAccuracyOf<T>>;
/// The [`IndexAssignment`][sp_npos_elections::IndexAssignment] type specialized for a particular
/// runtime `T`.
pub type IndexAssignmentOf<T> = sp_npos_elections::IndexAssignmentOf<SolutionOf<T>>;
/// The [`IndexAssignment`][frame_election_provider_support::IndexAssignment] type specialized for a
/// particular runtime `T`.
pub type IndexAssignmentOf<T> = frame_election_provider_support::IndexAssignmentOf<SolutionOf<T>>;
/// Error type of the pallet's [`crate::Config::Solver`].
pub type SolverErrorOf<T> = <<T as Config>::Solver as NposSolver>::Error;
@@ -742,10 +741,11 @@ mod tests {
};
use codec::Decode;
use frame_benchmarking::Zero;
use frame_election_provider_support::IndexAssignment;
use frame_support::{
assert_noop, assert_ok, bounded_vec, dispatch::Dispatchable, traits::OffchainWorker,
};
use sp_npos_elections::{ElectionScore, IndexAssignment};
use sp_npos_elections::ElectionScore;
use sp_runtime::{
offchain::storage_lock::{BlockAndTime, StorageLock},
traits::ValidateUnsigned,
@@ -25,5 +25,5 @@ parity-scale-codec = "3.0.0"
scale-info = "2.0.1"
sp-arithmetic = { version = "5.0.0", path = "../../../primitives/arithmetic" }
# used by generate_solution_type:
sp-npos-elections = { version = "4.0.0-dev", path = "../../../primitives/npos-elections" }
frame-election-provider-support = { version = "4.0.0-dev", path = ".." }
trybuild = "1.0.53"
@@ -20,6 +20,7 @@ rand = { version = "0.8", features = ["std", "small_rng"] }
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] }
scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
frame-election-provider-solution-type = { version = "4.0.0-dev", path = ".." }
frame-election-provider-support = { version = "4.0.0-dev", path = "../.." }
sp-arithmetic = { version = "5.0.0", path = "../../../../primitives/arithmetic" }
sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" }
# used by generate_solution_type:
@@ -51,14 +51,14 @@ fn decode_impl(
quote! {
let #name =
<
_npos::sp_std::prelude::Vec<(_npos::codec::Compact<#voter_type>, _npos::codec::Compact<#target_type>)>
_feps::sp_std::prelude::Vec<(_feps::codec::Compact<#voter_type>, _feps::codec::Compact<#target_type>)>
as
_npos::codec::Decode
_feps::codec::Decode
>::decode(value)?;
let #name = #name
.into_iter()
.map(|(v, t)| (v.0, t.0))
.collect::<_npos::sp_std::prelude::Vec<_>>();
.collect::<_feps::sp_std::prelude::Vec<_>>();
}
};
@@ -73,12 +73,12 @@ fn decode_impl(
quote! {
let #name =
<
_npos::sp_std::prelude::Vec<(
_npos::codec::Compact<#voter_type>,
[(_npos::codec::Compact<#target_type>, _npos::codec::Compact<#weight_type>); #c-1],
_npos::codec::Compact<#target_type>,
_feps::sp_std::prelude::Vec<(
_feps::codec::Compact<#voter_type>,
[(_feps::codec::Compact<#target_type>, _feps::codec::Compact<#weight_type>); #c-1],
_feps::codec::Compact<#target_type>,
)>
as _npos::codec::Decode
as _feps::codec::Decode
>::decode(value)?;
let #name = #name
.into_iter()
@@ -87,7 +87,7 @@ fn decode_impl(
[ #inner_impl ],
t_last.0,
))
.collect::<_npos::sp_std::prelude::Vec<_>>();
.collect::<_feps::sp_std::prelude::Vec<_>>();
}
})
.collect::<TokenStream2>();
@@ -100,8 +100,8 @@ fn decode_impl(
.collect::<TokenStream2>();
quote!(
impl _npos::codec::Decode for #ident {
fn decode<I: _npos::codec::Input>(value: &mut I) -> Result<Self, _npos::codec::Error> {
impl _feps::codec::Decode for #ident {
fn decode<I: _feps::codec::Input>(value: &mut I) -> Result<Self, _feps::codec::Error> {
#decode_impl_single
#decode_impl_rest
@@ -123,10 +123,10 @@ fn encode_impl(ident: &syn::Ident, count: usize) -> TokenStream2 {
let #name = self.#name
.iter()
.map(|(v, t)| (
_npos::codec::Compact(v.clone()),
_npos::codec::Compact(t.clone()),
_feps::codec::Compact(v.clone()),
_feps::codec::Compact(t.clone()),
))
.collect::<_npos::sp_std::prelude::Vec<_>>();
.collect::<_feps::sp_std::prelude::Vec<_>>();
#name.encode_to(&mut r);
}
};
@@ -139,8 +139,8 @@ fn encode_impl(ident: &syn::Ident, count: usize) -> TokenStream2 {
let inners_solution_array = (0..c - 1)
.map(|i| {
quote! {(
_npos::codec::Compact(inner[#i].0.clone()),
_npos::codec::Compact(inner[#i].1.clone()),
_feps::codec::Compact(inner[#i].0.clone()),
_feps::codec::Compact(inner[#i].1.clone()),
),}
})
.collect::<TokenStream2>();
@@ -149,19 +149,19 @@ fn encode_impl(ident: &syn::Ident, count: usize) -> TokenStream2 {
let #name = self.#name
.iter()
.map(|(v, inner, t_last)| (
_npos::codec::Compact(v.clone()),
_feps::codec::Compact(v.clone()),
[ #inners_solution_array ],
_npos::codec::Compact(t_last.clone()),
_feps::codec::Compact(t_last.clone()),
))
.collect::<_npos::sp_std::prelude::Vec<_>>();
.collect::<_feps::sp_std::prelude::Vec<_>>();
#name.encode_to(&mut r);
}
})
.collect::<TokenStream2>();
quote!(
impl _npos::codec::Encode for #ident {
fn encode(&self) -> _npos::sp_std::prelude::Vec<u8> {
impl _feps::codec::Encode for #ident {
fn encode(&self) -> _feps::sp_std::prelude::Vec<u8> {
let mut r = vec![];
#encode_impl_single
#encode_impl_rest
@@ -182,8 +182,8 @@ fn scale_info_impl(
let name = format!("{}", vote_field(1));
quote! {
.field(|f|
f.ty::<_npos::sp_std::prelude::Vec<
(_npos::codec::Compact<#voter_type>, _npos::codec::Compact<#target_type>)
f.ty::<_feps::sp_std::prelude::Vec<
(_feps::codec::Compact<#voter_type>, _feps::codec::Compact<#target_type>)
>>()
.name(#name)
)
@@ -194,10 +194,10 @@ fn scale_info_impl(
let name = format!("{}", vote_field(2));
quote! {
.field(|f|
f.ty::<_npos::sp_std::prelude::Vec<(
_npos::codec::Compact<#voter_type>,
(_npos::codec::Compact<#target_type>, _npos::codec::Compact<#weight_type>),
_npos::codec::Compact<#target_type>
f.ty::<_feps::sp_std::prelude::Vec<(
_feps::codec::Compact<#voter_type>,
(_feps::codec::Compact<#target_type>, _feps::codec::Compact<#weight_type>),
_feps::codec::Compact<#target_type>
)>>()
.name(#name)
)
@@ -209,13 +209,13 @@ fn scale_info_impl(
let name = format!("{}", vote_field(c));
quote! {
.field(|f|
f.ty::<_npos::sp_std::prelude::Vec<(
_npos::codec::Compact<#voter_type>,
f.ty::<_feps::sp_std::prelude::Vec<(
_feps::codec::Compact<#voter_type>,
[
(_npos::codec::Compact<#target_type>, _npos::codec::Compact<#weight_type>);
(_feps::codec::Compact<#target_type>, _feps::codec::Compact<#weight_type>);
#c - 1
],
_npos::codec::Compact<#target_type>
_feps::codec::Compact<#target_type>
)>>()
.name(#name)
)
@@ -224,14 +224,14 @@ fn scale_info_impl(
.collect::<TokenStream2>();
quote!(
impl _npos::scale_info::TypeInfo for #ident {
impl _feps::scale_info::TypeInfo for #ident {
type Identity = Self;
fn type_info() -> _npos::scale_info::Type<_npos::scale_info::form::MetaForm> {
_npos::scale_info::Type::builder()
.path(_npos::scale_info::Path::new(stringify!(#ident), module_path!()))
fn type_info() -> _feps::scale_info::Type<_feps::scale_info::form::MetaForm> {
_feps::scale_info::Type::builder()
.path(_feps::scale_info::Path::new(stringify!(#ident), module_path!()))
.composite(
_npos::scale_info::build::Fields::named()
_feps::scale_info::build::Fields::named()
#scale_info_impl_single
#scale_info_impl_double
#scale_info_impl_rest
@@ -88,7 +88,7 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error {
/// ```
///
/// The given struct provides function to convert from/to `Assignment` as part of
/// `sp_npos_elections::Solution` trait:
/// `frame_election_provider_support::NposSolution` trait:
///
/// - `fn from_assignment<..>(..)`
/// - `fn into_assignment<..>(..)`
@@ -101,7 +101,7 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error {
///
/// ```
/// # use frame_election_provider_solution_type::generate_solution_type;
/// # use sp_npos_elections::NposSolution;
/// # use frame_election_provider_support::NposSolution;
/// # use sp_arithmetic::per_things::Perbill;
/// generate_solution_type!(
/// #[compact]
@@ -226,11 +226,11 @@ where
}
fn imports() -> Result<TokenStream2> {
match crate_name("sp-npos-elections") {
Ok(FoundCrate::Itself) => Ok(quote! { use crate as _npos; }),
Ok(FoundCrate::Name(sp_npos_elections)) => {
let ident = syn::Ident::new(&sp_npos_elections, Span::call_site());
Ok(quote!( extern crate #ident as _npos; ))
match crate_name("frame-election-provider-support") {
Ok(FoundCrate::Itself) => Ok(quote! { use crate as _feps; }),
Ok(FoundCrate::Name(frame_election_provider_support)) => {
let ident = syn::Ident::new(&frame_election_provider_support, Span::call_site());
Ok(quote!( extern crate #ident as _feps; ))
},
Err(e) => Err(syn::Error::new(Span::call_site(), e)),
}
@@ -39,7 +39,7 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result<TokenStream2> {
let name = vote_field(1);
// NOTE: we use the visibility of the struct for the fields as well.. could be made better.
quote!(
#vis #name: _npos::sp_std::prelude::Vec<(#voter_type, #target_type)>,
#vis #name: _feps::sp_std::prelude::Vec<(#voter_type, #target_type)>,
)
};
@@ -48,7 +48,7 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result<TokenStream2> {
let field_name = vote_field(c);
let array_len = c - 1;
quote!(
#vis #field_name: _npos::sp_std::prelude::Vec<(
#vis #field_name: _feps::sp_std::prelude::Vec<(
#voter_type,
[(#target_type, #weight_type); #array_len],
#target_type
@@ -83,9 +83,9 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result<TokenStream2> {
Eq,
Clone,
Debug,
_npos::codec::Encode,
_npos::codec::Decode,
_npos::scale_info::TypeInfo,
_feps::codec::Encode,
_feps::codec::Decode,
_feps::scale_info::TypeInfo,
)])
};
@@ -101,8 +101,8 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result<TokenStream2> {
#derives_and_maybe_compact_encoding
#vis struct #ident { #single #rest }
use _npos::__OrInvalidIndex;
impl _npos::NposSolution for #ident {
use _feps::__OrInvalidIndex;
impl _feps::NposSolution for #ident {
const LIMIT: usize = #count;
type VoterIndex = #voter_type;
type TargetIndex = #target_type;
@@ -114,34 +114,34 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result<TokenStream2> {
}
fn from_assignment<FV, FT, A>(
assignments: &[_npos::Assignment<A, #weight_type>],
assignments: &[_feps::Assignment<A, #weight_type>],
voter_index: FV,
target_index: FT,
) -> Result<Self, _npos::Error>
) -> Result<Self, _feps::Error>
where
A: _npos::IdentifierT,
A: _feps::IdentifierT,
for<'r> FV: Fn(&'r A) -> Option<Self::VoterIndex>,
for<'r> FT: Fn(&'r A) -> Option<Self::TargetIndex>,
{
let mut #struct_name: #ident = Default::default();
for _npos::Assignment { who, distribution } in assignments {
for _feps::Assignment { who, distribution } in assignments {
match distribution.len() {
0 => continue,
#from_impl
_ => {
return Err(_npos::Error::SolutionTargetOverflow);
return Err(_feps::Error::SolutionTargetOverflow);
}
}
};
Ok(#struct_name)
}
fn into_assignment<A: _npos::IdentifierT>(
fn into_assignment<A: _feps::IdentifierT>(
self,
voter_at: impl Fn(Self::VoterIndex) -> Option<A>,
target_at: impl Fn(Self::TargetIndex) -> Option<A>,
) -> Result<_npos::sp_std::prelude::Vec<_npos::Assignment<A, #weight_type>>, _npos::Error> {
let mut #assignment_name: _npos::sp_std::prelude::Vec<_npos::Assignment<A, #weight_type>> = Default::default();
) -> Result<_feps::sp_std::prelude::Vec<_feps::Assignment<A, #weight_type>>, _feps::Error> {
let mut #assignment_name: _feps::sp_std::prelude::Vec<_feps::Assignment<A, #weight_type>> = Default::default();
#into_impl
Ok(#assignment_name)
}
@@ -158,10 +158,10 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result<TokenStream2> {
all_edges
}
fn unique_targets(&self) -> _npos::sp_std::prelude::Vec<Self::TargetIndex> {
fn unique_targets(&self) -> _feps::sp_std::prelude::Vec<Self::TargetIndex> {
// NOTE: this implementation returns the targets sorted, but we don't use it yet per
// se, nor is the API enforcing it.
use _npos::sp_std::collections::btree_set::BTreeSet;
use _feps::sp_std::collections::btree_set::BTreeSet;
let mut all_targets: BTreeSet<Self::TargetIndex> = BTreeSet::new();
let mut maybe_insert_target = |t: Self::TargetIndex| {
all_targets.insert(t);
@@ -173,22 +173,22 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result<TokenStream2> {
}
}
type __IndexAssignment = _npos::IndexAssignment<
<#ident as _npos::NposSolution>::VoterIndex,
<#ident as _npos::NposSolution>::TargetIndex,
<#ident as _npos::NposSolution>::Accuracy,
type __IndexAssignment = _feps::IndexAssignment<
<#ident as _feps::NposSolution>::VoterIndex,
<#ident as _feps::NposSolution>::TargetIndex,
<#ident as _feps::NposSolution>::Accuracy,
>;
impl<'a> _npos::sp_std::convert::TryFrom<&'a [__IndexAssignment]> for #ident {
type Error = _npos::Error;
impl<'a> _feps::sp_std::convert::TryFrom<&'a [__IndexAssignment]> for #ident {
type Error = _feps::Error;
fn try_from(index_assignments: &'a [__IndexAssignment]) -> Result<Self, Self::Error> {
let mut #struct_name = #ident::default();
for _npos::IndexAssignment { who, distribution } in index_assignments {
for _feps::IndexAssignment { who, distribution } in index_assignments {
match distribution.len() {
0 => {}
#from_index_impl
_ => {
return Err(_npos::Error::SolutionTargetOverflow);
return Err(_feps::Error::SolutionTargetOverflow);
}
}
};
@@ -310,7 +310,7 @@ pub(crate) fn into_impl(
let name = vote_field(1);
quote!(
for (voter_index, target_index) in self.#name {
#assignments.push(_npos::Assignment {
#assignments.push(_feps::Assignment {
who: voter_at(voter_index).or_invalid_index()?,
distribution: vec![
(target_at(target_index).or_invalid_index()?, #per_thing::one())
@@ -329,25 +329,25 @@ pub(crate) fn into_impl(
let mut inners_parsed = inners
.iter()
.map(|(ref t_idx, p)| {
sum = _npos::sp_arithmetic::traits::Saturating::saturating_add(sum, *p);
sum = _feps::sp_arithmetic::traits::Saturating::saturating_add(sum, *p);
let target = target_at(*t_idx).or_invalid_index()?;
Ok((target, *p))
})
.collect::<Result<_npos::sp_std::prelude::Vec<(A, #per_thing)>, _npos::Error>>()?;
.collect::<Result<_feps::sp_std::prelude::Vec<(A, #per_thing)>, _feps::Error>>()?;
if sum >= #per_thing::one() {
return Err(_npos::Error::SolutionWeightOverflow);
return Err(_feps::Error::SolutionWeightOverflow);
}
// defensive only. Since Percent doesn't have `Sub`.
let p_last = _npos::sp_arithmetic::traits::Saturating::saturating_sub(
let p_last = _feps::sp_arithmetic::traits::Saturating::saturating_sub(
#per_thing::one(),
sum,
);
inners_parsed.push((target_at(t_last_idx).or_invalid_index()?, p_last));
#assignments.push(_npos::Assignment {
#assignments.push(_feps::Assignment {
who: voter_at(voter_index).or_invalid_index()?,
distribution: inners_parsed,
});
@@ -167,17 +167,86 @@
#![cfg_attr(not(feature = "std"), no_std)]
pub mod onchain;
use frame_support::{traits::Get, BoundedVec};
pub mod traits;
use codec::{Decode, Encode};
use frame_support::{traits::Get, BoundedVec, RuntimeDebug};
use sp_runtime::traits::Bounded;
use sp_std::{fmt::Debug, prelude::*};
/// Re-export some type as they are used in the interface.
/// Re-export the solution generation macro.
pub use frame_election_provider_solution_type::generate_solution_type;
/// Re-export some type as they are used in the interface.
pub use sp_arithmetic::PerThing;
pub use sp_npos_elections::{
Assignment, ElectionResult, ExtendedBalance, IdentifierT, PerThing128, Support, Supports,
VoteWeight,
Assignment, ElectionResult, Error, ExtendedBalance, IdentifierT, PerThing128, Support,
Supports, VoteWeight,
};
pub use traits::NposSolution;
// re-export for the solution macro, with the dependencies of the macro.
#[doc(hidden)]
pub use codec;
#[doc(hidden)]
pub use scale_info;
#[doc(hidden)]
pub use sp_arithmetic;
#[doc(hidden)]
pub use sp_std;
// Simple Extension trait to easily convert `None` from index closures to `Err`.
//
// This is only generated and re-exported for the solution code to use.
#[doc(hidden)]
pub trait __OrInvalidIndex<T> {
fn or_invalid_index(self) -> Result<T, Error>;
}
impl<T> __OrInvalidIndex<T> for Option<T> {
fn or_invalid_index(self) -> Result<T, Error> {
self.ok_or(Error::SolutionInvalidIndex)
}
}
/// The [`IndexAssignment`] type is an intermediate between the assignments list
/// ([`&[Assignment<T>]`][Assignment]) and `SolutionOf<T>`.
///
/// The voter and target identifiers have already been replaced with appropriate indices,
/// making it fast to repeatedly encode into a `SolutionOf<T>`. This property turns out
/// to be important when trimming for solution length.
#[derive(RuntimeDebug, Clone, Default)]
#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))]
pub struct IndexAssignment<VoterIndex, TargetIndex, P: PerThing> {
/// Index of the voter among the voters list.
pub who: VoterIndex,
/// The distribution of the voter's stake among winning targets.
///
/// Targets are identified by their index in the canonical list.
pub distribution: Vec<(TargetIndex, P)>,
}
impl<VoterIndex, TargetIndex, P: PerThing> IndexAssignment<VoterIndex, TargetIndex, P> {
pub fn new<AccountId: IdentifierT>(
assignment: &Assignment<AccountId, P>,
voter_index: impl Fn(&AccountId) -> Option<VoterIndex>,
target_index: impl Fn(&AccountId) -> Option<TargetIndex>,
) -> Result<Self, Error> {
Ok(Self {
who: voter_index(&assignment.who).or_invalid_index()?,
distribution: assignment
.distribution
.iter()
.map(|(target, proportion)| Some((target_index(target)?, proportion.clone())))
.collect::<Option<Vec<_>>>()
.or_invalid_index()?,
})
}
}
/// A type alias for [`IndexAssignment`] made from [`NposSolution`].
pub type IndexAssignmentOf<C> = IndexAssignment<
<C as NposSolution>::VoterIndex,
<C as NposSolution>::TargetIndex,
<C as NposSolution>::Accuracy,
>;
/// Types that are used by the data provider trait.
pub mod data_provider {
@@ -0,0 +1,129 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
//! Traits for the election operations.
use crate::{Assignment, IdentifierT, IndexAssignmentOf, PerThing128, VoteWeight};
use codec::Encode;
use scale_info::TypeInfo;
use sp_arithmetic::traits::{Bounded, UniqueSaturatedInto};
use sp_npos_elections::{ElectionScore, Error, EvaluateSupport};
use sp_std::{
convert::{TryFrom, TryInto},
fmt::Debug,
prelude::*,
};
/// An opaque index-based, NPoS solution type.
pub trait NposSolution
where
Self: Sized + for<'a> sp_std::convert::TryFrom<&'a [IndexAssignmentOf<Self>], Error = Error>,
{
/// The maximum number of votes that are allowed.
const LIMIT: usize;
/// The voter type. Needs to be an index (convert to usize).
type VoterIndex: UniqueSaturatedInto<usize>
+ TryInto<usize>
+ TryFrom<usize>
+ Debug
+ Copy
+ Clone
+ Bounded
+ Encode
+ TypeInfo;
/// The target type. Needs to be an index (convert to usize).
type TargetIndex: UniqueSaturatedInto<usize>
+ TryInto<usize>
+ TryFrom<usize>
+ Debug
+ Copy
+ Clone
+ Bounded
+ Encode
+ TypeInfo;
/// The weight/accuracy type of each vote.
type Accuracy: PerThing128;
/// Get the length of all the voters that this type is encoding.
///
/// This is basically the same as the number of assignments, or number of active voters.
fn voter_count(&self) -> usize;
/// Get the total count of edges.
///
/// This is effectively in the range of {[`Self::voter_count`], [`Self::voter_count`] *
/// [`Self::LIMIT`]}.
fn edge_count(&self) -> usize;
/// Get the number of unique targets in the whole struct.
///
/// Once presented with a list of winners, this set and the set of winners must be
/// equal.
fn unique_targets(&self) -> Vec<Self::TargetIndex>;
/// Get the average edge count.
fn average_edge_count(&self) -> usize {
self.edge_count().checked_div(self.voter_count()).unwrap_or(0)
}
/// Compute the score of this solution type.
fn score<A, FS>(
self,
stake_of: FS,
voter_at: impl Fn(Self::VoterIndex) -> Option<A>,
target_at: impl Fn(Self::TargetIndex) -> Option<A>,
) -> Result<ElectionScore, Error>
where
for<'r> FS: Fn(&'r A) -> VoteWeight,
A: IdentifierT,
{
let ratio = self.into_assignment(voter_at, target_at)?;
let staked =
sp_npos_elections::helpers::assignment_ratio_to_staked_normalized(ratio, stake_of)?;
let supports = sp_npos_elections::to_supports(&staked);
Ok(supports.evaluate())
}
/// Remove a certain voter.
///
/// This will only search until the first instance of `to_remove`, and return true. If
/// no instance is found (no-op), then it returns false.
///
/// In other words, if this return true, exactly **one** element must have been removed self.
fn remove_voter(&mut self, to_remove: Self::VoterIndex) -> bool;
/// Build self from a list of assignments.
fn from_assignment<FV, FT, A>(
assignments: &[Assignment<A, Self::Accuracy>],
voter_index: FV,
target_index: FT,
) -> Result<Self, Error>
where
A: IdentifierT,
for<'r> FV: Fn(&'r A) -> Option<Self::VoterIndex>,
for<'r> FT: Fn(&'r A) -> Option<Self::TargetIndex>;
/// Convert self into a `Vec<Assignment<A, Self::Accuracy>>`
fn into_assignment<A: IdentifierT>(
self,
voter_at: impl Fn(Self::VoterIndex) -> Option<A>,
target_at: impl Fn(Self::TargetIndex) -> Option<A>,
) -> Result<Vec<Assignment<A, Self::Accuracy>>, Error>;
}
+4 -4
View File
@@ -40,9 +40,9 @@ mod impls;
pub use impls::*;
use crate::{
log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints,
Exposure, Forcing, MaxUnlockingChunks, NegativeImbalanceOf, Nominations, PositiveImbalanceOf,
Releases, RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk,
slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints, Exposure,
Forcing, MaxUnlockingChunks, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, Releases,
RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk,
ValidatorPrefs,
};
@@ -554,7 +554,7 @@ pub mod pallet {
}
for &(ref stash, ref controller, balance, ref status) in &self.stakers {
log!(
crate::log!(
trace,
"inserting genesis staker: {:?} => {:?} => {:?}",
stash,
@@ -17,7 +17,7 @@
//! Structs and helpers for distributing a voter's stake among various winners.
use crate::{Error, ExtendedBalance, IdentifierT, PerThing128, __OrInvalidIndex};
use crate::{ExtendedBalance, IdentifierT, PerThing128};
#[cfg(feature = "std")]
use codec::{Decode, Encode};
use sp_arithmetic::{
@@ -166,44 +166,3 @@ impl<AccountId> StakedAssignment<AccountId> {
self.distribution.iter().fold(Zero::zero(), |a, b| a.saturating_add(b.1))
}
}
/// The [`IndexAssignment`] type is an intermediate between the assignments list
/// ([`&[Assignment<T>]`][Assignment]) and `SolutionOf<T>`.
///
/// The voter and target identifiers have already been replaced with appropriate indices,
/// making it fast to repeatedly encode into a `SolutionOf<T>`. This property turns out
/// to be important when trimming for solution length.
#[derive(RuntimeDebug, Clone, Default)]
#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))]
pub struct IndexAssignment<VoterIndex, TargetIndex, P: PerThing> {
/// Index of the voter among the voters list.
pub who: VoterIndex,
/// The distribution of the voter's stake among winning targets.
///
/// Targets are identified by their index in the canonical list.
pub distribution: Vec<(TargetIndex, P)>,
}
impl<VoterIndex, TargetIndex, P: PerThing> IndexAssignment<VoterIndex, TargetIndex, P> {
pub fn new<AccountId: IdentifierT>(
assignment: &Assignment<AccountId, P>,
voter_index: impl Fn(&AccountId) -> Option<VoterIndex>,
target_index: impl Fn(&AccountId) -> Option<TargetIndex>,
) -> Result<Self, Error> {
Ok(Self {
who: voter_index(&assignment.who).or_invalid_index()?,
distribution: assignment
.distribution
.iter()
.map(|(target, proportion)| Some((target_index(target)?, proportion.clone())))
.collect::<Option<Vec<_>>>()
.or_invalid_index()?,
})
}
}
/// A type alias for [`IndexAssignment`] made from [`crate::NposSolution`].
pub type IndexAssignmentOf<C> = IndexAssignment<
<C as crate::NposSolution>::VoterIndex,
<C as crate::NposSolution>::TargetIndex,
<C as crate::NposSolution>::Accuracy,
>;
+3 -13
View File
@@ -100,26 +100,16 @@ pub mod pjr;
pub mod reduce;
pub mod traits;
pub use assignments::{Assignment, IndexAssignment, IndexAssignmentOf, StakedAssignment};
pub use assignments::{Assignment, StakedAssignment};
pub use balancing::*;
pub use helpers::*;
pub use phragmen::*;
pub use phragmms::*;
pub use pjr::*;
pub use reduce::reduce;
pub use traits::{IdentifierT, NposSolution, PerThing128, __OrInvalidIndex};
pub use traits::{IdentifierT, PerThing128};
// re-export for the solution macro, with the dependencies of the macro.
#[doc(hidden)]
pub use codec;
#[doc(hidden)]
pub use scale_info;
#[doc(hidden)]
pub use sp_arithmetic;
#[doc(hidden)]
pub use sp_std;
/// The errors that might occur in the this crate and solution-type.
/// The errors that might occur in this crate and `frame-election-provider-solution-type`.
#[derive(Eq, PartialEq, RuntimeDebug)]
pub enum Error {
/// While going from solution indices to ratio, the weight of all the edges has gone above the
@@ -17,22 +17,9 @@
//! Traits for the npos-election operations.
use crate::{
Assignment, ElectionScore, Error, EvaluateSupport, ExtendedBalance, IndexAssignmentOf,
VoteWeight,
};
use codec::Encode;
use scale_info::TypeInfo;
use sp_arithmetic::{
traits::{Bounded, UniqueSaturatedInto},
PerThing,
};
use sp_std::{
convert::{TryFrom, TryInto},
fmt::Debug,
ops::Mul,
prelude::*,
};
use crate::ExtendedBalance;
use sp_arithmetic::PerThing;
use sp_std::{fmt::Debug, ops::Mul, prelude::*};
/// an aggregator trait for a generic type of a voter/target identifier. This usually maps to
/// substrate's account id.
@@ -42,116 +29,3 @@ impl<T: Clone + Eq + Ord + Debug + codec::Codec> IdentifierT for T {}
/// Aggregator trait for a PerThing that can be multiplied by u128 (ExtendedBalance).
pub trait PerThing128: PerThing + Mul<ExtendedBalance, Output = ExtendedBalance> {}
impl<T: PerThing + Mul<ExtendedBalance, Output = ExtendedBalance>> PerThing128 for T {}
/// Simple Extension trait to easily convert `None` from index closures to `Err`.
///
/// This is only generated and re-exported for the solution code to use.
#[doc(hidden)]
pub trait __OrInvalidIndex<T> {
fn or_invalid_index(self) -> Result<T, Error>;
}
impl<T> __OrInvalidIndex<T> for Option<T> {
fn or_invalid_index(self) -> Result<T, Error> {
self.ok_or(Error::SolutionInvalidIndex)
}
}
/// An opaque index-based, NPoS solution type.
pub trait NposSolution
where
Self: Sized + for<'a> sp_std::convert::TryFrom<&'a [IndexAssignmentOf<Self>], Error = Error>,
{
/// The maximum number of votes that are allowed.
const LIMIT: usize;
/// The voter type. Needs to be an index (convert to usize).
type VoterIndex: UniqueSaturatedInto<usize>
+ TryInto<usize>
+ TryFrom<usize>
+ Debug
+ Copy
+ Clone
+ Bounded
+ Encode
+ TypeInfo;
/// The target type. Needs to be an index (convert to usize).
type TargetIndex: UniqueSaturatedInto<usize>
+ TryInto<usize>
+ TryFrom<usize>
+ Debug
+ Copy
+ Clone
+ Bounded
+ Encode
+ TypeInfo;
/// The weight/accuracy type of each vote.
type Accuracy: PerThing128;
/// Get the length of all the voters that this type is encoding.
///
/// This is basically the same as the number of assignments, or number of active voters.
fn voter_count(&self) -> usize;
/// Get the total count of edges.
///
/// This is effectively in the range of {[`Self::voter_count`], [`Self::voter_count`] *
/// [`Self::LIMIT`]}.
fn edge_count(&self) -> usize;
/// Get the number of unique targets in the whole struct.
///
/// Once presented with a list of winners, this set and the set of winners must be
/// equal.
fn unique_targets(&self) -> Vec<Self::TargetIndex>;
/// Get the average edge count.
fn average_edge_count(&self) -> usize {
self.edge_count().checked_div(self.voter_count()).unwrap_or(0)
}
/// Compute the score of this solution type.
fn score<A, FS>(
self,
stake_of: FS,
voter_at: impl Fn(Self::VoterIndex) -> Option<A>,
target_at: impl Fn(Self::TargetIndex) -> Option<A>,
) -> Result<ElectionScore, Error>
where
for<'r> FS: Fn(&'r A) -> VoteWeight,
A: IdentifierT,
{
let ratio = self.into_assignment(voter_at, target_at)?;
let staked = crate::helpers::assignment_ratio_to_staked_normalized(ratio, stake_of)?;
let supports = crate::to_supports(&staked);
Ok(supports.evaluate())
}
/// Remove a certain voter.
///
/// This will only search until the first instance of `to_remove`, and return true. If
/// no instance is found (no-op), then it returns false.
///
/// In other words, if this return true, exactly **one** element must have been removed self.
fn remove_voter(&mut self, to_remove: Self::VoterIndex) -> bool;
/// Build self from a list of assignments.
fn from_assignment<FV, FT, A>(
assignments: &[Assignment<A, Self::Accuracy>],
voter_index: FV,
target_index: FT,
) -> Result<Self, Error>
where
A: IdentifierT,
for<'r> FV: Fn(&'r A) -> Option<Self::VoterIndex>,
for<'r> FT: Fn(&'r A) -> Option<Self::TargetIndex>;
/// Convert self into a `Vec<Assignment<A, Self::Accuracy>>`
fn into_assignment<A: IdentifierT>(
self,
voter_at: impl Fn(Self::VoterIndex) -> Option<A>,
target_at: impl Fn(Self::TargetIndex) -> Option<A>,
) -> Result<Vec<Assignment<A, Self::Accuracy>>, Error>;
}