Offchain Phragmén BREAKING. (#4517)

* Initial skeleton for offchain phragmen

* Basic compact encoding decoding for results

* add compact files

* Bring back Self::ensure_storage_upgraded();

* Make staking use compact stuff.

* First seemingly working version of reduce, full of todos

* Everything phragmen related works again.

* Signing made easier, still issues.

* Signing from offchain compile fine 😎

* make compact work with staked asssignment

* Evaluation basics are in place.

* Move reduce into crate. Document stuff

* move reduce into no_std

* Add files

* Remove other std deps. Runtime compiles

* Seemingly it is al stable; cycle implemented but not integrated.

* Add fuzzing code.

* Cleanup reduce a bit more.

* a metric ton of tests for staking; wip 🔨

* Implement a lot more of the tests.

* wip getting the unsigned stuff to work

* A bit gleanup for unsigned debug

* Clean and finalize compact code.

* Document reduce.

* Still problems with signing

* We officaly duct taped the transaction submission stuff. 🤓

* Deadlock with keys again

* Runtime builds

* Unsigned test works 🙌

* Some cleanups

* Make all the tests compile and stuff

* Minor cleanup

* fix more merge stuff

* Most tests work again.

* a very nasty bug in reduce

* Fix all integrations

* Fix more todos

* Revamp everything and everything

* Remove bogus test

* Some review grumbles.

* Some fixes

* Fix doc test

* loop for submission

* Fix cli, keyring etc.

* some cleanup

* Fix staking tests again

* fix per-things; bring patches from benchmarking

* better score prediction

* Add fuzzer, more patches.

* Some fixes

* More docs

* Remove unused generics

* Remove max-nominator footgun

* Better fuzzer

* Disable it 

* Bump.

* Another round of self-review

* Refactor a lot

* More major fixes in perThing

* Add new fuzz file

* Update lock

* fix fuzzing code.

* Fix nominator retain test

* Add slashing check

* Update frame/staking/src/tests.rs

Co-Authored-By: Joshy Orndorff <JoshOrndorff@users.noreply.github.com>

* Some formatting nits

* Review comments.

* Fix cargo file

* Almost all tests work again

* Update frame/staking/src/tests.rs

Co-Authored-By: thiolliere <gui.thiolliere@gmail.com>

* Fix review comments

* More review stuff

* Some nits

* Fix new staking / session / babe relation

* Update primitives/phragmen/src/lib.rs

Co-Authored-By: thiolliere <gui.thiolliere@gmail.com>

* Update primitives/phragmen/src/lib.rs

Co-Authored-By: thiolliere <gui.thiolliere@gmail.com>

* Update primitives/phragmen/compact/src/lib.rs

Co-Authored-By: thiolliere <gui.thiolliere@gmail.com>

* Some doc updates to slashing

* Fix derive

* Remove imports

* Remove unimplemented tests

* nits

* Remove dbg

* Better fuzzing params

* Remove unused pref map

* Deferred Slashing/Offence for offchain Phragmen  (#5151)

* Some boilerplate

* Add test

* One more test

* Review comments

* Fix build

* review comments

* fix more

* fix build

* Some cleanups and self-reviews

* More minor self reviews

* Final nits

* Some merge fixes.

* opt comment

* Fix build

* Fix build again.

* Update frame/staking/fuzz/fuzz_targets/submit_solution.rs

Co-Authored-By: Gavin Wood <gavin@parity.io>

* Update frame/staking/src/slashing.rs

Co-Authored-By: Gavin Wood <gavin@parity.io>

* Update frame/staking/src/offchain_election.rs

Co-Authored-By: Gavin Wood <gavin@parity.io>

* Fix review comments

* fix test

* === 🔑 Revamp without staking key.

* final round of changes.

* Fix cargo-deny

* Update frame/staking/src/lib.rs

Co-Authored-By: Gavin Wood <gavin@parity.io>

Co-authored-by: Joshy Orndorff <JoshOrndorff@users.noreply.github.com>
Co-authored-by: thiolliere <gui.thiolliere@gmail.com>
Co-authored-by: Gavin Wood <gavin@parity.io>
This commit is contained in:
Kian Paimani
2020-03-26 15:37:40 +01:00
committed by GitHub
parent 2a67e6c437
commit 970c5f94f2
64 changed files with 11953 additions and 892 deletions
+401
View File
@@ -0,0 +1,401 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "arbitrary"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64cf76cb6e2222ed0ea86b2b0ee2f71c96ec6edd5af42e84d59160e91b836ec4"
[[package]]
name = "arrayvec"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
[[package]]
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
[[package]]
name = "bitvec"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993f74b4c99c1908d156b8d2e0fb6277736b0ecbd833982fd1241d39b2766a6"
[[package]]
name = "byte-slice-cast"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3"
[[package]]
name = "byteorder"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]]
name = "c2-chacha"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
dependencies = [
"ppv-lite86",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "fixed-hash"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3367952ceb191f4ab95dd5685dc163ac539e36202f9fcfd0cb22f9f9c542fefc"
dependencies = [
"byteorder",
"rand",
"rustc-hex",
"static_assertions",
]
[[package]]
name = "getrandom"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "honggfuzz"
version = "0.5.45"
dependencies = [
"arbitrary",
"lazy_static",
"memmap",
]
[[package]]
name = "impl-codec"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1be51a921b067b0eaca2fad532d9400041561aa922221cc65f95a85641c6bf53"
dependencies = [
"parity-scale-codec",
]
[[package]]
name = "integer-sqrt"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f65877bf7d44897a473350b1046277941cee20b263397e90869c50b6e766088b"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
[[package]]
name = "memmap"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "num-bigint"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
dependencies = [
"autocfg",
]
[[package]]
name = "parity-scale-codec"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f509c5e67ca0605ee17dcd3f91ef41cadd685c75a298fb6261b781a5acb3f910"
dependencies = [
"arrayvec",
"bitvec",
"byte-slice-cast",
"parity-scale-codec-derive",
"serde",
]
[[package]]
name = "parity-scale-codec-derive"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a0ec292e92e8ec7c58e576adacc1e3f399c597c8f263c42f18420abe58e7245"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "ppv-lite86"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
[[package]]
name = "primitive-types"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4336f4f5d5524fa60bcbd6fe626f9223d8142a50e7053e979acdf0da41ab975"
dependencies = [
"fixed-hash",
"impl-codec",
"uint",
]
[[package]]
name = "proc-macro-crate"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10d4b51f154c8a7fb96fd6dad097cb74b863943ec010ac94b9fd1be8861fe1e"
dependencies = [
"toml",
]
[[package]]
name = "proc-macro2"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom",
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
dependencies = [
"c2-chacha",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core",
]
[[package]]
name = "rustc-hex"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
[[package]]
name = "serde"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sp-arithmetic"
version = "2.0.0-alpha.3"
dependencies = [
"integer-sqrt",
"num-traits",
"parity-scale-codec",
"serde",
"sp-debug-derive",
"sp-std",
]
[[package]]
name = "sp-arithmetic-fuzzer"
version = "2.0.0"
dependencies = [
"honggfuzz",
"num-bigint",
"num-traits",
"primitive-types",
"sp-arithmetic",
]
[[package]]
name = "sp-debug-derive"
version = "2.0.0-alpha.3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sp-std"
version = "2.0.0-alpha.3"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "toml"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
dependencies = [
"serde",
]
[[package]]
name = "uint"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e75a4cdd7b87b28840dba13c483b9a88ee6bbf16ba5c951ee1ecfcf723078e0d"
dependencies = [
"byteorder",
"crunchy",
"rustc-hex",
"static_assertions",
]
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "winapi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
@@ -20,6 +20,10 @@ num-traits = "0.2"
name = "biguint"
path = "src/biguint.rs"
[[bin]]
name = "per_thing_rational"
path = "src/per_thing_rational.rs"
[[bin]]
name = "rational128"
path = "src/rational128.rs"
@@ -0,0 +1,123 @@
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! # Running
//! Running this fuzzer can be done with `cargo hfuzz run per_thing_rational`. `honggfuzz` CLI options can
//! be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads.
//!
//! # Debugging a panic
//! Once a panic is found, it can be debugged with
//! `cargo hfuzz run-debug per_thing_rational hfuzz_workspace/per_thing_rational/*.fuzz`.
use honggfuzz::fuzz;
use sp_arithmetic::{
PerThing, PerU16, Percent, Perbill, Perquintill, assert_eq_error_rate,
traits::SaturatedConversion,
};
fn main() {
loop {
fuzz!(|
data: ((u16, u16), (u32, u32), (u64, u64))
| {
let (u16_pair, u32_pair, u64_pair) = data;
// peru16
let (smaller, bigger) = (u16_pair.0.min(u16_pair.1), u16_pair.0.max(u16_pair.1));
let ratio = PerU16::from_rational_approximation(smaller, bigger);
assert_per_thing_equal_error(
ratio,
PerU16::from_fraction(smaller as f64 / bigger.max(1) as f64),
1,
);
let (smaller, bigger) = (u32_pair.0.min(u32_pair.1), u32_pair.0.max(u32_pair.1));
let ratio = PerU16::from_rational_approximation(smaller, bigger);
assert_per_thing_equal_error(
ratio,
PerU16::from_fraction(smaller as f64 / bigger.max(1) as f64),
1,
);
let (smaller, bigger) = (u64_pair.0.min(u64_pair.1), u64_pair.0.max(u64_pair.1));
let ratio = PerU16::from_rational_approximation(smaller, bigger);
assert_per_thing_equal_error(
ratio,
PerU16::from_fraction(smaller as f64 / bigger.max(1) as f64),
1,
);
// percent
let (smaller, bigger) = (u16_pair.0.min(u16_pair.1), u16_pair.0.max(u16_pair.1));
let ratio = Percent::from_rational_approximation(smaller, bigger);
assert_per_thing_equal_error(
ratio,
Percent::from_fraction(smaller as f64 / bigger.max(1) as f64),
1,
);
let (smaller, bigger) = (u32_pair.0.min(u32_pair.1), u32_pair.0.max(u32_pair.1));
let ratio = Percent::from_rational_approximation(smaller, bigger);
assert_per_thing_equal_error(
ratio,
Percent::from_fraction(smaller as f64 / bigger.max(1) as f64),
1,
);
let (smaller, bigger) = (u64_pair.0.min(u64_pair.1), u64_pair.0.max(u64_pair.1));
let ratio = Percent::from_rational_approximation(smaller, bigger);
assert_per_thing_equal_error(
ratio,
Percent::from_fraction(smaller as f64 / bigger.max(1) as f64),
1,
);
// perbill
let (smaller, bigger) = (u32_pair.0.min(u32_pair.1), u32_pair.0.max(u32_pair.1));
let ratio = Perbill::from_rational_approximation(smaller, bigger);
assert_per_thing_equal_error(
ratio,
Perbill::from_fraction(smaller as f64 / bigger.max(1) as f64),
100,
);
let (smaller, bigger) = (u64_pair.0.min(u64_pair.1), u64_pair.0.max(u64_pair.1));
let ratio = Perbill::from_rational_approximation(smaller, bigger);
assert_per_thing_equal_error(
ratio,
Perbill::from_fraction(smaller as f64 / bigger.max(1) as f64),
100,
);
// perquintillion
let (smaller, bigger) = (u64_pair.0.min(u64_pair.1), u64_pair.0.max(u64_pair.1));
let ratio = Perquintill::from_rational_approximation(smaller, bigger);
assert_per_thing_equal_error(
ratio,
Perquintill::from_fraction(smaller as f64 / bigger.max(1) as f64),
1000,
);
})
}
}
fn assert_per_thing_equal_error<T: PerThing>(a: T, b: T, err: u128) {
let a_abs = a.deconstruct().saturated_into::<u128>();
let b_abs = b.deconstruct().saturated_into::<u128>();
let diff = a_abs.max(b_abs) - a_abs.min(b_abs);
dbg!(&diff);
assert!(diff <= err, "{:?} !~ {:?}", a, b);
}
+14 -2
View File
@@ -19,7 +19,7 @@
#![cfg_attr(not(feature = "std"), no_std)]
/// Copied from `sp-runtime` and documented there.
#[cfg(test)]
#[macro_export]
macro_rules! assert_eq_error_rate {
($x:expr, $y:expr, $error:expr $(,)?) => {
assert!(
@@ -40,5 +40,17 @@ mod fixed64;
mod rational128;
pub use fixed64::Fixed64;
pub use per_things::{PerThing, Percent, Permill, Perbill, Perquintill};
pub use per_things::{PerThing, Percent, PerU16, Permill, Perbill, Perquintill};
pub use rational128::Rational128;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn peru16_rational_does_not_overflow() {
// A historical example that will panic only for per_thing type that are created with
// maximum capacity of their type, e.g. PerU16.
let _ = PerU16::from_rational_approximation(17424870u32, 17424870);
}
}
+288 -119
View File
@@ -17,18 +17,24 @@
#[cfg(feature = "std")]
use serde::{Serialize, Deserialize};
use sp_std::{ops, prelude::*, convert::TryInto};
use sp_std::{ops, fmt, prelude::*, convert::TryInto};
use codec::{Encode, Decode, CompactAs};
use crate::traits::{
SaturatedConversion, UniqueSaturatedInto, Saturating, BaseArithmetic,
use crate::{
traits::{SaturatedConversion, UniqueSaturatedInto, Saturating, BaseArithmetic, Bounded},
};
use sp_debug_derive::RuntimeDebug;
/// Something that implements a fixed point ration with an arbitrary granularity `X`, as _parts per
/// `X`_.
pub trait PerThing: Sized + Saturating + Copy {
pub trait PerThing:
Sized + Saturating + Copy + Default + Eq + PartialEq + Ord + PartialOrd + Bounded + fmt::Debug
{
/// The data type used to build this per-thingy.
type Inner: BaseArithmetic + Copy;
type Inner: BaseArithmetic + Copy + fmt::Debug;
/// The data type that is used to store values bigger than the maximum of this type. This must
/// at least be able to store `Self::ACCURACY * Self::ACCURACY`.
type Upper: BaseArithmetic + Copy + fmt::Debug;
/// accuracy of this type
const ACCURACY: Self::Inner;
@@ -63,12 +69,53 @@ pub trait PerThing: Sized + Saturating + Copy {
/// The computation of this approximation is performed in the generic type `N`. Given
/// `M` as the data type that can hold the maximum value of this per-thing (e.g. u32 for
/// perbill), this can only work if `N == M` or `N: From<M> + TryInto<M>`.
///
/// Note that this always rounds _down_, i.e.
///
/// ```rust
/// # use sp_arithmetic::{Percent, PerThing};
/// # fn main () {
/// // 989/100 is technically closer to 99%.
/// assert_eq!(
/// Percent::from_rational_approximation(989, 1000),
/// Percent::from_parts(98),
/// );
/// # }
/// ```
fn from_rational_approximation<N>(p: N, q: N) -> Self
where N: Clone + Ord + From<Self::Inner> + TryInto<Self::Inner> + ops::Div<N, Output=N>;
where N:
Clone + Ord + From<Self::Inner> + TryInto<Self::Inner> + TryInto<Self::Upper> +
ops::Div<N, Output=N> + ops::Rem<N, Output=N> + ops::Add<N, Output=N>;
/// A mul implementation that always rounds down, whilst the standard `Mul` implementation
/// rounds to the nearest numbers
///
/// ```rust
/// # use sp_arithmetic::{Percent, PerThing};
/// # fn main () {
/// // rounds to closest
/// assert_eq!(Percent::from_percent(34) * 10u64, 3);
/// assert_eq!(Percent::from_percent(36) * 10u64, 4);
///
/// // collapse down
/// assert_eq!(Percent::from_percent(34).mul_collapse(10u64), 3);
/// assert_eq!(Percent::from_percent(36).mul_collapse(10u64), 3);
/// # }
/// ```
fn mul_collapse<N>(self, b: N) -> N
where N: Clone + From<Self::Inner> + UniqueSaturatedInto<Self::Inner> + ops::Rem<N, Output=N>
+ ops::Div<N, Output=N> + ops::Mul<N, Output=N> + ops::Add<N, Output=N>;
}
macro_rules! implement_per_thing {
($name:ident, $test_mod:ident, [$($test_units:tt),+], $max:tt, $type:ty, $upper_type:ty, $title:expr $(,)?) => {
(
$name:ident,
$test_mod:ident,
[$($test_units:tt),+],
$max:tt, $type:ty,
$upper_type:ty,
$title:expr $(,)?
) => {
/// A fixed point representation of a number between in the range [0, 1].
///
#[doc = $title]
@@ -78,33 +125,30 @@ macro_rules! implement_per_thing {
impl PerThing for $name {
type Inner = $type;
type Upper = $upper_type;
/// The accuracy of this type.
const ACCURACY: Self::Inner = $max;
/// Nothing.
fn zero() -> Self { Self(0) }
/// `true` if this is nothing.
fn is_zero(&self) -> bool { self.0 == 0 }
/// Everything.
fn one() -> Self { Self($max) }
/// Consume self and deconstruct into a raw numeric type.
fn deconstruct(self) -> Self::Inner { self.0 }
/// From an explicitly defined number of parts per maximum of the type.
// needed only for peru16. Since peru16 is the only type in which $max ==
// $type::max_value(), rustc is being a smart-a** here by warning that the comparison
// is not needed.
#[allow(unused_comparisons)]
fn from_parts(parts: Self::Inner) -> Self {
Self([parts, $max][(parts > $max) as usize])
}
/// Converts a percent into `Self`. Equal to `x / 100`.
fn from_percent(x: Self::Inner) -> Self {
Self([x, 100][(x > 100) as usize] * ($max / 100))
Self::from_rational_approximation([x, 100][(x > 100) as usize] as $upper_type, 100)
}
/// Return the product of multiplication of this value by itself.
fn square(self) -> Self {
// both can be safely casted and multiplied.
let p: $upper_type = self.0 as $upper_type * self.0 as $upper_type;
@@ -112,39 +156,43 @@ macro_rules! implement_per_thing {
Self::from_rational_approximation(p, q)
}
/// Converts a fraction into `Self`.
#[cfg(feature = "std")]
fn from_fraction(x: f64) -> Self { Self((x * ($max as f64)) as Self::Inner) }
/// Approximate the fraction `p/q` into a per-thing fraction. This will never overflow.
///
/// The computation of this approximation is performed in the generic type `N`. Given
/// `M` as the data type that can hold the maximum value of this per-thing (e.g. u32 for
/// perbill), this can only work if `N == M` or `N: From<M> + TryInto<M>`.
fn from_rational_approximation<N>(p: N, q: N) -> Self
where N: Clone + Ord + From<Self::Inner> + TryInto<Self::Inner> + ops::Div<N, Output=N>
where N:
Clone + Ord + From<Self::Inner> + TryInto<Self::Inner> + TryInto<Self::Upper> +
ops::Div<N, Output=N> + ops::Rem<N, Output=N> + ops::Add<N, Output=N>
{
let div_ceil = |x: N, f: N| -> N {
let mut o = x.clone() / f.clone();
let r = x.rem(f.clone());
if r > N::from(0) {
o = o + N::from(1);
}
o
};
// q cannot be zero.
let q = q.max((1 as Self::Inner).into());
let q: N = q.max((1 as Self::Inner).into());
// p should not be bigger than q.
let p = p.min(q.clone());
let p: N = p.min(q.clone());
let factor = (q.clone() / $max.into()).max((1 as Self::Inner).into());
let factor: N = div_ceil(q.clone(), $max.into()).max((1 as Self::Inner).into());
// q cannot overflow: (q / (q/$max)) < 2 * $max. p < q hence p also cannot overflow.
// this implies that Self::Inner must be able to fit 2 * $max.
let q_reduce: Self::Inner = (q / factor.clone())
// q cannot overflow: (q / (q/$max)) < $max. p < q hence p also cannot overflow.
let q_reduce: $type = (q.clone() / factor.clone())
.try_into()
.map_err(|_| "Failed to convert")
.expect(
"q / (q/$max) < (2 * $max). Macro prevents any type being created that \
"q / ceil(q/$max) < $max. Macro prevents any type being created that \
does not satisfy this; qed"
);
let p_reduce: Self::Inner = (p / factor.clone())
let p_reduce: $type = (p / factor)
.try_into()
.map_err(|_| "Failed to convert")
.expect(
"q / (q/$max) < (2 * $max). Macro prevents any type being created that \
"q / ceil(q/$max) < $max. Macro prevents any type being created that \
does not satisfy this; qed"
);
@@ -157,13 +205,49 @@ macro_rules! implement_per_thing {
$name(part as Self::Inner)
}
fn mul_collapse<N>(self, b: N) -> N
where
N: Clone + From<$type> + UniqueSaturatedInto<$type> + ops::Rem<N, Output=N>
+ ops::Div<N, Output=N> + ops::Mul<N, Output=N> + ops::Add<N, Output=N>
{
let maximum: N = $max.into();
let upper_max: $upper_type = $max.into();
let part: N = self.0.into();
let rem_multiplied_divided = {
let rem = b.clone().rem(maximum.clone());
// `rem_sized` is inferior to $max, thus it fits into $type. This is assured by
// a test.
let rem_sized = rem.saturated_into::<$type>();
// `self` and `rem_sized` are inferior to $max, thus the product is less than
// $max^2 and fits into $upper_type. This is assured by a test.
let rem_multiplied_upper = rem_sized as $upper_type * self.0 as $upper_type;
// `rem_multiplied_upper` is less than $max^2 therefore divided by $max it fits
// in $type. remember that $type always fits $max.
let rem_multiplied_divided_sized =
(rem_multiplied_upper / upper_max) as $type;
// `rem_multiplied_divided_sized` is inferior to b, thus it can be converted
// back to N type
rem_multiplied_divided_sized.into()
};
(b / maximum) * part + rem_multiplied_divided
}
}
/// Implement const functions
impl $name {
/// From an explicitly defined number of parts per maximum of the type.
///
/// This can be called at compile time.
// needed only for peru16. Since peru16 is the only type in which $max ==
// $type::max_value(), rustc is being a smart-a** here by warning that the comparison
// is not needed.
#[allow(unused_comparisons)]
pub const fn from_parts(parts: $type) -> Self {
Self([parts, $max][(parts > $max) as usize])
}
@@ -202,6 +286,16 @@ macro_rules! implement_per_thing {
}
}
impl crate::traits::Bounded for $name {
fn min_value() -> Self {
<Self as PerThing>::zero()
}
fn max_value() -> Self {
<Self as PerThing>::one()
}
}
impl ops::Div for $name {
type Output = Self;
@@ -212,9 +306,10 @@ macro_rules! implement_per_thing {
}
}
/// Overflow-prune multiplication.
/// Non-overflow multiplication.
///
/// tailored to be used with a balance type.
///
impl<N> ops::Mul<N> for $name
where
N: Clone + From<$type> + UniqueSaturatedInto<$type> + ops::Rem<N, Output=N>
@@ -241,6 +336,7 @@ macro_rules! implement_per_thing {
// in $type. remember that $type always fits $max.
let mut rem_multiplied_divided_sized =
(rem_multiplied_upper / upper_max) as $type;
// fix a tiny rounding error
if rem_multiplied_upper % upper_max > upper_max / 2 {
rem_multiplied_divided_sized += 1;
@@ -261,15 +357,17 @@ macro_rules! implement_per_thing {
use super::{$name, Saturating, RuntimeDebug, PerThing};
use crate::traits::Zero;
#[test]
fn macro_expanded_correctly() {
// needed for the `from_percent` to work.
assert!($max >= 100);
assert!($max % 100 == 0);
// needed for the `from_percent` to work. UPDATE: this is no longer needed; yet note
// that tests that use percentage or fractions such as $name::from_fraction(0.2) to
// create values will most likely be inaccurate when used with per_things that are
// not multiples of 100.
// assert!($max >= 100);
// assert!($max % 100 == 0);
// needed for `from_rational_approximation`
assert!(2 * $max < <$type>::max_value());
assert!(2 * ($max as $upper_type) < <$upper_type>::max_value());
assert!(<$upper_type>::from($max) < <$upper_type>::max_value());
// for something like percent they can be the same.
@@ -298,7 +396,7 @@ macro_rules! implement_per_thing {
(63, 1),
(64, 2),
(65, 2),
(<$type>::max_value(), <$type>::max_value().encode().len() + 1)
// (<$type>::max_value(), <$type>::max_value().encode().len() + 1)
];
for &(n, l) in &tests {
let compact: codec::Compact<$name> = $name(n).into();
@@ -317,33 +415,73 @@ macro_rules! implement_per_thing {
assert_eq!($name::zero(), $name::from_parts(Zero::zero()));
assert_eq!($name::one(), $name::from_parts($max));
assert_eq!($name::ACCURACY, $max);
assert_eq!($name::from_percent(0), $name::from_parts(Zero::zero()));
assert_eq!($name::from_percent(10), $name::from_parts($max / 10));
assert_eq!($name::from_percent(100), $name::from_parts($max));
assert_eq!($name::from_fraction(0.0), $name::from_parts(Zero::zero()));
assert_eq!($name::from_fraction(0.1), $name::from_parts($max / 10));
assert_eq!($name::from_fraction(1.0), $name::from_parts($max));
}
macro_rules! u256ify {
($val:expr) => {
Into::<U256>::into($val)
};
}
macro_rules! per_thing_mul_test {
($num_type:tt) => {
// multiplication from all sort of from_percent
assert_eq!(
$name::from_percent(100) * $num_type::max_value(),
$name::from_fraction(1.0) * $num_type::max_value(),
$num_type::max_value()
);
assert_eq_error_rate!(
$name::from_percent(99) * $num_type::max_value(),
((Into::<U256>::into($num_type::max_value()) * 99u32) / 100u32).as_u128() as $num_type,
1,
);
assert_eq!(
$name::from_percent(50) * $num_type::max_value(),
$num_type::max_value() / 2,
);
assert_eq_error_rate!(
$name::from_percent(1) * $num_type::max_value(),
$num_type::max_value() / 100,
1,
);
assert_eq!($name::from_percent(0) * $num_type::max_value(), 0);
if $max % 100 == 0 {
assert_eq_error_rate!(
$name::from_percent(99) * $num_type::max_value(),
((Into::<U256>::into($num_type::max_value()) * 99u32) / 100u32).as_u128() as $num_type,
1,
);
assert_eq!(
$name::from_fraction(0.5) * $num_type::max_value(),
$num_type::max_value() / 2,
);
assert_eq_error_rate!(
$name::from_percent(1) * $num_type::max_value(),
$num_type::max_value() / 100,
1,
);
} else {
assert_eq!(
$name::from_fraction(0.99) * <$num_type>::max_value(),
(
(
u256ify!($name::from_fraction(0.99).0) *
u256ify!(<$num_type>::max_value()) /
u256ify!($max)
).as_u128()
) as $num_type,
);
assert_eq!(
$name::from_fraction(0.50) * <$num_type>::max_value(),
(
(
u256ify!($name::from_fraction(0.50).0) *
u256ify!(<$num_type>::max_value()) /
u256ify!($max)
).as_u128()
) as $num_type,
);
assert_eq!(
$name::from_fraction(0.01) * <$num_type>::max_value(),
(
(
u256ify!($name::from_fraction(0.01).0) *
u256ify!(<$num_type>::max_value()) /
u256ify!($max)
).as_u128()
) as $num_type,
);
}
assert_eq!($name::from_fraction(0.0) * $num_type::max_value(), 0);
// // multiplication with bounds
assert_eq!($name::one() * $num_type::max_value(), $num_type::max_value());
@@ -356,17 +494,20 @@ macro_rules! implement_per_thing {
use primitive_types::U256;
// accuracy test
assert_eq!($name::from_rational_approximation(1 as $type, 3) * 30 as $type, 10);
assert_eq!(
$name::from_rational_approximation(1 as $type, 3) * 30 as $type,
10,
);
$(per_thing_mul_test!($test_units);)*
}
#[test]
fn per_thing_mul_rounds_to_nearest_number() {
assert_eq!($name::from_percent(33) * 10u64, 3);
assert_eq!($name::from_percent(34) * 10u64, 3);
assert_eq!($name::from_percent(35) * 10u64, 3);
assert_eq!($name::from_percent(36) * 10u64, 4);
assert_eq!($name::from_fraction(0.33) * 10u64, 3);
assert_eq!($name::from_fraction(0.34) * 10u64, 3);
assert_eq!($name::from_fraction(0.35) * 10u64, 3);
assert_eq!($name::from_fraction(0.36) * 10u64, 4);
}
#[test]
@@ -398,31 +539,32 @@ macro_rules! implement_per_thing {
);
assert_eq!(
$name::from_rational_approximation(1 as $num_type, 10),
$name::from_percent(10),
$name::from_fraction(0.10),
);
assert_eq!(
$name::from_rational_approximation(1 as $num_type, 4),
$name::from_percent(25),
$name::from_fraction(0.25),
);
assert_eq!(
$name::from_rational_approximation(1 as $num_type, 4),
$name::from_rational_approximation(2 as $num_type, 8),
);
// no accurate anymore but won't overflow.
assert_eq!(
assert_eq_error_rate!(
$name::from_rational_approximation(
$num_type::max_value() - 1,
$num_type::max_value()
),
$name::one(),
).0 as $upper_type,
$name::one().0 as $upper_type,
2,
);
assert_eq_error_rate!(
$name::from_rational_approximation(
$num_type::max_value() / 3,
$num_type::max_value()
).0,
$name::from_parts($max / 3).0,
2
).0 as $upper_type,
$name::from_parts($max / 3).0 as $upper_type,
2,
);
assert_eq!(
$name::from_rational_approximation(1, $num_type::max_value()),
@@ -436,13 +578,14 @@ macro_rules! implement_per_thing {
// This is just to make sure something like Percent which _might_ get built from a
// u8 does not overflow in the context of this test.
let max_value = <$upper_type>::from($max);
// almost at the edge
assert_eq!(
$name::from_rational_approximation($max - 1, $max + 1),
$name::from_rational_approximation(max_value - 1, max_value + 1),
$name::from_parts($max - 2),
);
assert_eq!(
$name::from_rational_approximation(1, $max-1),
$name::from_rational_approximation(1, $max - 1),
$name::from_parts(1),
);
assert_eq!(
@@ -450,76 +593,83 @@ macro_rules! implement_per_thing {
$name::from_parts(1),
);
assert_eq!(
$name::from_rational_approximation(2, 2 * $max - 1),
$name::from_rational_approximation(2, 2 * max_value - 1),
$name::from_parts(1),
);
assert_eq!(
$name::from_rational_approximation(1, $max+1),
$name::from_rational_approximation(1, max_value + 1),
$name::zero(),
);
assert_eq!(
$name::from_rational_approximation(3 * max_value / 2, 3 * max_value),
$name::from_percent(50),
$name::from_fraction(0.5),
);
$(per_thing_from_rationale_approx_test!($test_units);)*
}
#[test]
fn per_things_mul_operates_in_output_type() {
// assert_eq!($name::from_percent(50) * 100u32, 50u32);
assert_eq!($name::from_percent(50) * 100u64, 50u64);
assert_eq!($name::from_percent(50) * 100u128, 50u128);
// assert_eq!($name::from_fraction(0.5) * 100u32, 50u32);
assert_eq!($name::from_fraction(0.5) * 100u64, 50u64);
assert_eq!($name::from_fraction(0.5) * 100u128, 50u128);
}
#[test]
fn per_thing_saturating_op_works() {
assert_eq!(
$name::from_percent(50).saturating_add($name::from_percent(40)),
$name::from_percent(90)
assert_eq_error_rate!(
$name::from_fraction(0.5).saturating_add($name::from_fraction(0.4)).0 as $upper_type,
$name::from_fraction(0.9).0 as $upper_type,
2,
);
assert_eq_error_rate!(
$name::from_fraction(0.5).saturating_add($name::from_fraction(0.5)).0 as $upper_type,
$name::one().0 as $upper_type,
2,
);
assert_eq!(
$name::from_percent(50).saturating_add($name::from_percent(50)),
$name::from_percent(100)
);
assert_eq!(
$name::from_percent(60).saturating_add($name::from_percent(50)),
$name::from_percent(100)
$name::from_fraction(0.6).saturating_add($name::from_fraction(0.5)),
$name::one(),
);
assert_eq!(
$name::from_percent(60).saturating_sub($name::from_percent(50)),
$name::from_percent(10)
assert_eq_error_rate!(
$name::from_fraction(0.6).saturating_sub($name::from_fraction(0.5)).0 as $upper_type,
$name::from_fraction(0.1).0 as $upper_type,
2,
);
assert_eq!(
$name::from_percent(60).saturating_sub($name::from_percent(60)),
$name::from_percent(0)
$name::from_fraction(0.6).saturating_sub($name::from_fraction(0.6)),
$name::from_fraction(0.0),
);
assert_eq!(
$name::from_percent(60).saturating_sub($name::from_percent(70)),
$name::from_percent(0)
$name::from_fraction(0.6).saturating_sub($name::from_fraction(0.7)),
$name::from_fraction(0.0),
);
assert_eq!(
$name::from_percent(50).saturating_mul($name::from_percent(50)),
$name::from_percent(25)
assert_eq_error_rate!(
$name::from_fraction(0.5).saturating_mul($name::from_fraction(0.5)).0 as $upper_type,
$name::from_fraction(0.25).0 as $upper_type,
2,
);
assert_eq!(
$name::from_percent(20).saturating_mul($name::from_percent(20)),
$name::from_percent(4)
assert_eq_error_rate!(
$name::from_fraction(0.2).saturating_mul($name::from_fraction(0.2)).0 as $upper_type,
$name::from_fraction(0.04).0 as $upper_type,
2,
);
assert_eq!(
$name::from_percent(10).saturating_mul($name::from_percent(10)),
$name::from_percent(1)
assert_eq_error_rate!(
$name::from_fraction(0.1).saturating_mul($name::from_fraction(0.1)).0 as $upper_type,
$name::from_fraction(0.01).0 as $upper_type,
1,
);
}
#[test]
fn per_thing_square_works() {
assert_eq!($name::from_percent(100).square(), $name::from_percent(100));
assert_eq!($name::from_percent(50).square(), $name::from_percent(25));
assert_eq!($name::from_percent(10).square(), $name::from_percent(1));
assert_eq!($name::from_fraction(1.0).square(), $name::from_fraction(1.0));
assert_eq!($name::from_fraction(0.5).square(), $name::from_fraction(0.25));
assert_eq!($name::from_fraction(0.1).square(), $name::from_fraction(0.01));
assert_eq!(
$name::from_percent(2).square(),
$name::from_fraction(0.02).square(),
$name::from_parts((4 * <$upper_type>::from($max) / 100 / 100) as $type)
);
}
@@ -527,22 +677,32 @@ macro_rules! implement_per_thing {
#[test]
fn per_things_div_works() {
// normal
assert_eq!($name::from_percent(10) / $name::from_percent(20),
$name::from_percent(50)
assert_eq_error_rate!(
($name::from_fraction(0.1) / $name::from_fraction(0.20)).0 as $upper_type,
$name::from_fraction(0.50).0 as $upper_type,
2,
);
assert_eq!($name::from_percent(10) / $name::from_percent(10),
$name::from_percent(100)
assert_eq_error_rate!(
($name::from_fraction(0.1) / $name::from_fraction(0.10)).0 as $upper_type,
$name::from_fraction(1.0).0 as $upper_type,
2,
);
assert_eq!($name::from_percent(10) / $name::from_percent(0),
$name::from_percent(100)
assert_eq_error_rate!(
($name::from_fraction(0.1) / $name::from_fraction(0.0)).0 as $upper_type,
$name::from_fraction(1.0).0 as $upper_type,
2,
);
// will not overflow
assert_eq!($name::from_percent(10) / $name::from_percent(5),
$name::from_percent(100)
assert_eq_error_rate!(
($name::from_fraction(0.10) / $name::from_fraction(0.05)).0 as $upper_type,
$name::from_fraction(1.0).0 as $upper_type,
2,
);
assert_eq!($name::from_percent(100) / $name::from_percent(50),
$name::from_percent(100)
assert_eq_error_rate!(
($name::from_fraction(1.0) / $name::from_fraction(0.5)).0 as $upper_type,
$name::from_fraction(1.0).0 as $upper_type,
2,
);
}
}
@@ -558,6 +718,15 @@ implement_per_thing!(
u16,
"_Percent_",
);
implement_per_thing!(
PerU16,
test_peru16,
[u32, u64, u128],
65535_u16,
u16,
u32,
"_Parts per 65535_",
);
implement_per_thing!(
Permill,
test_permill,
@@ -17,8 +17,6 @@
//! Schnorrkel-based VRF.
use codec::{Encode, Decode};
#[cfg(feature = "std")]
use sp_core::U512;
use sp_runtime::RuntimeDebug;
use sp_std::ops::{Deref, DerefMut};
#[cfg(feature = "std")]
@@ -27,6 +25,8 @@ use std::convert::TryFrom;
use codec::EncodeLike;
#[cfg(feature = "std")]
use schnorrkel::errors::MultiSignatureStage;
#[cfg(feature = "std")]
use sp_core::U512;
#[cfg(feature = "std")]
pub use schnorrkel::{SignatureError, vrf::{VRF_PROOF_LENGTH, VRF_OUTPUT_LENGTH}};
+2
View File
@@ -962,6 +962,8 @@ pub mod key_types {
pub const IM_ONLINE: KeyTypeId = KeyTypeId(*b"imon");
/// Key type for AuthorityDiscovery module, built-in.
pub const AUTHORITY_DISCOVERY: KeyTypeId = KeyTypeId(*b"audi");
/// Key type for staking, built-in.
pub const STAKING: KeyTypeId = KeyTypeId(*b"stak");
/// A key type ID useful for tests.
pub const DUMMY: KeyTypeId = KeyTypeId(*b"dumy");
}
+7 -3
View File
@@ -6,21 +6,25 @@ edition = "2018"
license = "GPL-3.0"
homepage = "https://substrate.dev"
repository = "https://github.com/paritytech/substrate/"
description = "PHRAGMENT primitives"
description = "Phragmen primitives"
[dependencies]
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] }
serde = { version = "1.0.101", optional = true, features = ["derive"] }
sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" }
sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" }
sp-phragmen-compact = { version = "2.0.0-alpha.4", path = "./compact" }
[dev-dependencies]
substrate-test-utils = { version = "2.0.0-alpha.5", path = "../../test-utils" }
sp-io ={ version = "2.0.0-alpha.5", path = "../../primitives/io" }
rand = "0.7.2"
rand = "0.7.3"
sp-phragmen = { path = "." }
[features]
default = ["std"]
bench = []
std = [
"codec/std",
"serde",
"sp-std/std",
"sp-runtime/std",
@@ -16,6 +16,7 @@
//! Note that execution times will not be accurate in an absolute scale, since
//! - Everything is executed in the context of `TestExternalities`
//! - Everything is executed in native environment.
#![cfg(feature = "bench")]
#![feature(test)]
@@ -0,0 +1,18 @@
[package]
name = "sp-phragmen-compact"
version = "2.0.0-dev"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0"
homepage = "https://substrate.dev"
repository = "https://github.com/paritytech/substrate/"
description = "Phragmen Compact Solution"
[lib]
proc-macro = true
[dependencies]
syn = { version = "1.0.7", features = ["full", "visit"] }
quote = "1.0"
proc-macro2 = "1.0.6"
proc-macro-crate = "0.1.4"
@@ -0,0 +1,210 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Code generation for the ratio assignment type.
use crate::field_name_for;
use proc_macro2::{TokenStream as TokenStream2};
use syn::{GenericArgument};
use quote::quote;
fn from_impl(count: usize) -> TokenStream2 {
let from_impl_single = {
let name = field_name_for(1);
quote!(1 => compact.#name.push(
(
index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?,
index_of_target(&distribution[0].0).ok_or(_phragmen::Error::CompactInvalidIndex)?,
)
),)
};
let from_impl_double = {
let name = field_name_for(2);
quote!(2 => compact.#name.push(
(
index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?,
(
index_of_target(&distribution[0].0).ok_or(_phragmen::Error::CompactInvalidIndex)?,
distribution[0].1,
),
index_of_target(&distribution[1].0).ok_or(_phragmen::Error::CompactInvalidIndex)?,
)
),)
};
let from_impl_rest = (3..=count).map(|c| {
let inner = (0..c-1).map(|i|
quote!((index_of_target(&distribution[#i].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, distribution[#i].1),)
).collect::<TokenStream2>();
let field_name = field_name_for(c);
let last_index = c - 1;
let last = quote!(index_of_target(&distribution[#last_index].0).ok_or(_phragmen::Error::CompactInvalidIndex)?);
quote!(
#c => compact.#field_name.push((index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?, [#inner], #last)),
)
}).collect::<TokenStream2>();
quote!(
#from_impl_single
#from_impl_double
#from_impl_rest
)
}
fn into_impl(count: usize) -> TokenStream2 {
let into_impl_single = {
let name = field_name_for(1);
quote!(
for (voter_index, target_index) in self.#name {
assignments.push(_phragmen::Assignment {
who: voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?,
distribution: vec![
(target_at(target_index).ok_or(_phragmen::Error::CompactInvalidIndex)?, Accuracy::one())
],
})
}
)
};
let into_impl_double = {
let name = field_name_for(2);
quote!(
for (voter_index, (t1_idx, p1), t2_idx) in self.#name {
if p1 >= Accuracy::one() {
return Err(_phragmen::Error::CompactStakeOverflow);
}
// defensive only. Since Percent doesn't have `Sub`.
let p2 = _phragmen::sp_runtime::traits::Saturating::saturating_sub(
Accuracy::one(),
p1,
);
assignments.push( _phragmen::Assignment {
who: voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?,
distribution: vec![
(target_at(t1_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, p1),
(target_at(t2_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, p2),
]
});
}
)
};
let into_impl_rest = (3..=count).map(|c| {
let name = field_name_for(c);
quote!(
for (voter_index, inners, t_last_idx) in self.#name {
let mut sum = Accuracy::zero();
let mut inners_parsed = inners
.iter()
.map(|(ref t_idx, p)| {
sum = _phragmen::sp_runtime::traits::Saturating::saturating_add(sum, *p);
let target = target_at(*t_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?;
Ok((target, *p))
})
.collect::<Result<Vec<(A, Accuracy)>, _phragmen::Error>>()?;
if sum >= Accuracy::one() {
return Err(_phragmen::Error::CompactStakeOverflow);
}
// defensive only. Since Percent doesn't have `Sub`.
let p_last = _phragmen::sp_runtime::traits::Saturating::saturating_sub(
Accuracy::one(),
sum,
);
inners_parsed.push((target_at(t_last_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, p_last));
assignments.push(_phragmen::Assignment {
who: voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?,
distribution: inners_parsed,
});
}
)
}).collect::<TokenStream2>();
quote!(
#into_impl_single
#into_impl_double
#into_impl_rest
)
}
pub(crate) fn assignment(
ident: syn::Ident,
voter_type: GenericArgument,
target_type: GenericArgument,
count: usize,
) -> TokenStream2 {
let from_impl = from_impl(count);
let into_impl = into_impl(count);
quote!(
impl<
#voter_type: _phragmen::codec::Codec + Default + Copy,
#target_type: _phragmen::codec::Codec + Default + Copy,
Accuracy:
_phragmen::codec::Codec + Default + Clone + _phragmen::sp_runtime::PerThing +
PartialOrd,
>
#ident<#voter_type, #target_type, Accuracy>
{
pub fn from_assignment<FV, FT, A>(
assignments: Vec<_phragmen::Assignment<A, Accuracy>>,
index_of_voter: FV,
index_of_target: FT,
) -> Result<Self, _phragmen::Error>
where
for<'r> FV: Fn(&'r A) -> Option<#voter_type>,
for<'r> FT: Fn(&'r A) -> Option<#target_type>,
A: _phragmen::IdentifierT,
{
let mut compact: #ident<
#voter_type,
#target_type,
Accuracy,
> = Default::default();
for _phragmen::Assignment { who, distribution } in assignments {
match distribution.len() {
0 => continue,
#from_impl
_ => {
return Err(_phragmen::Error::CompactTargetOverflow);
}
}
};
Ok(compact)
}
pub fn into_assignment<A: _phragmen::IdentifierT>(
self,
voter_at: impl Fn(#voter_type) -> Option<A>,
target_at: impl Fn(#target_type) -> Option<A>,
) -> Result<Vec<_phragmen::Assignment<A, Accuracy>>, _phragmen::Error> {
let mut assignments: Vec<_phragmen::Assignment<A, Accuracy>> = Default::default();
#into_impl
Ok(assignments)
}
}
)
}
@@ -0,0 +1,219 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Proc macro for phragmen compact assignment.
use proc_macro::TokenStream;
use proc_macro2::{TokenStream as TokenStream2, Span, Ident};
use proc_macro_crate::crate_name;
use quote::quote;
use syn::{GenericArgument, Type, parse::{Parse, ParseStream, Result}};
mod assignment;
mod staked;
// prefix used for struct fields in compact.
const PREFIX: &'static str = "votes";
/// Generates a struct to store the phragmen assignments in a compact way. The struct can only store
/// distributions up to the given input count. The given count must be greater than 2.
///
/// ```ignore
/// // generate a struct with nominator and edge weight u128, with maximum supported
/// // edge per voter of 16.
/// generate_compact_solution_type(pub TestCompact, 16)
/// ```
///
/// This generates:
///
/// ```ignore
/// pub struct TestCompact<V, T, W> {
/// votes1: Vec<(V, T)>,
/// votes2: Vec<(V, (T, W), T)>,
/// votes3: Vec<(V, [(T, W); 2usize], T)>,
/// votes4: Vec<(V, [(T, W); 3usize], T)>,
/// votes5: Vec<(V, [(T, W); 4usize], T)>,
/// votes6: Vec<(V, [(T, W); 5usize], T)>,
/// votes7: Vec<(V, [(T, W); 6usize], T)>,
/// votes8: Vec<(V, [(T, W); 7usize], T)>,
/// votes9: Vec<(V, [(T, W); 8usize], T)>,
/// votes10: Vec<(V, [(T, W); 9usize], T)>,
/// votes11: Vec<(V, [(T, W); 10usize], T)>,
/// votes12: Vec<(V, [(T, W); 11usize], T)>,
/// votes13: Vec<(V, [(T, W); 12usize], T)>,
/// votes14: Vec<(V, [(T, W); 13usize], T)>,
/// votes15: Vec<(V, [(T, W); 14usize], T)>,
/// votes16: Vec<(V, [(T, W); 15usize], T)>,
/// }
/// ```
///
/// The generic arguments are:
/// - `V`: identifier/index for voter (nominator) types.
/// - `T` identifier/index for candidate (validator) types.
/// - `W` weight type.
///
/// Some conversion implementations are provided by default if
/// - `W` is u128, or
/// - `W` is anything that implements `PerThing` (such as `Perbill`)
///
/// The ideas behind the structure are as follows:
///
/// - For single distribution, no weight is stored. The weight is known to be 100%.
/// - For all the rest, the weight if the last distribution is omitted. This value can be computed
/// from the rest.
///
#[proc_macro]
pub fn generate_compact_solution_type(item: TokenStream) -> TokenStream {
let CompactSolutionDef {
vis,
ident,
count,
} = syn::parse_macro_input!(item as CompactSolutionDef);
let voter_type = GenericArgument::Type(Type::Verbatim(quote!(V)));
let target_type = GenericArgument::Type(Type::Verbatim(quote!(T)));
let weight_type = GenericArgument::Type(Type::Verbatim(quote!(W)));
let imports = imports().unwrap_or_else(|e| e.to_compile_error());
let compact_def = struct_def(
vis,
ident.clone(),
count,
voter_type.clone(),
target_type.clone(),
weight_type,
).unwrap_or_else(|e| e.to_compile_error());
let assignment_impls = assignment::assignment(
ident.clone(),
voter_type.clone(),
target_type.clone(),
count,
);
let staked_impls = staked::staked(
ident,
voter_type,
target_type,
count,
);
quote!(
#imports
#compact_def
#assignment_impls
#staked_impls
).into()
}
fn struct_def(
vis: syn::Visibility,
ident: syn::Ident,
count: usize,
voter_type: GenericArgument,
target_type: GenericArgument,
weight_type: GenericArgument,
) -> Result<TokenStream2> {
if count <= 2 {
Err(syn::Error::new(
Span::call_site(),
"cannot build compact solution struct with capacity less than 2."
))?
}
let singles = {
let name = field_name_for(1);
quote!(#name: Vec<(#voter_type, #target_type)>,)
};
let doubles = {
let name = field_name_for(2);
quote!(#name: Vec<(#voter_type, (#target_type, #weight_type), #target_type)>,)
};
let rest = (3..=count).map(|c| {
let field_name = field_name_for(c);
let array_len = c - 1;
quote!(
#field_name: Vec<(
#voter_type,
[(#target_type, #weight_type); #array_len],
#target_type
)>,
)
}).collect::<TokenStream2>();
Ok(quote! (
/// A struct to encode a Phragmen assignment in a compact way.
#[derive(
Default,
PartialEq,
Eq,
Clone,
_phragmen::sp_runtime::RuntimeDebug,
_phragmen::codec::Encode,
_phragmen::codec::Decode,
)]
#vis struct #ident<#voter_type, #target_type, #weight_type> {
// _marker: sp_std::marker::PhantomData<A>,
#singles
#doubles
#rest
}
impl<#voter_type, #target_type, #weight_type> _phragmen::VotingLimit
for #ident<#voter_type, #target_type, #weight_type>
{
const LIMIT: usize = #count;
}
))
}
fn imports() -> Result<TokenStream2> {
let sp_phragmen_imports = match crate_name("sp-phragmen") {
Ok(sp_phragmen) => {
let ident = syn::Ident::new(&sp_phragmen, Span::call_site());
quote!( extern crate #ident as _phragmen; )
}
Err(e) => return Err(syn::Error::new(Span::call_site(), &e)),
};
Ok(quote!(
#sp_phragmen_imports
))
}
struct CompactSolutionDef {
vis: syn::Visibility,
ident: syn::Ident,
count: usize,
}
impl Parse for CompactSolutionDef {
fn parse(input: ParseStream) -> syn::Result<Self> {
let vis: syn::Visibility = input.parse()?;
let ident: syn::Ident = input.parse()?;
let _ = <syn::Token![,]>::parse(input)?;
let count_literal: syn::LitInt = input.parse()?;
let count = count_literal.base10_parse::<usize>()?;
Ok(Self { vis, ident, count } )
}
}
fn field_name_for(n: usize) -> Ident {
Ident::new(&format!("{}{}", PREFIX, n), Span::call_site())
}
@@ -0,0 +1,208 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Code generation for the staked assignment type.
use crate::field_name_for;
use proc_macro2::{TokenStream as TokenStream2};
use syn::{GenericArgument};
use quote::quote;
fn from_impl(count: usize) -> TokenStream2 {
let from_impl_single = {
let name = field_name_for(1);
quote!(1 => compact.#name.push(
(
index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?,
index_of_target(&distribution[0].0).ok_or(_phragmen::Error::CompactInvalidIndex)?,
)
),)
};
let from_impl_double = {
let name = field_name_for(2);
quote!(2 => compact.#name.push(
(
index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?,
(
index_of_target(&distribution[0].0).ok_or(_phragmen::Error::CompactInvalidIndex)?,
distribution[0].1,
),
index_of_target(&distribution[1].0).ok_or(_phragmen::Error::CompactInvalidIndex)?,
)
),)
};
let from_impl_rest = (3..=count).map(|c| {
let inner = (0..c-1).map(|i|
quote!((index_of_target(&distribution[#i].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, distribution[#i].1),)
).collect::<TokenStream2>();
let field_name = field_name_for(c);
let last_index = c - 1;
let last = quote!(index_of_target(&distribution[#last_index].0).ok_or(_phragmen::Error::CompactInvalidIndex)?);
quote!(
#c => compact.#field_name.push((index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?, [#inner], #last)),
)
}).collect::<TokenStream2>();
quote!(
#from_impl_single
#from_impl_double
#from_impl_rest
)
}
fn into_impl(count: usize) -> TokenStream2 {
let into_impl_single = {
let name = field_name_for(1);
quote!(
for (voter_index, target_index) in self.#name {
let who = voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?;
let all_stake = max_of(&who);
assignments.push(_phragmen::StakedAssignment {
who,
distribution: vec![(target_at(target_index).ok_or(_phragmen::Error::CompactInvalidIndex)?, all_stake)],
})
}
)
};
let into_impl_double = {
let name = field_name_for(2);
quote!(
for (voter_index, (t1_idx, w1), t2_idx) in self.#name {
let who = voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?;
let all_stake = max_of(&who);
if w1 >= all_stake {
return Err(_phragmen::Error::CompactStakeOverflow);
}
// w2 is ensured to be positive.
let w2 = all_stake - w1;
assignments.push( _phragmen::StakedAssignment {
who,
distribution: vec![
(target_at(t1_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, w1),
(target_at(t2_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, w2),
]
});
}
)
};
let into_impl_rest = (3..=count).map(|c| {
let name = field_name_for(c);
quote!(
for (voter_index, inners, t_last_idx) in self.#name {
let who = voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?;
let mut sum = u128::min_value();
let all_stake = max_of(&who);
let mut inners_parsed = inners
.iter()
.map(|(ref t_idx, w)| {
sum = sum.saturating_add(*w);
let target = target_at(*t_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?;
Ok((target, *w))
}).collect::<Result<Vec<(A, u128)>, _phragmen::Error>>()?;
if sum >= all_stake {
return Err(_phragmen::Error::CompactStakeOverflow);
}
// w_last is proved to be positive.
let w_last = all_stake - sum;
inners_parsed.push((target_at(t_last_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, w_last));
assignments.push(_phragmen::StakedAssignment {
who,
distribution: inners_parsed,
});
}
)
}).collect::<TokenStream2>();
quote!(
#into_impl_single
#into_impl_double
#into_impl_rest
)
}
pub(crate) fn staked(
ident: syn::Ident,
voter_type: GenericArgument,
target_type: GenericArgument,
count: usize,
) -> TokenStream2 {
let from_impl = from_impl(count);
let into_impl = into_impl(count);
quote!(
impl<
#voter_type: _phragmen::codec::Codec + Default + Copy,
#target_type: _phragmen::codec::Codec + Default + Copy,
>
#ident<#voter_type, #target_type, u128>
{
/// Generate self from a vector of `StakedAssignment`.
pub fn from_staked<FV, FT, A>(
assignments: Vec<_phragmen::StakedAssignment<A>>,
index_of_voter: FV,
index_of_target: FT,
) -> Result<Self, _phragmen::Error>
where
for<'r> FV: Fn(&'r A) -> Option<#voter_type>,
for<'r> FT: Fn(&'r A) -> Option<#target_type>,
A: _phragmen::IdentifierT
{
let mut compact: #ident<#voter_type, #target_type, u128> = Default::default();
for _phragmen::StakedAssignment { who, distribution } in assignments {
match distribution.len() {
0 => continue,
#from_impl
_ => {
return Err(_phragmen::Error::CompactTargetOverflow);
}
}
};
Ok(compact)
}
/// Convert self into `StakedAssignment`. The given function should return the total
/// weight of a voter. It is used to subtract the sum of all the encoded weights to
/// infer the last one.
pub fn into_staked<FM, A>(
self,
max_of: FM,
voter_at: impl Fn(#voter_type) -> Option<A>,
target_at: impl Fn(#target_type) -> Option<A>,
)
-> Result<Vec<_phragmen::StakedAssignment<A>>, _phragmen::Error>
where
for<'r> FM: Fn(&'r A) -> u128,
A: _phragmen::IdentifierT,
{
let mut assignments: Vec<_phragmen::StakedAssignment<A>> = Default::default();
#into_impl
Ok(assignments)
}
}
)
}
@@ -0,0 +1,2 @@
hfuzz_target
hfuzz_workspace
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,16 @@
[package]
name = "sp-phragmen-fuzzer"
version = "2.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[dependencies]
sp-phragmen = { version = "2.0.0-alpha.3", path = ".." }
honggfuzz = "0.5"
rand = "0.7.3"
[workspace]
[[bin]]
name = "reduce"
path = "src/reduce.rs"
@@ -0,0 +1,145 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! # Running
//!
//! Run with `cargo hfuzz run reduce`. `honggfuzz`.
//!
//! # Debugging a panic
//!
//! Once a panic is found, it can be debugged with
//! `cargo hfuzz run-debug reduce hfuzz_workspace/reduce/*.fuzz`.
use honggfuzz::fuzz;
use sp_phragmen::{StakedAssignment, ExtendedBalance, build_support_map, reduce};
use rand::{self, Rng};
type Balance = u128;
type AccountId = u64;
/// Or any other token type.
const KSM: Balance = 1_000_000_000_000;
fn main() {
loop {
fuzz!(|_data: _| {
let (assignments, winners) = generate_random_phragmen_assignment(
rr(100, 1000),
rr(100, 2000),
8,
8,
);
reduce_and_compare(&assignments, &winners);
});
}
}
fn generate_random_phragmen_assignment(
voter_count: usize,
target_count: usize,
avg_edge_per_voter: usize,
edge_per_voter_var: usize,
) -> (Vec<StakedAssignment<AccountId>>, Vec<AccountId>) {
// random in range of (a, b)
let rr_128 = |a: u128, b: u128| -> u128 { rand::thread_rng().gen_range(a, b) };
// prefix to distinguish the voter and target account ranges.
let target_prefix = 1_000_000;
// let target_prefix = 1000;
assert!(voter_count < target_prefix);
let mut assignments = Vec::with_capacity(voter_count as usize);
let mut winners: Vec<AccountId> = Vec::new();
let all_targets = (target_prefix..(target_prefix + target_count))
.map(|a| a as AccountId)
.collect::<Vec<AccountId>>();
(1..=voter_count).for_each(|acc| {
let mut targets_to_chose_from = all_targets.clone();
let targets_to_chose = if edge_per_voter_var > 0 { rr(
avg_edge_per_voter - edge_per_voter_var,
avg_edge_per_voter + edge_per_voter_var,
) } else { avg_edge_per_voter };
let distribution = (0..targets_to_chose).map(|_| {
let target = targets_to_chose_from.remove(rr(0, targets_to_chose_from.len()));
if winners.iter().find(|w| **w == target).is_none() {
winners.push(target.clone());
}
(target, rr_128(1 * KSM, 100 * KSM))
}).collect::<Vec<(AccountId, ExtendedBalance)>>();
assignments.push(StakedAssignment {
who: (acc as AccountId),
distribution,
});
});
(assignments, winners)
}
fn assert_assignments_equal(
winners: &Vec<AccountId>,
ass1: &Vec<StakedAssignment<AccountId>>,
ass2: &Vec<StakedAssignment<AccountId>>,
) {
let (support_1, _) = build_support_map::<AccountId>(winners, ass1);
let (support_2, _) = build_support_map::<AccountId>(winners, ass2);
for (who, support) in support_1.iter() {
assert_eq!(support.total, support_2.get(who).unwrap().total);
}
}
fn reduce_and_compare(
assignment: &Vec<StakedAssignment<AccountId>>,
winners: &Vec<AccountId>,
) {
let mut altered_assignment = assignment.clone();
let n = assignment.len() as u32;
let m = winners.len() as u32;
let edges_before = assignment_len(&assignment);
let num_changed = reduce(&mut altered_assignment);
let edges_after = edges_before - num_changed;
assert!(
edges_after <= m + n,
"reduce bound not satisfied. n = {}, m = {}, edges after reduce = {} (removed {})",
n,
m,
edges_after,
num_changed,
);
assert_assignments_equal(
winners,
&assignment,
&altered_assignment,
);
}
fn assignment_len(assignments: &[StakedAssignment<AccountId>]) -> u32 {
let mut counter = 0;
assignments.iter().for_each(|x| x.distribution.iter().for_each(|_| counter += 1));
counter
}
fn rr(a: usize, b: usize) -> usize {
rand::thread_rng().gen_range(a, b)
}
@@ -0,0 +1,94 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Helper methods for phragmen.
use crate::{Assignment, ExtendedBalance, IdentifierT, StakedAssignment};
use sp_runtime::PerThing;
use sp_std::prelude::*;
/// Converts a vector of ratio assignments into ones with absolute budget value.
pub fn assignment_ratio_to_staked<A: IdentifierT, T: PerThing, FS>(
ratio: Vec<Assignment<A, T>>,
stake_of: FS,
) -> Vec<StakedAssignment<A>>
where
for<'r> FS: Fn(&'r A) -> ExtendedBalance,
T: sp_std::ops::Mul<ExtendedBalance, Output = ExtendedBalance>,
ExtendedBalance: From<<T as PerThing>::Inner>,
{
ratio
.into_iter()
.map(|a| {
let stake = stake_of(&a.who);
a.into_staked(stake, true)
})
.collect()
}
/// Converts a vector of staked assignments into ones with ratio values.
pub fn assignment_staked_to_ratio<A: IdentifierT, T: PerThing>(
ratio: Vec<StakedAssignment<A>>,
) -> Vec<Assignment<A, T>>
where
ExtendedBalance: From<<T as PerThing>::Inner>,
{
ratio.into_iter().map(|a| a.into_assignment(true)).collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ExtendedBalance;
use sp_runtime::Perbill;
#[test]
fn into_staked_works() {
let ratio = vec![
Assignment {
who: 1u32,
distribution: vec![
(10u32, Perbill::from_fraction(0.5)),
(20, Perbill::from_fraction(0.5)),
],
},
Assignment {
who: 2u32,
distribution: vec![
(10, Perbill::from_fraction(0.33)),
(20, Perbill::from_fraction(0.67)),
],
},
];
let stake_of = |_: &u32| -> ExtendedBalance { 100u128 };
let staked = assignment_ratio_to_staked(ratio, stake_of);
assert_eq!(
staked,
vec![
StakedAssignment {
who: 1u32,
distribution: vec![(10u32, 50), (20, 50),]
},
StakedAssignment {
who: 2u32,
distribution: vec![(10u32, 33), (20, 67),]
}
]
);
}
}
+316 -69
View File
@@ -33,19 +33,60 @@
#![cfg_attr(not(feature = "std"), no_std)]
use sp_std::{prelude::*, collections::btree_map::BTreeMap, convert::TryFrom};
use sp_runtime::{
PerThing, Rational128, RuntimeDebug,
helpers_128bit::multiply_by_rational,
};
use sp_runtime::traits::{
Zero, Convert, Member, AtLeast32Bit, SaturatedConversion, Bounded, Saturating,
};
use sp_std::{prelude::*, collections::btree_map::BTreeMap, fmt::Debug, cmp::Ordering, convert::TryFrom};
use sp_runtime::{helpers_128bit::multiply_by_rational, PerThing, Rational128, RuntimeDebug, SaturatedConversion};
use sp_runtime::traits::{Zero, Convert, Member, AtLeast32Bit, Saturating, Bounded};
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "std")]
use serde::{Serialize, Deserialize};
#[cfg(feature = "std")]
use codec::{Encode, Decode};
mod node;
mod reduce;
mod helpers;
// re-export reduce stuff.
pub use reduce::reduce;
// re-export the helpers.
pub use helpers::*;
// re-export the compact macro, with the dependencies of the macro.
#[doc(hidden)]
pub use codec;
#[doc(hidden)]
pub use sp_runtime;
// re-export the compact solution type.
pub use sp_phragmen_compact::generate_compact_solution_type;
/// A trait to limit the number of votes per voter. The generated compact type will implement this.
pub trait VotingLimit {
const LIMIT: usize;
}
/// an aggregator trait for a generic type of a voter/target identifier. This usually maps to
/// substrate's account id.
pub trait IdentifierT: Clone + Eq + Default + Ord + Debug + codec::Codec {}
impl<T: Clone + Eq + Default + Ord + Debug + codec::Codec> IdentifierT for T {}
/// The errors that might occur in the this crate and compact.
#[derive(Debug, Eq, PartialEq)]
pub enum Error {
/// While going from compact to staked, the stake of all the edges has gone above the
/// total and the last stake cannot be assigned.
CompactStakeOverflow,
/// The compact type has a voter who's number of targets is out of bound.
CompactTargetOverflow,
/// One of the index functions returned none.
CompactInvalidIndex,
}
/// A type in which performing operations on balances and stakes of candidates and voters are safe.
///
@@ -55,6 +96,9 @@ mod tests;
/// Balance types converted to `ExtendedBalance` are referred to as `Votes`.
pub type ExtendedBalance = u128;
/// The score of an assignment. This can be computed from the support map via [`evaluate_support`].
pub type PhragmenScore = [ExtendedBalance; 3];
/// The denominator used for loads. Since votes are collected as u64, the smallest ratio that we
/// might collect is `1/approval_stake` where approval stake is the sum of votes. Hence, some number
/// bigger than u64::max_value() is needed. For maximum accuracy we simply use u128;
@@ -62,11 +106,11 @@ const DEN: u128 = u128::max_value();
/// A candidate entity for phragmen election.
#[derive(Clone, Default, RuntimeDebug)]
pub struct Candidate<AccountId> {
struct Candidate<AccountId> {
/// Identifier.
pub who: AccountId,
who: AccountId,
/// Intermediary value used to sort candidates.
pub score: Rational128,
score: Rational128,
/// Sum of the stake of this candidate based on received votes.
approval_stake: ExtendedBalance,
/// Flag for being elected.
@@ -75,7 +119,7 @@ pub struct Candidate<AccountId> {
/// A voter entity.
#[derive(Clone, Default, RuntimeDebug)]
pub struct Voter<AccountId> {
struct Voter<AccountId> {
/// Identifier.
who: AccountId,
/// List of candidates proposed by this voter.
@@ -88,7 +132,7 @@ pub struct Voter<AccountId> {
/// A candidate being backed by a voter.
#[derive(Clone, Default, RuntimeDebug)]
pub struct Edge<AccountId> {
struct Edge<AccountId> {
/// Identifier.
who: AccountId,
/// Load of this vote.
@@ -97,12 +141,6 @@ pub struct Edge<AccountId> {
candidate_index: usize,
}
/// Particular `AccountId` was backed by `T`-ratio of a nominator's stake.
pub type PhragmenAssignment<AccountId, T> = (AccountId, T);
/// Particular `AccountId` was backed by `ExtendedBalance` of a nominator's stake.
pub type PhragmenStakedAssignment<AccountId> = (AccountId, ExtendedBalance);
/// Final result of the phragmen election.
#[derive(RuntimeDebug)]
pub struct PhragmenResult<AccountId, T: PerThing> {
@@ -111,7 +149,142 @@ pub struct PhragmenResult<AccountId, T: PerThing> {
pub winners: Vec<(AccountId, ExtendedBalance)>,
/// Individual assignments. for each tuple, the first elements is a voter and the second
/// is the list of candidates that it supports.
pub assignments: Vec<(AccountId, Vec<PhragmenAssignment<AccountId, T>>)>
pub assignments: Vec<Assignment<AccountId, T>>,
}
/// A voter's stake assignment among a set of targets, represented as ratios.
#[derive(RuntimeDebug, Clone, Default)]
#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))]
pub struct Assignment<AccountId, T: PerThing> {
/// Voter's identifier
pub who: AccountId,
/// The distribution of the voter's stake.
pub distribution: Vec<(AccountId, T)>,
}
impl<AccountId, T: PerThing> Assignment<AccountId, T>
where
ExtendedBalance: From<<T as PerThing>::Inner>,
{
/// Convert from a ratio assignment into one with absolute values aka. [`StakedAssignment`].
///
/// It needs `stake` which is the total budget of the voter. If `fill` is set to true,
/// it _tries_ to ensure that all the potential rounding errors are compensated and the
/// distribution's sum is exactly equal to the total budget, by adding or subtracting the
/// remainder from the last distribution.
///
/// If an edge ratio is [`Bounded::max_value()`], it is dropped. This edge can never mean
/// anything useful.
pub fn into_staked(self, stake: ExtendedBalance, fill: bool) -> StakedAssignment<AccountId>
where
T: sp_std::ops::Mul<ExtendedBalance, Output = ExtendedBalance>,
{
let mut sum: ExtendedBalance = Bounded::min_value();
let mut distribution = self
.distribution
.into_iter()
.filter_map(|(target, p)| {
// if this ratio is zero, then skip it.
if p == Bounded::min_value() {
None
} else {
// NOTE: this mul impl will always round to the nearest number, so we might both
// overflow and underflow.
let distribution_stake = p * stake;
// defensive only. We assume that balance cannot exceed extended balance.
sum = sum.saturating_add(distribution_stake);
Some((target, distribution_stake))
}
})
.collect::<Vec<(AccountId, ExtendedBalance)>>();
if fill {
// NOTE: we can do this better.
// https://revs.runtime-revolution.com/getting-100-with-rounded-percentages-273ffa70252b
if let Some(leftover) = stake.checked_sub(sum) {
if let Some(last) = distribution.last_mut() {
last.1 = last.1.saturating_add(leftover);
}
} else if let Some(excess) = sum.checked_sub(stake) {
if let Some(last) = distribution.last_mut() {
last.1 = last.1.saturating_sub(excess);
}
}
}
StakedAssignment {
who: self.who,
distribution,
}
}
}
/// A voter's stake assignment among a set of targets, represented as absolute values in the scale
/// of [`ExtendedBalance`].
#[derive(RuntimeDebug, Clone, Default)]
#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))]
pub struct StakedAssignment<AccountId> {
/// Voter's identifier
pub who: AccountId,
/// The distribution of the voter's stake.
pub distribution: Vec<(AccountId, ExtendedBalance)>,
}
impl<AccountId> StakedAssignment<AccountId> {
/// Converts self into the normal [`Assignment`] type.
///
/// If `fill` is set to true, it _tries_ to ensure that all the potential rounding errors are
/// compensated and the distribution's sum is exactly equal to 100%, by adding or subtracting
/// the remainder from the last distribution.
///
/// NOTE: it is quite critical that this attempt always works. The data type returned here will
/// potentially get used to create a compact type; a compact type requires sum of ratios to be
/// less than 100% upon un-compacting.
///
/// If an edge stake is so small that it cannot be represented in `T`, it is ignored. This edge
/// can never be re-created and does not mean anything useful anymore.
pub fn into_assignment<T: PerThing>(self, fill: bool) -> Assignment<AccountId, T>
where
ExtendedBalance: From<<T as PerThing>::Inner>,
{
let accuracy: u128 = T::ACCURACY.saturated_into();
let mut sum: u128 = Zero::zero();
let stake = self.distribution.iter().map(|x| x.1).sum();
let mut distribution = self
.distribution
.into_iter()
.filter_map(|(target, w)| {
let per_thing = T::from_rational_approximation(w, stake);
if per_thing == Bounded::min_value() {
None
} else {
sum += per_thing.clone().deconstruct().saturated_into();
Some((target, per_thing))
}
})
.collect::<Vec<(AccountId, T)>>();
if fill {
if let Some(leftover) = accuracy.checked_sub(sum) {
if let Some(last) = distribution.last_mut() {
last.1 = last.1.saturating_add(
T::from_parts(leftover.saturated_into())
);
}
} else if let Some(excess) = sum.checked_sub(accuracy) {
if let Some(last) = distribution.last_mut() {
last.1 = last.1.saturating_sub(
T::from_parts(excess.saturated_into())
);
}
}
}
Assignment {
who: self.who,
distribution,
}
}
}
/// A structure to demonstrate the phragmen result from the perspective of the candidate, i.e. how
@@ -122,12 +295,12 @@ pub struct PhragmenResult<AccountId, T: PerThing> {
/// This, at the current version, resembles the `Exposure` defined in the Staking pallet, yet
/// they do not necessarily have to be the same.
#[derive(Default, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize, Eq, PartialEq))]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Eq, PartialEq))]
pub struct Support<AccountId> {
/// Total support.
pub total: ExtendedBalance,
/// Support from voters.
pub voters: Vec<PhragmenStakedAssignment<AccountId>>,
pub voters: Vec<(AccountId, ExtendedBalance)>,
}
/// A linkage from a candidate and its [`Support`].
@@ -164,7 +337,7 @@ pub fn elect<AccountId, Balance, C, R>(
// return structures
let mut elected_candidates: Vec<(AccountId, ExtendedBalance)>;
let mut assigned: Vec<(AccountId, Vec<PhragmenAssignment<AccountId, R>>)>;
let mut assigned: Vec<Assignment<AccountId, R>>;
// used to cache and access candidates index.
let mut c_idx_cache = BTreeMap::<AccountId, usize>::new();
@@ -271,7 +444,10 @@ pub fn elect<AccountId, Balance, C, R>(
// update backing stake of candidates and voters
for n in &mut voters {
let mut assignment = (n.who.clone(), vec![]);
let mut assignment = Assignment {
who: n.who.clone(),
..Default::default()
};
for e in &mut n.edges {
if elected_candidates.iter().position(|(ref c, _)| *c == e.who).is_some() {
let per_bill_parts: R::Inner =
@@ -298,44 +474,45 @@ pub fn elect<AccountId, Balance, C, R>(
// R::Inner.
.unwrap_or(Bounded::max_value())
} else {
// defensive only. Both edge and nominator loads are built from
// defensive only. Both edge and voter loads are built from
// scores, hence MUST have the same denominator.
Zero::zero()
}
}
};
let per_thing = R::from_parts(per_bill_parts);
assignment.1.push((e.who.clone(), per_thing));
assignment.distribution.push((e.who.clone(), per_thing));
}
}
if assignment.1.len() > 0 {
// To ensure an assertion indicating: no stake from the nominator going to waste,
let len = assignment.distribution.len();
if len > 0 {
// To ensure an assertion indicating: no stake from the voter going to waste,
// we add a minimal post-processing to equally assign all of the leftover stake ratios.
let vote_count: R::Inner = assignment.1.len().saturated_into();
let len = assignment.1.len();
let mut sum: R::Inner = Zero::zero();
assignment.1.iter().for_each(|a| sum = sum.saturating_add(a.1.deconstruct()));
let vote_count: R::Inner = len.saturated_into();
let accuracy = R::ACCURACY;
let mut sum: R::Inner = Zero::zero();
assignment.distribution.iter().for_each(|a| sum = sum.saturating_add(a.1.deconstruct()));
let diff = accuracy.saturating_sub(sum);
let diff_per_vote = (diff / vote_count).min(accuracy);
if !diff_per_vote.is_zero() {
for i in 0..len {
let current_ratio = assignment.1[i % len].1;
let current_ratio = assignment.distribution[i % len].1;
let next_ratio = current_ratio
.saturating_add(R::from_parts(diff_per_vote));
assignment.1[i % len].1 = next_ratio;
assignment.distribution[i % len].1 = next_ratio;
}
}
// `remainder` is set to be less than maximum votes of a nominator (currently 16).
// `remainder` is set to be less than maximum votes of a voter (currently 16).
// safe to cast it to usize.
let remainder = diff - diff_per_vote * vote_count;
for i in 0..remainder.saturated_into::<usize>() {
let current_ratio = assignment.1[i % len].1;
let current_ratio = assignment.distribution[i % len].1;
let next_ratio = current_ratio.saturating_add(R::from_parts(1u8.into()));
assignment.1[i % len].1 = next_ratio;
assignment.distribution[i % len].1 = next_ratio;
}
assigned.push(assignment);
}
@@ -347,39 +524,109 @@ pub fn elect<AccountId, Balance, C, R>(
})
}
/// Build the support map from the given phragmen result.
pub fn build_support_map<Balance, AccountId, FS, C, R>(
elected_stashes: &Vec<AccountId>,
assignments: &Vec<(AccountId, Vec<PhragmenAssignment<AccountId, R>>)>,
stake_of: FS,
) -> SupportMap<AccountId> where
/// Build the support map from the given phragmen result. It maps a flat structure like
///
/// ```nocompile
/// assignments: vec![
/// voter1, vec![(candidate1, w11), (candidate2, w12)],
/// voter2, vec![(candidate1, w21), (candidate2, w22)]
/// ]
/// ```
///
/// into a mapping of candidates and their respective support:
///
/// ```nocompile
/// SupportMap {
/// candidate1: Support {
/// own:0,
/// total: w11 + w21,
/// others: vec![(candidate1, w11), (candidate2, w21)]
/// },
/// candidate2: Support {
/// own:0,
/// total: w12 + w22,
/// others: vec![(candidate1, w12), (candidate2, w22)]
/// },
/// }
/// ```
///
/// The second returned flag indicates the number of edges who didn't corresponded to an actual
/// winner from the given winner set. A value in this place larger than 0 indicates a potentially
/// faulty assignment.
///
/// `O(E)` where `E` is the total number of edges.
pub fn build_support_map<AccountId>(
winners: &[AccountId],
assignments: &[StakedAssignment<AccountId>],
) -> (SupportMap<AccountId>, u32) where
AccountId: Default + Ord + Member,
Balance: Default + Copy + AtLeast32Bit,
C: Convert<Balance, u64> + Convert<u128, Balance>,
for<'r> FS: Fn(&'r AccountId) -> Balance,
R: PerThing + sp_std::ops::Mul<ExtendedBalance, Output=ExtendedBalance>,
{
let to_votes = |b: Balance| <C as Convert<Balance, u64>>::convert(b) as ExtendedBalance;
let mut errors = 0;
// Initialize the support of each candidate.
let mut supports = <SupportMap<AccountId>>::new();
elected_stashes
winners
.iter()
.for_each(|e| { supports.insert(e.clone(), Default::default()); });
// build support struct.
for (n, assignment) in assignments.iter() {
for (c, per_thing) in assignment.iter() {
let nominator_stake = to_votes(stake_of(n));
// AUDIT: it is crucially important for the `Mul` implementation of all
// per-things to be sound.
let other_stake = *per_thing * nominator_stake;
for StakedAssignment { who, distribution } in assignments.iter() {
for (c, weight_extended) in distribution.iter() {
if let Some(support) = supports.get_mut(c) {
support.voters.push((n.clone(), other_stake));
support.total = support.total.saturating_add(other_stake);
support.total = support.total.saturating_add(*weight_extended);
support.voters.push((who.clone(), *weight_extended));
} else {
errors = errors.saturating_add(1);
}
}
}
supports
(supports, errors)
}
/// Evaluate a phragmen result, given the support map. The returned tuple contains:
///
/// - Minimum support. This value must be **maximized**.
/// - Sum of all supports. This value must be **maximized**.
/// - Sum of all supports squared. This value must be **minimized**.
///
/// `O(E)` where `E` is the total number of edges.
pub fn evaluate_support<AccountId>(
support: &SupportMap<AccountId>,
) -> PhragmenScore {
let mut min_support = ExtendedBalance::max_value();
let mut sum: ExtendedBalance = Zero::zero();
// NOTE: this will probably saturate but using big num makes it even slower. We'll have to see.
// This must run on chain..
let mut sum_squared: ExtendedBalance = Zero::zero();
for (_, support) in support.iter() {
sum += support.total;
let squared = support.total.saturating_mul(support.total);
sum_squared = sum_squared.saturating_add(squared);
if support.total < min_support {
min_support = support.total;
}
}
[min_support, sum, sum_squared]
}
/// Compares two sets of phragmen scores based on desirability and returns true if `that` is
/// better than `this`.
///
/// Evaluation is done in a lexicographic manner.
///
/// Note that the third component should be minimized.
pub fn is_score_better(this: PhragmenScore, that: PhragmenScore) -> bool {
match that
.iter()
.enumerate()
.map(|(i, e)| e.cmp(&this[i]))
.collect::<Vec<Ordering>>()
.as_slice()
{
[Ordering::Greater, _, _] => true,
[Ordering::Equal, Ordering::Greater, _] => true,
[Ordering::Equal, Ordering::Equal, Ordering::Less] => true,
_ => false,
}
}
/// Performs equalize post-processing to the output of the election algorithm. This happens in
@@ -388,13 +635,13 @@ pub fn build_support_map<Balance, AccountId, FS, C, R>(
///
/// No value is returned from the function and the `supports` parameter is updated.
///
/// * `assignments`: exactly the same is the output of phragmen.
/// * `supports`: mutable reference to s `SupportMap`. This parameter is updated.
/// * `tolerance`: maximum difference that can occur before an early quite happens.
/// * `iterations`: maximum number of iterations that will be processed.
/// * `stake_of`: something that can return the stake stake of a particular candidate or voter.
/// - `assignments`: exactly the same is the output of phragmen.
/// - `supports`: mutable reference to s `SupportMap`. This parameter is updated.
/// - `tolerance`: maximum difference that can occur before an early quite happens.
/// - `iterations`: maximum number of iterations that will be processed.
/// - `stake_of`: something that can return the stake stake of a particular candidate or voter.
pub fn equalize<Balance, AccountId, C, FS>(
mut assignments: Vec<(AccountId, Vec<PhragmenStakedAssignment<AccountId>>)>,
mut assignments: Vec<StakedAssignment<AccountId>>,
supports: &mut SupportMap<AccountId>,
tolerance: ExtendedBalance,
iterations: usize,
@@ -408,13 +655,13 @@ pub fn equalize<Balance, AccountId, C, FS>(
for _i in 0..iterations {
let mut max_diff = 0;
for (voter, assignment) in assignments.iter_mut() {
let voter_budget = stake_of(&voter);
for StakedAssignment { who, distribution } in assignments.iter_mut() {
let voter_budget = stake_of(&who);
let diff = do_equalize::<_, _, C>(
voter,
who,
voter_budget,
assignment,
distribution,
supports,
tolerance,
);
@@ -432,7 +679,7 @@ pub fn equalize<Balance, AccountId, C, FS>(
fn do_equalize<Balance, AccountId, C>(
voter: &AccountId,
budget_balance: Balance,
elected_edges: &mut Vec<PhragmenStakedAssignment<AccountId>>,
elected_edges: &mut Vec<(AccountId, ExtendedBalance)>,
support_map: &mut SupportMap<AccountId>,
tolerance: ExtendedBalance
) -> ExtendedBalance where
+17 -16
View File
@@ -18,10 +18,10 @@
#![cfg(test)]
use crate::{elect, PhragmenResult, PhragmenAssignment};
use crate::{elect, PhragmenResult, Assignment};
use sp_runtime::{
assert_eq_error_rate, Perbill, PerThing,
traits::{Convert, Member, SaturatedConversion}
assert_eq_error_rate, PerThing,
traits::{Convert, Member, SaturatedConversion, Zero, One}
};
use sp_std::collections::btree_map::BTreeMap;
@@ -320,22 +320,23 @@ pub(crate) fn create_stake_of(stakes: &[(AccountId, Balance)])
}
pub fn check_assignments(assignments: Vec<(AccountId, Vec<PhragmenAssignment<AccountId, Perbill>>)>) {
for (_, a) in assignments {
let sum: u32 = a.iter().map(|(_, p)| p.deconstruct()).sum();
assert_eq_error_rate!(sum, Perbill::ACCURACY, 5);
pub fn check_assignments_sum<T: PerThing>(assignments: Vec<Assignment<AccountId, T>>) {
for Assignment { distribution, .. } in assignments {
let mut sum: u128 = Zero::zero();
distribution.iter().for_each(|(_, p)| sum += p.deconstruct().saturated_into());
assert_eq_error_rate!(sum, T::ACCURACY.saturated_into(), 1);
}
}
pub(crate) fn run_and_compare(
pub(crate) fn run_and_compare<Output: PerThing>(
candidates: Vec<AccountId>,
voters: Vec<(AccountId, Vec<AccountId>)>,
stake_of: Box<dyn Fn(&AccountId) -> Balance>,
stake_of: &Box<dyn Fn(&AccountId) -> Balance>,
to_elect: usize,
min_to_elect: usize,
) {
// run fixed point code.
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>(
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>(
to_elect,
min_to_elect,
candidates.clone(),
@@ -353,14 +354,14 @@ pub(crate) fn run_and_compare(
assert_eq!(winners, truth_value.winners);
for (nominator, assigned) in assignments.clone() {
if let Some(float_assignments) = truth_value.assignments.iter().find(|x| x.0 == nominator) {
for (candidate, per_thingy) in assigned {
for Assignment { who, distribution } in assignments.clone() {
if let Some(float_assignments) = truth_value.assignments.iter().find(|x| x.0 == who) {
for (candidate, per_thingy) in distribution {
if let Some(float_assignment) = float_assignments.1.iter().find(|x| x.0 == candidate ) {
assert_eq_error_rate!(
Perbill::from_fraction(float_assignment.1).deconstruct(),
Output::from_fraction(float_assignment.1).deconstruct(),
per_thingy.deconstruct(),
1,
Output::Inner::one(),
);
} else {
panic!("candidate mismatch. This should never happen.")
@@ -371,7 +372,7 @@ pub(crate) fn run_and_compare(
}
}
check_assignments(assignments);
check_assignments_sum(assignments);
}
pub(crate) fn build_support_map_float<FS>(
+287
View File
@@ -0,0 +1,287 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! (very) Basic implementation of a graph node used in the reduce algorithm.
use sp_runtime::RuntimeDebug;
use sp_std::{cell::RefCell, fmt, prelude::*, rc::Rc};
/// The role that a node can accept.
#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, RuntimeDebug)]
pub(crate) enum NodeRole {
/// A voter. This is synonym to a nominator in a staking context.
Voter,
/// A target. This is synonym to a candidate/validator in a staking context.
Target,
}
pub(crate) type RefCellOf<T> = Rc<RefCell<T>>;
pub(crate) type NodeRef<A> = RefCellOf<Node<A>>;
/// Identifier of a node. This is particularly handy to have a proper `PartialEq` implementation.
/// Otherwise, self votes wouldn't have been indistinguishable.
#[derive(PartialOrd, Ord, Clone, PartialEq, Eq)]
pub(crate) struct NodeId<A> {
/// An account-like identifier representing the node.
pub who: A,
/// The role of the node.
pub role: NodeRole,
}
impl<A> NodeId<A> {
/// Create a new [`NodeId`].
pub fn from(who: A, role: NodeRole) -> Self {
Self { who, role }
}
}
#[cfg(feature = "std")]
impl<A: fmt::Debug> sp_std::fmt::Debug for NodeId<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> sp_std::fmt::Result {
write!(
f,
"Node({:?}, {:?})",
self.who,
if self.role == NodeRole::Voter {
"V"
} else {
"T"
}
)
}
}
/// A one-way graph note. This can only store a pointer to its parent.
#[derive(Clone)]
pub(crate) struct Node<A> {
/// The identifier of the note.
pub(crate) id: NodeId<A>,
/// The parent pointer.
pub(crate) parent: Option<NodeRef<A>>,
}
impl<A: PartialEq> PartialEq for Node<A> {
fn eq(&self, other: &Node<A>) -> bool {
self.id == other.id
}
}
impl<A: PartialEq> Eq for Node<A> {}
#[cfg(feature = "std")]
impl<A: fmt::Debug + Clone> fmt::Debug for Node<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"({:?} --> {:?})",
self.id,
self.parent.as_ref().map(|p| p.borrow().id.clone())
)
}
}
impl<A: PartialEq + Eq + Clone + fmt::Debug> Node<A> {
/// Create a new [`Node`]
pub fn new(id: NodeId<A>) -> Node<A> {
Self { id, parent: None }
}
/// Returns true if `other` is the parent of `who`.
pub fn is_parent_of(who: &NodeRef<A>, other: &NodeRef<A>) -> bool {
if who.borrow().parent.is_none() {
return false;
}
who.borrow().parent.as_ref() == Some(other)
}
/// Removes the parent of `who`.
pub fn remove_parent(who: &NodeRef<A>) {
who.borrow_mut().parent = None;
}
/// Sets `who`'s parent to be `parent`.
pub fn set_parent_of(who: &NodeRef<A>, parent: &NodeRef<A>) {
who.borrow_mut().parent = Some(parent.clone());
}
/// Finds the root of `start`. It return a tuple of `(root, root_vec)` where `root_vec` is the
/// vector of Nodes leading to the root. Hence the first element is the start itself and the
/// last one is the root. As convenient, the root itself is also returned as the first element
/// of the tuple.
///
/// This function detects cycles and breaks as soon a duplicate node is visited, returning the
/// cycle up to but not including the duplicate node.
///
/// If you are certain that no cycles exist, you can use [`root_unchecked`].
pub fn root(start: &NodeRef<A>) -> (NodeRef<A>, Vec<NodeRef<A>>) {
let mut parent_path: Vec<NodeRef<A>> = Vec::new();
let mut visited: Vec<NodeRef<A>> = Vec::new();
parent_path.push(start.clone());
visited.push(start.clone());
let mut current = start.clone();
while let Some(ref next_parent) = current.clone().borrow().parent {
if visited.contains(next_parent) {
break;
}
parent_path.push(next_parent.clone());
current = next_parent.clone();
visited.push(current.clone());
}
(current, parent_path)
}
/// Consumes self and wraps it in a `Rc<RefCell<T>>`. This type can be used as the pointer type
/// to a parent node.
pub fn into_ref(self) -> NodeRef<A> {
Rc::from(RefCell::from(self))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn id(i: u32) -> NodeId<u32> {
NodeId::from(i, NodeRole::Target)
}
#[test]
fn basic_create_works() {
let node = Node::new(id(10));
assert_eq!(
node,
Node {
id: NodeId {
who: 10,
role: NodeRole::Target
},
parent: None
}
);
}
#[test]
fn set_parent_works() {
let a = Node::new(id(10)).into_ref();
let b = Node::new(id(20)).into_ref();
assert_eq!(a.borrow().parent, None);
Node::set_parent_of(&a, &b);
assert_eq!(*a.borrow().parent.as_ref().unwrap(), b);
}
#[test]
fn get_root_singular() {
let a = Node::new(id(1)).into_ref();
assert_eq!(Node::root(&a), (a.clone(), vec![a.clone()]));
}
#[test]
fn get_root_works() {
// D <-- A <-- B <-- C
// \
// <-- E
let a = Node::new(id(1)).into_ref();
let b = Node::new(id(2)).into_ref();
let c = Node::new(id(3)).into_ref();
let d = Node::new(id(4)).into_ref();
let e = Node::new(id(5)).into_ref();
let f = Node::new(id(6)).into_ref();
Node::set_parent_of(&c, &b);
Node::set_parent_of(&b, &a);
Node::set_parent_of(&e, &a);
Node::set_parent_of(&a, &d);
assert_eq!(
Node::root(&e),
(d.clone(), vec![e.clone(), a.clone(), d.clone()]),
);
assert_eq!(Node::root(&a), (d.clone(), vec![a.clone(), d.clone()]),);
assert_eq!(
Node::root(&c),
(d.clone(), vec![c.clone(), b.clone(), a.clone(), d.clone()]),
);
// D A <-- B <-- C
// F <-- / \
// <-- E
Node::set_parent_of(&a, &f);
assert_eq!(Node::root(&a), (f.clone(), vec![a.clone(), f.clone()]),);
assert_eq!(
Node::root(&c),
(f.clone(), vec![c.clone(), b.clone(), a.clone(), f.clone()]),
);
}
#[test]
fn get_root_on_cycle() {
// A ---> B
// | |
// <---- C
let a = Node::new(id(1)).into_ref();
let b = Node::new(id(2)).into_ref();
let c = Node::new(id(3)).into_ref();
Node::set_parent_of(&a, &b);
Node::set_parent_of(&b, &c);
Node::set_parent_of(&c, &a);
let (root, path) = Node::root(&a);
assert_eq!(root, c);
assert_eq!(path.clone(), vec![a.clone(), b.clone(), c.clone()]);
}
#[test]
fn get_root_on_cycle_2() {
// A ---> B
// | | |
// - C
let a = Node::new(id(1)).into_ref();
let b = Node::new(id(2)).into_ref();
let c = Node::new(id(3)).into_ref();
Node::set_parent_of(&a, &b);
Node::set_parent_of(&b, &c);
Node::set_parent_of(&c, &b);
let (root, path) = Node::root(&a);
assert_eq!(root, c);
assert_eq!(path.clone(), vec![a.clone(), b.clone(), c.clone()]);
}
#[test]
fn node_cmp_stack_overflows_on_non_unique_elements() {
// To make sure we don't stack overflow on duplicate who. This needs manual impl of
// PartialEq.
let a = Node::new(id(1)).into_ref();
let b = Node::new(id(2)).into_ref();
let c = Node::new(id(3)).into_ref();
Node::set_parent_of(&a, &b);
Node::set_parent_of(&b, &c);
Node::set_parent_of(&c, &a);
Node::root(&a);
}
}
File diff suppressed because it is too large Load Diff
+556 -63
View File
@@ -19,11 +19,12 @@
#![cfg(test)]
use crate::mock::*;
use crate::{elect, PhragmenResult, PhragmenStakedAssignment, build_support_map, Support, equalize};
use crate::{
elect, equalize, build_support_map, is_score_better,
Support, StakedAssignment, Assignment, PhragmenResult, ExtendedBalance,
};
use substrate_test_utils::assert_eq_uvec;
use sp_runtime::Perbill;
type Output = Perbill;
use sp_runtime::{Perbill, Permill, Percent, PerU16, traits::Convert};
#[test]
fn float_phragmen_poc_works() {
@@ -81,7 +82,7 @@ fn phragmen_poc_works() {
];
let stake_of = create_stake_of(&[(10, 10), (20, 20), (30, 30)]);
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>(
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>(
2,
2,
candidates,
@@ -92,9 +93,21 @@ fn phragmen_poc_works() {
assert_eq_uvec!(
assignments,
vec![
(10, vec![(2, Perbill::from_percent(100))]),
(20, vec![(3, Perbill::from_percent(100))]),
(30, vec![(2, Perbill::from_percent(100/2)), (3, Perbill::from_percent(100/2))]),
Assignment {
who: 10u64,
distribution: vec![(2, Perbill::from_percent(100))],
},
Assignment {
who: 20,
distribution: vec![(3, Perbill::from_percent(100))],
},
Assignment {
who: 30,
distribution: vec![
(2, Perbill::from_percent(100/2)),
(3, Perbill::from_percent(100/2)),
],
},
]
);
}
@@ -115,7 +128,10 @@ fn phragmen_poc_2_works() {
(4, 500),
]);
run_and_compare(candidates, voters, stake_of, 2, 2);
run_and_compare::<Perbill>(candidates.clone(), voters.clone(), &stake_of, 2, 2);
run_and_compare::<Permill>(candidates.clone(), voters.clone(), &stake_of, 2, 2);
run_and_compare::<Percent>(candidates.clone(), voters.clone(), &stake_of, 2, 2);
run_and_compare::<PerU16>(candidates, voters, &stake_of, 2, 2);
}
#[test]
@@ -133,7 +149,10 @@ fn phragmen_poc_3_works() {
(4, 1000),
]);
run_and_compare(candidates, voters, stake_of, 2, 2);
run_and_compare::<Perbill>(candidates.clone(), voters.clone(), &stake_of, 2, 2);
run_and_compare::<Permill>(candidates.clone(), voters.clone(), &stake_of, 2, 2);
run_and_compare::<Percent>(candidates.clone(), voters.clone(), &stake_of, 2, 2);
run_and_compare::<PerU16>(candidates, voters, &stake_of, 2, 2);
}
#[test]
@@ -149,7 +168,7 @@ fn phragmen_accuracy_on_large_scale_only_validators() {
(5, (u64::max_value() - 2).into()),
]);
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>(
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>(
2,
2,
candidates.clone(),
@@ -158,7 +177,7 @@ fn phragmen_accuracy_on_large_scale_only_validators() {
assert_eq_uvec!(winners, vec![(1, 18446744073709551614u128), (5, 18446744073709551613u128)]);
assert_eq!(assignments.len(), 2);
check_assignments(assignments);
check_assignments_sum(assignments);
}
#[test]
@@ -179,7 +198,7 @@ fn phragmen_accuracy_on_large_scale_validators_and_nominators() {
(14, u64::max_value().into()),
]);
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>(
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>(
2,
2,
candidates,
@@ -190,13 +209,25 @@ fn phragmen_accuracy_on_large_scale_validators_and_nominators() {
assert_eq!(
assignments,
vec![
(13, vec![(1, Perbill::one())]),
(14, vec![(2, Perbill::one())]),
(1, vec![(1, Perbill::one())]),
(2, vec![(2, Perbill::one())]),
Assignment {
who: 13u64,
distribution: vec![(1, Perbill::one())],
},
Assignment {
who: 14,
distribution: vec![(2, Perbill::one())],
},
Assignment {
who: 1,
distribution: vec![(1, Perbill::one())],
},
Assignment {
who: 2,
distribution: vec![(2, Perbill::one())],
},
]
);
check_assignments(assignments);
check_assignments_sum(assignments);
}
#[test]
@@ -210,7 +241,7 @@ fn phragmen_accuracy_on_small_scale_self_vote() {
(30, 1),
]);
let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Output>(
let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Perbill>(
3,
3,
candidates,
@@ -240,7 +271,7 @@ fn phragmen_accuracy_on_small_scale_no_self_vote() {
(3, 1),
]);
let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Output>(
let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Perbill>(
3,
3,
candidates,
@@ -273,7 +304,7 @@ fn phragmen_large_scale_test() {
(50, 990000000000000000),
]);
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>(
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>(
2,
2,
candidates,
@@ -281,7 +312,7 @@ fn phragmen_large_scale_test() {
).unwrap();
assert_eq_uvec!(winners, vec![(24, 1490000000000200000u128), (22, 1490000000000100000u128)]);
check_assignments(assignments);
check_assignments_sum(assignments);
}
#[test]
@@ -299,7 +330,7 @@ fn phragmen_large_scale_test_2() {
(50, nom_budget.into()),
]);
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>(
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>(
2,
2,
candidates,
@@ -310,12 +341,24 @@ fn phragmen_large_scale_test_2() {
assert_eq!(
assignments,
vec![
(50, vec![(2, Perbill::from_parts(500000001)), (4, Perbill::from_parts(499999999))]),
(2, vec![(2, Perbill::one())]),
(4, vec![(4, Perbill::one())]),
Assignment {
who: 50u64,
distribution: vec![
(2, Perbill::from_parts(500000001)),
(4, Perbill::from_parts(499999999))
],
},
Assignment {
who: 2,
distribution: vec![(2, Perbill::one())],
},
Assignment {
who: 4,
distribution: vec![(4, Perbill::one())],
},
],
);
check_assignments(assignments);
check_assignments_sum(assignments);
}
#[test]
@@ -348,7 +391,7 @@ fn phragmen_linear_equalize() {
(130, 1000),
]);
run_and_compare(candidates, voters, stake_of, 2, 2);
run_and_compare::<Perbill>(candidates, voters, &stake_of, 2, 2);
}
#[test]
@@ -363,7 +406,7 @@ fn elect_has_no_entry_barrier() {
(2, 10),
]);
let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Output>(
let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Perbill>(
3,
3,
candidates,
@@ -390,7 +433,7 @@ fn minimum_to_elect_is_respected() {
(2, 10),
]);
let maybe_result = elect::<_, _, TestCurrencyToVote, Output>(
let maybe_result = elect::<_, _, TestCurrencyToVote, Perbill>(
10,
10,
candidates,
@@ -416,7 +459,7 @@ fn self_votes_should_be_kept() {
(1, 8),
]);
let result = elect::<_, _, TestCurrencyToVote, Output>(
let result = elect::<_, _, TestCurrencyToVote, Perbill>(
2,
2,
candidates,
@@ -427,27 +470,28 @@ fn self_votes_should_be_kept() {
assert_eq!(
result.assignments,
vec![
(10, vec![(10, Perbill::from_percent(100))]),
(20, vec![(20, Perbill::from_percent(100))]),
(1, vec![
Assignment { who: 10, distribution: vec![(10, Perbill::from_percent(100))] },
Assignment { who: 20, distribution: vec![(20, Perbill::from_percent(100))] },
Assignment { who: 1, distribution: vec![
(10, Perbill::from_percent(50)),
(20, Perbill::from_percent(50))
]
)
},
],
);
let mut supports = build_support_map::<
Balance,
AccountId,
_,
TestCurrencyToVote,
Output,
>(
&result.winners.into_iter().map(|(who, _)| who).collect(),
&result.assignments,
&stake_of
);
let staked_assignments: Vec<StakedAssignment<AccountId>> = result.assignments
.into_iter()
.map(|a| {
let stake = <TestCurrencyToVote as Convert<Balance, u64>>::convert(stake_of(&a.who)) as ExtendedBalance;
a.into_staked(stake, true)
}).collect();
let winners = result.winners.into_iter().map(|(who, _)| who).collect::<Vec<AccountId>>();
let (mut supports, _) = build_support_map::<AccountId>(
winners.as_slice(),
&staked_assignments,
);
assert_eq!(supports.get(&5u64), None);
assert_eq!(
@@ -459,23 +503,13 @@ fn self_votes_should_be_kept() {
&Support { total: 24u128, voters: vec![(20u64, 20u128), (1u64, 4u128)] },
);
let assignments = result.assignments;
let mut staked_assignments
: Vec<(AccountId, Vec<PhragmenStakedAssignment<AccountId>>)>
= Vec::with_capacity(assignments.len());
for (n, assignment) in assignments.iter() {
let mut staked_assignment
: Vec<PhragmenStakedAssignment<AccountId>>
= Vec::with_capacity(assignment.len());
let stake = stake_of(&n);
for (c, per_thing) in assignment.iter() {
let vote_stake = *per_thing * stake;
staked_assignment.push((c.clone(), vote_stake));
}
staked_assignments.push((n.clone(), staked_assignment));
}
equalize::<Balance, AccountId, TestCurrencyToVote, _>(staked_assignments, &mut supports, 0, 2usize, &stake_of);
equalize::<Balance, AccountId, TestCurrencyToVote, _>(
staked_assignments,
&mut supports,
0,
2usize,
&stake_of,
);
assert_eq!(
supports.get(&10u64).unwrap(),
@@ -486,3 +520,462 @@ fn self_votes_should_be_kept() {
&Support { total: 20u128, voters: vec![(20u64, 20u128)] },
);
}
#[test]
fn assignment_convert_works() {
let staked = StakedAssignment {
who: 1 as AccountId,
distribution: vec![
(20, 100 as Balance),
(30, 25),
],
};
let assignment = staked.clone().into_assignment(true);
assert_eq!(
assignment,
Assignment {
who: 1,
distribution: vec![
(20, Perbill::from_percent(80)),
(30, Perbill::from_percent(20)),
]
}
);
assert_eq!(
assignment.into_staked(125, true),
staked,
);
}
#[test]
fn score_comparison_is_lexicographical() {
// only better in the fist parameter, worse in the other two ✅
assert_eq!(
is_score_better([10, 20, 30], [12, 10, 35]),
true,
);
// worse in the first, better in the other two ❌
assert_eq!(
is_score_better([10, 20, 30], [9, 30, 10]),
false,
);
// equal in the first, the second one dictates.
assert_eq!(
is_score_better([10, 20, 30], [10, 25, 40]),
true,
);
// equal in the first two, the last one dictates.
assert_eq!(
is_score_better([10, 20, 30], [10, 20, 40]),
false,
);
}
mod compact {
use codec::{Decode, Encode};
use crate::generate_compact_solution_type;
use super::{AccountId, Balance};
// these need to come from the same dev-dependency `sp-phragmen`, not from the crate.
use sp_phragmen::{Assignment, StakedAssignment, Error as PhragmenError};
use sp_std::{convert::{TryInto, TryFrom}, fmt::Debug};
use sp_runtime::Percent;
type Accuracy = Percent;
generate_compact_solution_type!(TestCompact, 16);
#[test]
fn compact_struct_is_codec() {
let compact = TestCompact::<_, _, _> {
votes1: vec![(2u64, 20), (4, 40)],
votes2: vec![
(1, (10, Accuracy::from_percent(80)), 11),
(5, (50, Accuracy::from_percent(85)), 51),
],
..Default::default()
};
let encoded = compact.encode();
assert_eq!(
compact,
Decode::decode(&mut &encoded[..]).unwrap(),
);
}
fn basic_ratio_test_with<V, T>() where
V: codec::Codec + Copy + Default + PartialEq + Eq + TryInto<usize> + TryFrom<usize> + From<u8> + Debug,
T: codec::Codec + Copy + Default + PartialEq + Eq + TryInto<usize> + TryFrom<usize> + From<u8> + Debug,
<V as TryFrom<usize>>::Error: std::fmt::Debug,
<T as TryFrom<usize>>::Error: std::fmt::Debug,
<V as TryInto<usize>>::Error: std::fmt::Debug,
<T as TryInto<usize>>::Error: std::fmt::Debug,
{
let voters = vec![
2 as AccountId,
4,
1,
5,
3,
];
let targets = vec![
10 as AccountId,
11,
20, // 2
30,
31, // 4
32,
40, // 6
50,
51, // 8
];
let assignments = vec![
Assignment {
who: 2 as AccountId,
distribution: vec![(20u64, Accuracy::from_percent(100))]
},
Assignment {
who: 4,
distribution: vec![(40, Accuracy::from_percent(100))],
},
Assignment {
who: 1,
distribution: vec![
(10, Accuracy::from_percent(80)),
(11, Accuracy::from_percent(20))
],
},
Assignment {
who: 5,
distribution: vec![
(50, Accuracy::from_percent(85)),
(51, Accuracy::from_percent(15)),
]
},
Assignment {
who: 3,
distribution: vec![
(30, Accuracy::from_percent(50)),
(31, Accuracy::from_percent(25)),
(32, Accuracy::from_percent(25)),
],
},
];
let voter_index = |a: &AccountId| -> Option<V> {
voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok()
};
let target_index = |a: &AccountId| -> Option<T> {
targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok()
};
let compacted = <TestCompact<V, T, Percent>>::from_assignment(
assignments.clone(),
voter_index,
target_index,
).unwrap();
assert_eq!(
compacted,
TestCompact {
votes1: vec![(V::from(0u8), T::from(2u8)), (V::from(1u8), T::from(6u8))],
votes2: vec![
(V::from(2u8), (T::from(0u8), Accuracy::from_percent(80)), T::from(1u8)),
(V::from(3u8), (T::from(7u8), Accuracy::from_percent(85)), T::from(8u8)),
],
votes3: vec![
(
V::from(4),
[(T::from(3u8), Accuracy::from_percent(50)), (T::from(4u8), Accuracy::from_percent(25))],
T::from(5u8),
),
],
..Default::default()
}
);
let voter_at = |a: V| -> Option<AccountId> { voters.get(<V as TryInto<usize>>::try_into(a).unwrap()).cloned() };
let target_at = |a: T| -> Option<AccountId> { targets.get(<T as TryInto<usize>>::try_into(a).unwrap()).cloned() };
assert_eq!(
compacted.into_assignment(voter_at, target_at).unwrap(),
assignments,
);
}
#[test]
fn basic_from_and_into_compact_works_assignments() {
basic_ratio_test_with::<u16, u16>();
basic_ratio_test_with::<u16, u32>();
basic_ratio_test_with::<u8, u32>();
}
#[test]
fn basic_from_and_into_compact_works_staked_assignments() {
let voters = vec![
2 as AccountId,
4,
1,
5,
3,
];
let targets = vec![
10 as AccountId, 11,
20,
30, 31, 32,
40,
50, 51,
];
let assignments = vec![
StakedAssignment {
who: 2 as AccountId,
distribution: vec![(20, 100 as Balance)]
},
StakedAssignment {
who: 4,
distribution: vec![(40, 100)],
},
StakedAssignment {
who: 1,
distribution: vec![
(10, 80),
(11, 20)
],
},
StakedAssignment {
who: 5, distribution:
vec![
(50, 85),
(51, 15),
]
},
StakedAssignment {
who: 3,
distribution: vec![
(30, 50),
(31, 25),
(32, 25),
],
},
];
let voter_index = |a: &AccountId| -> Option<u16> {
voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok()
};
let target_index = |a: &AccountId| -> Option<u16> {
targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok()
};
let compacted = <TestCompact<u16, u16, Balance>>::from_staked(
assignments.clone(),
voter_index,
target_index,
).unwrap();
assert_eq!(
compacted,
TestCompact {
votes1: vec![(0, 2), (1, 6)],
votes2: vec![
(2, (0, 80), 1),
(3, (7, 85), 8),
],
votes3: vec![
(4, [(3, 50), (4, 25)], 5),
],
..Default::default()
}
);
let max_of_fn = |_: &AccountId| -> Balance { 100u128 };
let voter_at = |a: u16| -> Option<AccountId> { voters.get(a as usize).cloned() };
let target_at = |a: u16| -> Option<AccountId> { targets.get(a as usize).cloned() };
assert_eq!(
compacted.into_staked(
max_of_fn,
voter_at,
target_at,
).unwrap(),
assignments,
);
}
#[test]
fn compact_into_stake_must_report_overflow() {
// The last edge which is computed from the rest should ALWAYS be positive.
// in votes2
let compact = TestCompact::<u16, u16, Balance> {
votes1: Default::default(),
votes2: vec![(0, (1, 10), 2)],
..Default::default()
};
let entity_at = |a: u16| -> Option<AccountId> { Some(a as AccountId) };
let max_of = |_: &AccountId| -> Balance { 5 };
assert_eq!(
compact.into_staked(&max_of, &entity_at, &entity_at).unwrap_err(),
PhragmenError::CompactStakeOverflow,
);
// in votes3 onwards
let compact = TestCompact::<u16, u16, Balance> {
votes1: Default::default(),
votes2: Default::default(),
votes3: vec![(0, [(1, 7), (2, 8)], 3)],
..Default::default()
};
assert_eq!(
compact.into_staked(&max_of, &entity_at, &entity_at).unwrap_err(),
PhragmenError::CompactStakeOverflow,
);
// Also if equal
let compact = TestCompact::<u16, u16, Balance> {
votes1: Default::default(),
votes2: Default::default(),
// 5 is total, we cannot leave none for 30 here.
votes3: vec![(0, [(1, 3), (2, 2)], 3)],
..Default::default()
};
assert_eq!(
compact.into_staked(&max_of, &entity_at, &entity_at).unwrap_err(),
PhragmenError::CompactStakeOverflow,
);
}
#[test]
fn compact_into_assignment_must_report_overflow() {
// in votes2
let compact = TestCompact::<u16, u16, Accuracy> {
votes1: Default::default(),
votes2: vec![(0, (1, Accuracy::from_percent(100)), 2)],
..Default::default()
};
let entity_at = |a: u16| -> Option<AccountId> { Some(a as AccountId) };
assert_eq!(
compact.into_assignment(&entity_at, &entity_at).unwrap_err(),
PhragmenError::CompactStakeOverflow,
);
// in votes3 onwards
let compact = TestCompact::<u16, u16, Accuracy> {
votes1: Default::default(),
votes2: Default::default(),
votes3: vec![(0, [(1, Accuracy::from_percent(70)), (2, Accuracy::from_percent(80))], 3)],
..Default::default()
};
assert_eq!(
compact.into_assignment(&entity_at, &entity_at).unwrap_err(),
PhragmenError::CompactStakeOverflow,
);
}
#[test]
fn target_count_overflow_is_detected() {
let assignments = vec![
StakedAssignment {
who: 1 as AccountId,
distribution: (10..26).map(|i| (i as AccountId, i as Balance)).collect::<Vec<_>>(),
},
];
let entity_index = |a: &AccountId| -> Option<u16> { Some(*a as u16) };
let compacted = <TestCompact<u16, u16, Balance>>::from_staked(
assignments.clone(),
entity_index,
entity_index,
);
assert!(compacted.is_ok());
let assignments = vec![
StakedAssignment {
who: 1 as AccountId,
distribution: (10..27).map(|i| (i as AccountId, i as Balance)).collect::<Vec<_>>(),
},
];
let compacted = <TestCompact<u16, u16, Balance>>::from_staked(
assignments.clone(),
entity_index,
entity_index,
);
assert_eq!(
compacted.unwrap_err(),
PhragmenError::CompactTargetOverflow,
);
let assignments = vec![
Assignment {
who: 1 as AccountId,
distribution: (10..27).map(|i| (i as AccountId, Percent::from_parts(i as u8))).collect::<Vec<_>>(),
},
];
let compacted = <TestCompact<u16, u16, Percent>>::from_assignment(
assignments.clone(),
entity_index,
entity_index,
);
assert_eq!(
compacted.unwrap_err(),
PhragmenError::CompactTargetOverflow,
);
}
#[test]
fn zero_target_count_is_ignored() {
let voters = vec![1 as AccountId, 2];
let targets = vec![10 as AccountId, 11];
let assignments = vec![
StakedAssignment {
who: 1 as AccountId,
distribution: vec![(10, 100 as Balance), (11, 100)]
},
StakedAssignment {
who: 2,
distribution: vec![],
},
];
let voter_index = |a: &AccountId| -> Option<u16> {
voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok()
};
let target_index = |a: &AccountId| -> Option<u16> {
targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok()
};
let compacted = <TestCompact<u16, u16, Balance>>::from_staked(
assignments.clone(),
voter_index,
target_index,
).unwrap();
assert_eq!(
compacted,
TestCompact {
votes1: Default::default(),
votes2: vec![(0, (0, 100), 1)],
..Default::default()
}
);
}
}
+4 -1
View File
@@ -68,7 +68,10 @@ pub use sp_application_crypto::{RuntimeAppPublic, BoundToRuntimeAppPublic};
pub use sp_core::RuntimeDebug;
/// Re-export top-level arithmetic stuff.
pub use sp_arithmetic::{Perquintill, Perbill, Permill, Percent, Rational128, Fixed64, PerThing};
pub use sp_arithmetic::{
Perquintill, Perbill, Permill, Percent, PerU16, Rational128, Fixed64, PerThing,
traits::SaturatedConversion,
};
/// Re-export 128 bit helpers.
pub use sp_arithmetic::helpers_128bit;
/// Re-export big_uint stuff.
+1 -1
View File
@@ -929,7 +929,7 @@ pub trait OpaqueKeys: Clone {
fn key_ids() -> &'static [crate::KeyTypeId];
/// Get the raw bytes of key with key-type ID `i`.
fn get_raw(&self, i: super::KeyTypeId) -> &[u8];
/// Get the decoded key with index `i`.
/// Get the decoded key with key-type ID `i`.
fn get<T: Decode>(&self, i: super::KeyTypeId) -> Option<T> {
T::decode(&mut self.get_raw(i)).ok()
}
+13 -2
View File
@@ -142,11 +142,20 @@ pub trait OnOffenceHandler<Reporter, Offender> {
/// Zero is a valid value for a fraction.
///
/// The `session` parameter is the session index of the offence.
///
/// The receiver might decide to not accept this offence. In this case, the call site is
/// responsible for queuing the report and re-submitting again.
fn on_offence(
offenders: &[OffenceDetails<Reporter, Offender>],
slash_fraction: &[Perbill],
session: SessionIndex,
);
) -> Result<(), ()>;
/// Can an offence be reported now or not. This is an method to short-circuit a call into
/// `on_offence`. Ideally, a correct implementation should return `false` if `on_offence` will
/// return `Err`. Nonetheless, this is up to the implementation and this trait cannot guarantee
/// it.
fn can_report() -> bool;
}
impl<Reporter, Offender> OnOffenceHandler<Reporter, Offender> for () {
@@ -154,7 +163,9 @@ impl<Reporter, Offender> OnOffenceHandler<Reporter, Offender> for () {
_offenders: &[OffenceDetails<Reporter, Offender>],
_slash_fraction: &[Perbill],
_session: SessionIndex,
) {}
) -> Result<(), ()> { Ok(()) }
fn can_report() -> bool { true }
}
/// A details about an offending authority for a particular kind of offence.