mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-08 01:58:00 +00:00
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:
Generated
+124
-116
@@ -70,12 +70,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alga"
|
||||
version = "0.9.2"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "658f9468113d34781f6ca9d014d174c74b73de870f1e0e3ad32079bbab253b19"
|
||||
checksum = "4f823d037a7ec6ea2197046bafd4ae150e6bc36f9ca347404f46a46823fa84f2"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"libm",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
]
|
||||
@@ -100,9 +99,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.26"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c"
|
||||
checksum = "013a6e0a2cbe3d20f9c60b65458f7a7f7a5e636c5d0f45a5a6aee5d4b1f01785"
|
||||
|
||||
[[package]]
|
||||
name = "app_dirs"
|
||||
@@ -133,9 +132,9 @@ checksum = "75153c95fdedd7db9732dfbfc3702324a1627eec91ba56e37cd0ac78314ab2ed"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "0.4.4"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff"
|
||||
checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
@@ -179,9 +178,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6283bac8dd7226470d491bc4737816fea4ca1fba7a2847f2e9097fd6bfb4624c"
|
||||
checksum = "35ad62275a8bda1c2c9a9303aea121eb04204272d3be0735d5dc1f49eb9ff9a9"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
"escargot",
|
||||
@@ -269,9 +268,9 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.45"
|
||||
version = "0.3.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8"
|
||||
checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e"
|
||||
dependencies = [
|
||||
"backtrace-sys",
|
||||
"cfg-if",
|
||||
@@ -281,9 +280,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.34"
|
||||
version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69"
|
||||
checksum = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -457,9 +456,9 @@ checksum = "b170cd256a3f9fa6b9edae3e44a7dfdfc77e8124dbc3e2612d75f9c3e2396dae"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.11"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "502ae1441a0a5adb8fbd38a5955a6416b9493e92b465de5e4a9bde6a539c2c48"
|
||||
checksum = "2889e6d50f394968c8bf4240dc3f2a7eb4680844d27308f798229ac9d4725f41"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"memchr",
|
||||
@@ -1109,9 +1108,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "807e5847c39ad6a11eac66de492ed1406f76a260eb8656e8740cad9eabc69c27"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
@@ -1411,9 +1410,9 @@ checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.13"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f"
|
||||
checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crc32fast",
|
||||
@@ -1917,9 +1916,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.4"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2"
|
||||
checksum = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
@@ -1972,16 +1971,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d5c295d1c0c68e4e42003d75f908f5e16a1edd1cbe0b0d02e4dc2006a384f47"
|
||||
checksum = "7938e6aa2a31df4e21f224dc84704bd31c089a6d1355c535b03667371cccc843"
|
||||
dependencies = [
|
||||
"bytes 0.5.4",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.0",
|
||||
"http 0.2.1",
|
||||
"indexmap",
|
||||
"log 0.4.8",
|
||||
"slab",
|
||||
@@ -2102,9 +2101,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b"
|
||||
checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
|
||||
dependencies = [
|
||||
"bytes 0.5.4",
|
||||
"fnv",
|
||||
@@ -2130,7 +2129,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b"
|
||||
dependencies = [
|
||||
"bytes 0.5.4",
|
||||
"http 0.2.0",
|
||||
"http 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2199,16 +2198,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.13.3"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7b15203263d1faa615f9337d79c1d37959439dc46c2b4faab33286fadc2a1c5"
|
||||
checksum = "ed6081100e960d9d74734659ffc9cc91daf1c0fc7aceb8eaa94ee1a3f5046f2e"
|
||||
dependencies = [
|
||||
"bytes 0.5.4",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.2.2",
|
||||
"http 0.2.0",
|
||||
"h2 0.2.3",
|
||||
"http 0.2.1",
|
||||
"http-body 0.3.1",
|
||||
"httparse",
|
||||
"itoa",
|
||||
@@ -2230,7 +2229,7 @@ dependencies = [
|
||||
"bytes 0.5.4",
|
||||
"ct-logs",
|
||||
"futures-util",
|
||||
"hyper 0.13.3",
|
||||
"hyper 0.13.4",
|
||||
"log 0.4.8",
|
||||
"rustls 0.17.0",
|
||||
"rustls-native-certs",
|
||||
@@ -2353,9 +2352,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a859057dc563d1388c1e816f98a1892629075fc046ed06e845b883bb8b2916fb"
|
||||
checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
@@ -2383,9 +2382,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.36"
|
||||
version = "0.3.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cb931d43e71f560c81badb0191596562bafad2be06a3f9025b845c847c60df5"
|
||||
checksum = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -2620,9 +2619,9 @@ checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.67"
|
||||
version = "0.2.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
|
||||
checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@@ -2636,9 +2635,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.1.4"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
|
||||
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
|
||||
|
||||
[[package]]
|
||||
name = "libp2p"
|
||||
@@ -3212,11 +3211,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9"
|
||||
checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
"autocfg 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3439,6 +3438,7 @@ dependencies = [
|
||||
"pallet-contracts",
|
||||
"pallet-im-online",
|
||||
"pallet-indices",
|
||||
"pallet-staking",
|
||||
"pallet-timestamp",
|
||||
"pallet-transaction-payment",
|
||||
"parity-scale-codec",
|
||||
@@ -3834,9 +3834,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da4dc79f9e6c81bef96148c8f6b8e72ad4541caa4a24373e900a36da07de03a3"
|
||||
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"num-bigint",
|
||||
@@ -3851,6 +3851,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4035,12 +4036,9 @@ version = "2.0.0-alpha.5"
|
||||
dependencies = [
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"hex-literal",
|
||||
"lazy_static",
|
||||
"pallet-session",
|
||||
"pallet-timestamp",
|
||||
"parity-scale-codec",
|
||||
"parking_lot 0.10.0",
|
||||
"serde",
|
||||
"sp-consensus-babe",
|
||||
"sp-consensus-vrf",
|
||||
@@ -4051,8 +4049,6 @@ dependencies = [
|
||||
"sp-staking",
|
||||
"sp-std",
|
||||
"sp-timestamp",
|
||||
"sp-version",
|
||||
"substrate-test-runtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4512,25 +4508,30 @@ dependencies = [
|
||||
name = "pallet-staking"
|
||||
version = "2.0.0-alpha.5"
|
||||
dependencies = [
|
||||
"env_logger 0.7.1",
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"hex",
|
||||
"pallet-authorship",
|
||||
"pallet-balances",
|
||||
"pallet-indices",
|
||||
"pallet-session",
|
||||
"pallet-staking-reward-curve",
|
||||
"pallet-timestamp",
|
||||
"parity-scale-codec",
|
||||
"parking_lot 0.10.0",
|
||||
"rand 0.7.3",
|
||||
"rand_chacha 0.2.2",
|
||||
"serde",
|
||||
"sp-application-crypto",
|
||||
"sp-core",
|
||||
"sp-io",
|
||||
"sp-keyring",
|
||||
"sp-phragmen",
|
||||
"sp-runtime",
|
||||
"sp-staking",
|
||||
"sp-std",
|
||||
"static_assertions",
|
||||
"substrate-test-utils",
|
||||
]
|
||||
|
||||
@@ -4845,9 +4846,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e1afe738d71b1ebab5f1207c055054015427dbfc7bbe9ee1266894156ec046"
|
||||
checksum = "8292c1e1e81ddb552c4c90c36af201a0ce7e34995f55f0480f01052f242811c9"
|
||||
dependencies = [
|
||||
"paste-impl",
|
||||
"proc-macro-hack",
|
||||
@@ -4855,9 +4856,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "paste-impl"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d4dc4a7f6f743211c5aab239640a65091535d97d43d92a52bca435a640892bb"
|
||||
checksum = "5e9c43f2645f06ee452544ad032886a75f3d1797b9487dcadcae9100ba58a51c"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
@@ -5033,9 +5034,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "0.4.11"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7959c6467d962050d639361f7703b2051c43036d03493c36f01d440fdd3138a"
|
||||
checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
@@ -5046,9 +5047,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "0.4.11"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4002d9f55991d5e019fb940a90e1a95eb80c24e77cb2462dd4dc869604d543a"
|
||||
checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5059,20 +5060,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.11"
|
||||
version = "0.5.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
checksum = "fcfdefadc3d57ca21cf17990a28ef4c0f7c61383a28cb7604cf4a18e6ede1420"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-nested"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e"
|
||||
checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
@@ -5150,9 +5146,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "2.10.2"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37a5325d019a4d837d3abde0a836920f959e33d350f77b5f1e289e061e774942"
|
||||
checksum = "fc1b4a8efc42cf150049e8a490f618c7c60e82332405065f202a7e33aa5a1f06"
|
||||
|
||||
[[package]]
|
||||
name = "pwasm-utils"
|
||||
@@ -5466,9 +5462,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.3.4"
|
||||
version = "1.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8"
|
||||
checksum = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -5487,9 +5483,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.16"
|
||||
version = "0.6.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1132f845907680735a84409c3bebc64d1364a5683ffbce899550cd09d5eaefc1"
|
||||
checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
|
||||
|
||||
[[package]]
|
||||
name = "region"
|
||||
@@ -5529,9 +5525,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rlp"
|
||||
version = "0.4.4"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a44d5ae8afcb238af8b75640907edc6c931efcfab2c854e81ed35fa080f84cd"
|
||||
checksum = "4a7d3f9bed94764eac15b8f14af59fac420c236adaff743b7bcc88e265cb4345"
|
||||
dependencies = [
|
||||
"rustc-hex",
|
||||
]
|
||||
@@ -5657,9 +5653,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
|
||||
checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76"
|
||||
|
||||
[[package]]
|
||||
name = "safe-mix"
|
||||
@@ -6412,7 +6408,7 @@ dependencies = [
|
||||
"fnv",
|
||||
"futures 0.3.4",
|
||||
"futures-timer 3.0.2",
|
||||
"hyper 0.13.3",
|
||||
"hyper 0.13.4",
|
||||
"hyper-rustls",
|
||||
"log 0.4.8",
|
||||
"num_cpus",
|
||||
@@ -6705,9 +6701,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.17"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "507a9e6e8ffe0a4e0ebb9a10293e62fdf7657c06f1b8bb07a8fcf697d2abf295"
|
||||
checksum = "039c25b130bd8c1321ee2d7de7fde2659fa9c2744e4bb29711cfc852ea53cd19"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"winapi 0.3.8",
|
||||
@@ -7031,7 +7027,7 @@ dependencies = [
|
||||
"bytes 0.5.4",
|
||||
"flate2",
|
||||
"futures 0.3.4",
|
||||
"http 0.2.0",
|
||||
"http 0.2.1",
|
||||
"httparse",
|
||||
"log 0.4.8",
|
||||
"rand 0.7.3",
|
||||
@@ -7405,14 +7401,26 @@ dependencies = [
|
||||
name = "sp-phragmen"
|
||||
version = "2.0.0-alpha.5"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"sp-io",
|
||||
"sp-phragmen",
|
||||
"sp-phragmen-compact",
|
||||
"sp-runtime",
|
||||
"sp-std",
|
||||
"substrate-test-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sp-phragmen-compact"
|
||||
version = "2.0.0-dev"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sp-rpc"
|
||||
version = "2.0.0-alpha.5"
|
||||
@@ -7722,9 +7730,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "structopt"
|
||||
version = "0.3.11"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fe43617218c0805c6eb37160119dc3c548110a67786da7218d1c6555212f073"
|
||||
checksum = "c8faa2719539bbe9d77869bfb15d4ee769f99525e707931452c97b693b3f159d"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"lazy_static",
|
||||
@@ -7733,9 +7741,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "structopt-derive"
|
||||
version = "0.4.4"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6e79c80e0f4efd86ca960218d4e056249be189ff1c42824dcd9a7f51a56f0bd"
|
||||
checksum = "3f88b8e18c69496aad6f9ddf4630dd7d585bcaf765786cb415b9aec2fe5a0430"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
@@ -7881,7 +7889,7 @@ dependencies = [
|
||||
"async-std",
|
||||
"derive_more",
|
||||
"futures-util",
|
||||
"hyper 0.13.3",
|
||||
"hyper 0.13.4",
|
||||
"log 0.4.8",
|
||||
"prometheus",
|
||||
"tokio 0.2.13",
|
||||
@@ -8086,9 +8094,9 @@ checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.16"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859"
|
||||
checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -8198,18 +8206,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.11"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee14bf8e6767ab4c687c9e8bc003879e042a96fd67a3ba5934eadb6536bef4db"
|
||||
checksum = "e3711fd1c4e75b3eff12ba5c40dba762b6b65c5476e8174c1a664772060c49bf"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.11"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7b51e1fbc44b5a0840be594fbc0f960be09050f2617e61e6aa43bef97cd3ef4"
|
||||
checksum = "ae2b85ba4c9aa32dd3343bd80eb8d22e9b54b7688c17ea3907f236885353b233"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -8247,9 +8255,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tiny-bip39"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6848cd8f566953ce1e8faeba12ee23cbdbb0437754792cd857d44628b5685e3"
|
||||
checksum = "e255ec4f7d4aaccbede17dffcfb2e71434d17f5c921d5a06823b8e58a2bcd468"
|
||||
dependencies = [
|
||||
"failure",
|
||||
"hmac",
|
||||
@@ -8897,9 +8905,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wabt-sys"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af5d153dc96aad7dc13ab90835b892c69867948112d95299e522d370c4e13a08"
|
||||
checksum = "23d7043ebb3e5d96fad7a8d3ca22ee9880748ff8c3e18092cfb2a49d3b8f9084"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
@@ -8946,9 +8954,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.59"
|
||||
version = "0.2.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3557c397ab5a8e347d434782bcd31fc1483d927a6826804cec05cc792ee2519d"
|
||||
checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
@@ -8956,9 +8964,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.59"
|
||||
version = "0.2.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0da9c9a19850d3af6df1cb9574970b566d617ecfaf36eb0b706b6f3ef9bd2f8"
|
||||
checksum = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
@@ -8971,9 +8979,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.9"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "457414a91863c0ec00090dba537f88ab955d93ca6555862c29b6d860990b8a8a"
|
||||
checksum = "7add542ea1ac7fdaa9dc25e031a6af33b7d63376292bd24140c637d00d1c312a"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -8983,9 +8991,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.59"
|
||||
version = "0.2.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f6fde1d36e75a714b5fe0cffbb78978f222ea6baebb726af13c78869fdb4205"
|
||||
checksum = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -8993,9 +9001,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.59"
|
||||
version = "0.2.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25bda4168030a6412ea8a047e27238cadf56f0e53516e1e83fec0a8b7c786f6d"
|
||||
checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9006,9 +9014,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.59"
|
||||
version = "0.2.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc9f36ad51f25b0219a3d4d13b90eb44cd075dff8b6280cca015775d7acaddd8"
|
||||
checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-gc-api"
|
||||
@@ -9130,27 +9138,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wast"
|
||||
version = "10.0.0"
|
||||
version = "11.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4efb62ecebf5cc9dbf2954309a20d816289c6550c0597a138b9e811cefc05007"
|
||||
checksum = "df4d67ba9266f4fcaf2e8a1afadc5e2a959e51aecc07b1ecbdf85a6ddaf08bde"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wat"
|
||||
version = "1.0.11"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffdea5e25273cc3a62f3ae3a1a4c7d7996625875b50c0b4475fee6698c2b069c"
|
||||
checksum = "9a9400dc1c8512087b2d974b1b9b0a6c4e6e26e7e8acf629e3e351165a1ed301"
|
||||
dependencies = [
|
||||
"wast",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.36"
|
||||
version = "0.3.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "721c6263e2c66fd44501cc5efbfa2b7dfa775d13e4ea38c46299646ed1f9c70a"
|
||||
checksum = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
||||
@@ -130,6 +130,7 @@ members = [
|
||||
"primitives/offchain",
|
||||
"primitives/panic-handler",
|
||||
"primitives/phragmen",
|
||||
"primitives/phragmen/compact",
|
||||
"primitives/rpc",
|
||||
"primitives/runtime-interface",
|
||||
"primitives/runtime-interface/proc-macro",
|
||||
|
||||
@@ -82,6 +82,7 @@ pallet-transaction-payment = { version = "2.0.0-alpha.5", path = "../../../frame
|
||||
frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/support" }
|
||||
pallet-im-online = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/im-online" }
|
||||
pallet-authority-discovery = { version = "2.0.0-alpha.5", path = "../../../frame/authority-discovery" }
|
||||
pallet-staking = { version = "2.0.0-alpha.5", path = "../../../frame/staking" }
|
||||
|
||||
# node-specific dependencies
|
||||
node-runtime = { version = "2.0.0-alpha.5", path = "../runtime" }
|
||||
|
||||
@@ -202,7 +202,14 @@ pub fn get_authority_keys_from_seed(seed: &str) -> (
|
||||
|
||||
/// Helper function to create GenesisConfig for testing
|
||||
pub fn testnet_genesis(
|
||||
initial_authorities: Vec<(AccountId, AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId)>,
|
||||
initial_authorities: Vec<(
|
||||
AccountId,
|
||||
AccountId,
|
||||
GrandpaId,
|
||||
BabeId,
|
||||
ImOnlineId,
|
||||
AuthorityDiscoveryId,
|
||||
)>,
|
||||
root_key: AccountId,
|
||||
endowed_accounts: Option<Vec<AccountId>>,
|
||||
enable_println: bool,
|
||||
@@ -244,7 +251,12 @@ pub fn testnet_genesis(
|
||||
}),
|
||||
pallet_session: Some(SessionConfig {
|
||||
keys: initial_authorities.iter().map(|x| {
|
||||
(x.0.clone(), x.0.clone(), session_keys(x.2.clone(), x.3.clone(), x.4.clone(), x.5.clone()))
|
||||
(x.0.clone(), x.0.clone(), session_keys(
|
||||
x.2.clone(),
|
||||
x.3.clone(),
|
||||
x.4.clone(),
|
||||
x.5.clone(),
|
||||
))
|
||||
}).collect::<Vec<_>>(),
|
||||
}),
|
||||
pallet_staking: Some(StakingConfig {
|
||||
|
||||
@@ -58,6 +58,7 @@ impl<Number> FactoryState<Number> {
|
||||
frame_system::CheckWeight::new(),
|
||||
pallet_transaction_payment::ChargeTransactionPayment::from(0),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -122,7 +123,7 @@ impl RuntimeAdapter for FactoryState<Number> {
|
||||
(*amount).into()
|
||||
)
|
||||
)
|
||||
}, key, (version, genesis_hash.clone(), prior_block_hash.clone(), (), (), (), ()))
|
||||
}, key, (version, genesis_hash.clone(), prior_block_hash.clone(), (), (), (), (), ()))
|
||||
}
|
||||
|
||||
fn inherent_extrinsics(&self) -> InherentData {
|
||||
|
||||
@@ -624,11 +624,12 @@ mod tests {
|
||||
check_weight,
|
||||
payment,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let raw_payload = SignedPayload::from_raw(
|
||||
function,
|
||||
extra,
|
||||
(version, genesis_hash, genesis_hash, (), (), (), ())
|
||||
(version, genesis_hash, genesis_hash, (), (), (), (), ())
|
||||
);
|
||||
let signature = raw_payload.using_encoded(|payload| {
|
||||
signer.sign(payload)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use node_runtime::{
|
||||
Call, Executive, Indices, Runtime, SubmitTransaction, UncheckedExtrinsic,
|
||||
Call, Executive, Indices, Runtime, TransactionSubmitterOf, UncheckedExtrinsic,
|
||||
};
|
||||
use sp_application_crypto::AppKey;
|
||||
use sp_core::testing::KeyStore;
|
||||
@@ -31,6 +31,8 @@ use codec::Decode;
|
||||
pub mod common;
|
||||
use self::common::*;
|
||||
|
||||
type SubmitTransaction = TransactionSubmitterOf<pallet_im_online::sr25519::AuthorityId>;
|
||||
|
||||
#[test]
|
||||
fn should_submit_unsigned_transaction() {
|
||||
let mut t = new_test_ext(COMPACT_CODE, false);
|
||||
|
||||
@@ -59,7 +59,7 @@ pub use pallet_timestamp::Call as TimestampCall;
|
||||
pub use pallet_balances::Call as BalancesCall;
|
||||
pub use pallet_contracts::Gas;
|
||||
pub use frame_support::StorageValue;
|
||||
pub use pallet_staking::StakerStatus;
|
||||
pub use pallet_staking::{StakerStatus, LockStakingStatus};
|
||||
|
||||
/// Implementations of some helper traits passed into runtime modules as associated types.
|
||||
pub mod impls;
|
||||
@@ -73,6 +73,52 @@ use constants::{time::*, currency::*};
|
||||
#[cfg(feature = "std")]
|
||||
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
|
||||
|
||||
/// A transaction submitter with the given key type.
|
||||
pub type TransactionSubmitterOf<KeyType> = TransactionSubmitter<KeyType, Runtime, UncheckedExtrinsic>;
|
||||
|
||||
/// Submits transaction with the node's public and signature type. Adheres to the signed extension
|
||||
/// format of the chain.
|
||||
impl frame_system::offchain::CreateTransaction<Runtime, UncheckedExtrinsic> for Runtime {
|
||||
type Public = <Signature as traits::Verify>::Signer;
|
||||
type Signature = Signature;
|
||||
|
||||
fn create_transaction<TSigner: frame_system::offchain::Signer<Self::Public, Self::Signature>>(
|
||||
call: Call,
|
||||
public: Self::Public,
|
||||
account: AccountId,
|
||||
index: Index,
|
||||
) -> Option<(Call, <UncheckedExtrinsic as traits::Extrinsic>::SignaturePayload)> {
|
||||
// take the biggest period possible.
|
||||
let period = BlockHashCount::get()
|
||||
.checked_next_power_of_two()
|
||||
.map(|c| c / 2)
|
||||
.unwrap_or(2) as u64;
|
||||
let current_block = System::block_number()
|
||||
.saturated_into::<u64>()
|
||||
// The `System::block_number` is initialized with `n+1`,
|
||||
// so the actual block number is `n`.
|
||||
.saturating_sub(1);
|
||||
let tip = 0;
|
||||
let extra: SignedExtra = (
|
||||
frame_system::CheckVersion::<Runtime>::new(),
|
||||
frame_system::CheckGenesis::<Runtime>::new(),
|
||||
frame_system::CheckEra::<Runtime>::from(generic::Era::mortal(period, current_block)),
|
||||
frame_system::CheckNonce::<Runtime>::from(index),
|
||||
frame_system::CheckWeight::<Runtime>::new(),
|
||||
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let raw_payload = SignedPayload::new(call, extra).map_err(|e| {
|
||||
debug::warn!("Unable to create signed payload: {:?}", e);
|
||||
}).ok()?;
|
||||
let signature = TSigner::sign(public, &raw_payload)?;
|
||||
let address = Indices::unlookup(account);
|
||||
let (call, extra, _) = raw_payload.deconstruct();
|
||||
Some((call, (address, signature, extra)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime version.
|
||||
pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
spec_name: create_runtime_str!("node"),
|
||||
@@ -255,6 +301,7 @@ impl pallet_session::Trait for Runtime {
|
||||
type SessionHandler = <SessionKeys as OpaqueKeys>::KeyTypeIdProviders;
|
||||
type Keys = SessionKeys;
|
||||
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
|
||||
type NextSessionRotation = Babe;
|
||||
}
|
||||
|
||||
impl pallet_session::historical::Trait for Runtime {
|
||||
@@ -278,6 +325,7 @@ parameter_types! {
|
||||
pub const BondingDuration: pallet_staking::EraIndex = 24 * 28;
|
||||
pub const SlashDeferDuration: pallet_staking::EraIndex = 24 * 7; // 1/4 the bonding duration.
|
||||
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
|
||||
pub const ElectionLookahead: BlockNumber = 25; // 10 minutes per session => 100 block.
|
||||
pub const MaxNominatorRewardedPerValidator: u32 = 64;
|
||||
}
|
||||
|
||||
@@ -296,6 +344,10 @@ impl pallet_staking::Trait for Runtime {
|
||||
type SlashCancelOrigin = pallet_collective::EnsureProportionAtLeast<_3, _4, AccountId, CouncilCollective>;
|
||||
type SessionInterface = Self;
|
||||
type RewardCurve = RewardCurve;
|
||||
type NextNewSession = Session;
|
||||
type ElectionLookahead = ElectionLookahead;
|
||||
type Call = Call;
|
||||
type SubmitTransaction = TransactionSubmitterOf<()>;
|
||||
type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator;
|
||||
}
|
||||
|
||||
@@ -470,9 +522,6 @@ impl pallet_sudo::Trait for Runtime {
|
||||
type Call = Call;
|
||||
}
|
||||
|
||||
/// A runtime transaction submitter.
|
||||
pub type SubmitTransaction = TransactionSubmitter<ImOnlineId, Runtime, UncheckedExtrinsic>;
|
||||
|
||||
parameter_types! {
|
||||
pub const SessionDuration: BlockNumber = EPOCH_DURATION_IN_SLOTS as _;
|
||||
}
|
||||
@@ -481,7 +530,7 @@ impl pallet_im_online::Trait for Runtime {
|
||||
type AuthorityId = ImOnlineId;
|
||||
type Event = Event;
|
||||
type Call = Call;
|
||||
type SubmitTransaction = SubmitTransaction;
|
||||
type SubmitTransaction = TransactionSubmitterOf<Self::AuthorityId>;
|
||||
type SessionDuration = SessionDuration;
|
||||
type ReportUnresponsiveness = Offences;
|
||||
}
|
||||
@@ -530,46 +579,6 @@ impl pallet_identity::Trait for Runtime {
|
||||
type RegistrarOrigin = pallet_collective::EnsureProportionMoreThan<_1, _2, AccountId, CouncilCollective>;
|
||||
}
|
||||
|
||||
impl frame_system::offchain::CreateTransaction<Runtime, UncheckedExtrinsic> for Runtime {
|
||||
type Public = <Signature as traits::Verify>::Signer;
|
||||
type Signature = Signature;
|
||||
|
||||
fn create_transaction<TSigner: frame_system::offchain::Signer<Self::Public, Self::Signature>>(
|
||||
call: Call,
|
||||
public: Self::Public,
|
||||
account: AccountId,
|
||||
index: Index,
|
||||
) -> Option<(Call, <UncheckedExtrinsic as traits::Extrinsic>::SignaturePayload)> {
|
||||
// take the biggest period possible.
|
||||
let period = BlockHashCount::get()
|
||||
.checked_next_power_of_two()
|
||||
.map(|c| c / 2)
|
||||
.unwrap_or(2) as u64;
|
||||
let current_block = System::block_number()
|
||||
.saturated_into::<u64>()
|
||||
// The `System::block_number` is initialized with `n+1`,
|
||||
// so the actual block number is `n`.
|
||||
.saturating_sub(1);
|
||||
let tip = 0;
|
||||
let extra: SignedExtra = (
|
||||
frame_system::CheckVersion::<Runtime>::new(),
|
||||
frame_system::CheckGenesis::<Runtime>::new(),
|
||||
frame_system::CheckEra::<Runtime>::from(generic::Era::mortal(period, current_block)),
|
||||
frame_system::CheckNonce::<Runtime>::from(index),
|
||||
frame_system::CheckWeight::<Runtime>::new(),
|
||||
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
|
||||
Default::default(),
|
||||
);
|
||||
let raw_payload = SignedPayload::new(call, extra).map_err(|e| {
|
||||
debug::warn!("Unable to create signed payload: {:?}", e);
|
||||
}).ok()?;
|
||||
let signature = TSigner::sign(public, &raw_payload)?;
|
||||
let address = Indices::unlookup(account);
|
||||
let (call, extra, _) = raw_payload.deconstruct();
|
||||
Some((call, (address, signature, extra)))
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ConfigDepositBase: Balance = 5 * DOLLARS;
|
||||
pub const FriendDepositFactor: Balance = 50 * CENTS;
|
||||
@@ -638,7 +647,7 @@ construct_runtime!(
|
||||
Indices: pallet_indices::{Module, Call, Storage, Config<T>, Event<T>},
|
||||
Balances: pallet_balances::{Module, Call, Storage, Config<T>, Event<T>},
|
||||
TransactionPayment: pallet_transaction_payment::{Module, Storage},
|
||||
Staking: pallet_staking::{Module, Call, Config<T>, Storage, Event<T>},
|
||||
Staking: pallet_staking::{Module, Call, Config<T>, Storage, Event<T>, ValidateUnsigned},
|
||||
Session: pallet_session::{Module, Call, Storage, Event, Config<T>},
|
||||
Democracy: pallet_democracy::{Module, Call, Storage, Config, Event<T>},
|
||||
Council: pallet_collective::<Instance1>::{Module, Call, Storage, Origin<T>, Event<T>, Config<T>},
|
||||
@@ -680,6 +689,7 @@ pub type SignedExtra = (
|
||||
frame_system::CheckWeight<Runtime>,
|
||||
pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
|
||||
pallet_contracts::CheckBlockGasLimit<Runtime>,
|
||||
pallet_staking::LockStakingStatus<Runtime>,
|
||||
);
|
||||
/// Unchecked extrinsic type as expected by this runtime.
|
||||
pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
|
||||
@@ -965,7 +975,7 @@ mod tests {
|
||||
>,
|
||||
{}
|
||||
|
||||
is_submit_signed_transaction::<SubmitTransaction>();
|
||||
is_sign_and_submit_transaction::<SubmitTransaction>();
|
||||
is_submit_signed_transaction::<TransactionSubmitterOf<ImOnlineId>>();
|
||||
is_sign_and_submit_transaction::<TransactionSubmitterOf<ImOnlineId>>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ pub fn signed_extra(nonce: Index, extra_fee: Balance) -> SignedExtra {
|
||||
frame_system::CheckWeight::new(),
|
||||
pallet_transaction_payment::ChargeTransactionPayment::from(extra_fee),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -683,6 +683,7 @@ fn create_extrinsic<C: Crypto>(
|
||||
frame_system::CheckWeight::<Runtime>::new(),
|
||||
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(f),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
)
|
||||
};
|
||||
let raw_payload = SignedPayload::from_raw(
|
||||
@@ -696,6 +697,7 @@ fn create_extrinsic<C: Crypto>(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
),
|
||||
);
|
||||
let signature = raw_payload.using_encoded(|payload| signer.sign(payload)).into_runtime();
|
||||
|
||||
@@ -121,6 +121,7 @@ mod tests {
|
||||
type ValidatorId = AuthorityId;
|
||||
type ValidatorIdOf = ConvertInto;
|
||||
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
|
||||
type NextSessionRotation = pallet_session::PeriodicSessions<Period, Offset>;
|
||||
}
|
||||
|
||||
impl pallet_session::historical::Trait for Test {
|
||||
|
||||
@@ -9,7 +9,6 @@ repository = "https://github.com/paritytech/substrate/"
|
||||
description = "Consensus extension module for BABE consensus. Collects on-chain randomness from VRF outputs and manages epoch transitions."
|
||||
|
||||
[dependencies]
|
||||
hex-literal = "0.2.1"
|
||||
codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] }
|
||||
serde = { version = "1.0.101", optional = true }
|
||||
sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/inherents" }
|
||||
@@ -26,11 +25,7 @@ sp-consensus-vrf = { version = "0.8.0-alpha.5", default-features = false, path =
|
||||
sp-io = { path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.5"}
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
parking_lot = "0.10.0"
|
||||
sp-version = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/version" }
|
||||
sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" }
|
||||
substrate-test-runtime = { version = "2.0.0-dev", path = "../../test-utils/runtime" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![forbid(unused_must_use, unsafe_code, unused_variables, unused_must_use)]
|
||||
#![deny(unused_imports)]
|
||||
pub use pallet_timestamp;
|
||||
|
||||
use pallet_timestamp;
|
||||
|
||||
use sp_std::{result, prelude::*};
|
||||
use frame_support::{
|
||||
@@ -29,7 +29,7 @@ use frame_support::{
|
||||
};
|
||||
use sp_timestamp::OnTimestampSet;
|
||||
use sp_runtime::{generic::DigestItem, ConsensusEngineId, Perbill, PerThing};
|
||||
use sp_runtime::traits::{IsMember, SaturatedConversion, Saturating, Hash};
|
||||
use sp_runtime::traits::{IsMember, SaturatedConversion, Saturating, Hash, One};
|
||||
use sp_staking::{
|
||||
SessionIndex,
|
||||
offence::{Offence, Kind},
|
||||
@@ -307,12 +307,34 @@ impl<T: Trait> Module<T> {
|
||||
// epoch 0 as having started at the slot of block 1. We want to use
|
||||
// the same randomness and validator set as signalled in the genesis,
|
||||
// so we don't rotate the epoch.
|
||||
now != sp_runtime::traits::One::one() && {
|
||||
now != One::one() && {
|
||||
let diff = CurrentSlot::get().saturating_sub(Self::current_epoch_start());
|
||||
diff >= T::EpochDuration::get()
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the _best guess_ block number, at which the next epoch change is predicted to happen.
|
||||
///
|
||||
/// Returns None if the prediction is in the past; This implies an error internally in the Babe
|
||||
/// and should not happen under normal circumstances.
|
||||
///
|
||||
/// In other word, this is only accurate if no slots are missed. Given missed slots, the slot
|
||||
/// number will grow while the block number will not. Hence, the result can be interpreted as an
|
||||
/// upper bound.
|
||||
// -------------- IMPORTANT NOTE --------------
|
||||
// This implementation is linked to how [`should_epoch_change`] is working. This might need to
|
||||
// be updated accordingly, if the underlying mechanics of slot and epochs change.
|
||||
pub fn next_expected_epoch_change(now: T::BlockNumber) -> Option<T::BlockNumber> {
|
||||
let next_slot = Self::current_epoch_start().saturating_add(T::EpochDuration::get());
|
||||
next_slot
|
||||
.checked_sub(CurrentSlot::get())
|
||||
.map(|slots_remaining| {
|
||||
// This is a best effort guess. Drifts in the slot/block ratio will cause errors here.
|
||||
let blocks_remaining: T::BlockNumber = slots_remaining.saturated_into();
|
||||
now.saturating_add(blocks_remaining)
|
||||
})
|
||||
}
|
||||
|
||||
/// DANGEROUS: Enact an epoch change. Should be done on every block where `should_epoch_change` has returned `true`,
|
||||
/// and the caller is the only caller of this function.
|
||||
///
|
||||
@@ -324,10 +346,7 @@ impl<T: Trait> Module<T> {
|
||||
) {
|
||||
// PRECONDITION: caller has done initialization and is guaranteed
|
||||
// by the session module to be called before this.
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
assert!(Self::initialized().is_some())
|
||||
}
|
||||
debug_assert!(Self::initialized().is_some());
|
||||
|
||||
// Update epoch index
|
||||
let epoch_index = EpochIndex::get()
|
||||
@@ -473,6 +492,12 @@ impl<T: Trait> OnTimestampSet<T::Moment> for Module<T> {
|
||||
fn on_timestamp_set(_moment: T::Moment) { }
|
||||
}
|
||||
|
||||
impl<T: Trait> frame_support::traits::EstimateNextSessionRotation<T::BlockNumber> for Module<T> {
|
||||
fn estimate_next_session_rotation(now: T::BlockNumber) -> Option<T::BlockNumber> {
|
||||
Self::next_expected_epoch_change(now)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> sp_runtime::BoundToRuntimeAppPublic for Module<T> {
|
||||
type Public = AuthorityId;
|
||||
}
|
||||
|
||||
@@ -16,14 +16,22 @@
|
||||
|
||||
//! Test utilities
|
||||
|
||||
use super::{Trait, Module, GenesisConfig};
|
||||
use codec::Encode;
|
||||
use super::{Trait, Module, GenesisConfig, CurrentSlot};
|
||||
use sp_runtime::{
|
||||
traits::IdentityLookup, Perbill, testing::{Header, UintAuthorityId}, impl_opaque_keys,
|
||||
Perbill, impl_opaque_keys,
|
||||
testing::{Header, UintAuthorityId, Digest, DigestItem},
|
||||
traits::IdentityLookup,
|
||||
};
|
||||
use frame_system::InitKind;
|
||||
use frame_support::{
|
||||
impl_outer_origin, parameter_types, StorageValue,
|
||||
traits::OnInitialize,
|
||||
weights::Weight,
|
||||
};
|
||||
use sp_version::RuntimeVersion;
|
||||
use frame_support::{impl_outer_origin, parameter_types, weights::Weight};
|
||||
use sp_io;
|
||||
use sp_core::H256;
|
||||
use sp_consensus_vrf::schnorrkel::{RawVRFOutput, RawVRFProof};
|
||||
|
||||
impl_outer_origin!{
|
||||
pub enum Origin for Test where system = frame_system {}
|
||||
@@ -43,7 +51,6 @@ parameter_types! {
|
||||
pub const MinimumPeriod: u64 = 1;
|
||||
pub const EpochDuration: u64 = 3;
|
||||
pub const ExpectedBlockTime: u64 = 1;
|
||||
pub const Version: RuntimeVersion = substrate_test_runtime::VERSION;
|
||||
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(16);
|
||||
}
|
||||
|
||||
@@ -53,7 +60,7 @@ impl frame_system::Trait for Test {
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Version = Version;
|
||||
type Version = ();
|
||||
type Hashing = sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = DummyValidatorId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
@@ -79,11 +86,12 @@ impl pallet_session::Trait for Test {
|
||||
type Event = ();
|
||||
type ValidatorId = <Self as frame_system::Trait>::AccountId;
|
||||
type ShouldEndSession = Babe;
|
||||
type SessionHandler = (Babe,Babe,);
|
||||
type SessionHandler = (Babe,);
|
||||
type SessionManager = ();
|
||||
type ValidatorIdOf = ();
|
||||
type Keys = MockSessionKeys;
|
||||
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
|
||||
type NextSessionRotation = Babe;
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Trait for Test {
|
||||
@@ -106,5 +114,44 @@ pub fn new_test_ext(authorities: Vec<DummyValidatorId>) -> sp_io::TestExternalit
|
||||
t.into()
|
||||
}
|
||||
|
||||
pub fn go_to_block(n: u64, s: u64) {
|
||||
let pre_digest = make_pre_digest(0, s, RawVRFOutput([1; 32]), RawVRFProof([0xff; 64]));
|
||||
System::initialize(&n, &Default::default(), &Default::default(), &pre_digest, InitKind::Full);
|
||||
System::set_block_number(n);
|
||||
if s > 1 {
|
||||
CurrentSlot::put(s);
|
||||
}
|
||||
// includes a call into `Babe::do_initialize`.
|
||||
Session::on_initialize(n);
|
||||
}
|
||||
|
||||
/// Slots will grow accordingly to blocks
|
||||
pub fn progress_to_block(n: u64) {
|
||||
let mut slot = Babe::current_slot() + 1;
|
||||
for i in System::block_number()+1..=n {
|
||||
go_to_block(i, slot);
|
||||
slot += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_pre_digest(
|
||||
authority_index: sp_consensus_babe::AuthorityIndex,
|
||||
slot_number: sp_consensus_babe::SlotNumber,
|
||||
vrf_output: RawVRFOutput,
|
||||
vrf_proof: RawVRFProof,
|
||||
) -> Digest {
|
||||
let digest_data = sp_consensus_babe::digests::RawPreDigest::Primary(
|
||||
sp_consensus_babe::digests::RawPrimaryPreDigest {
|
||||
authority_index,
|
||||
slot_number,
|
||||
vrf_output,
|
||||
vrf_proof,
|
||||
}
|
||||
);
|
||||
let log = DigestItem::PreRuntime(sp_consensus_babe::BABE_ENGINE_ID, digest_data.encode());
|
||||
Digest { logs: vec![log] }
|
||||
}
|
||||
|
||||
pub type System = frame_system::Module<Test>;
|
||||
pub type Babe = Module<Test>;
|
||||
pub type Session = pallet_session::Module<Test>;
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
//! Consensus extension module tests for BABE consensus.
|
||||
|
||||
use super::*;
|
||||
use mock::*;
|
||||
use frame_support::traits::OnFinalize;
|
||||
use mock::{new_test_ext, Babe, System};
|
||||
use sp_runtime::testing::{Digest, DigestItem};
|
||||
use sp_consensus_vrf::schnorrkel::{RawVRFOutput, RawVRFProof};
|
||||
use pallet_session::ShouldEndSession;
|
||||
use sp_consensus_vrf::schnorrkel::{RawVRFOutput, RawVRFProof};
|
||||
|
||||
const EMPTY_RANDOMNESS: [u8; 32] = [
|
||||
74, 25, 49, 128, 53, 97, 244, 49,
|
||||
@@ -30,24 +29,6 @@ const EMPTY_RANDOMNESS: [u8; 32] = [
|
||||
217, 153, 138, 37, 48, 192, 248, 0,
|
||||
];
|
||||
|
||||
fn make_pre_digest(
|
||||
authority_index: sp_consensus_babe::AuthorityIndex,
|
||||
slot_number: sp_consensus_babe::SlotNumber,
|
||||
vrf_output: RawVRFOutput,
|
||||
vrf_proof: RawVRFProof,
|
||||
) -> Digest {
|
||||
let digest_data = sp_consensus_babe::digests::RawPreDigest::Primary(
|
||||
sp_consensus_babe::digests::RawPrimaryPreDigest {
|
||||
authority_index,
|
||||
slot_number,
|
||||
vrf_output,
|
||||
vrf_proof,
|
||||
}
|
||||
);
|
||||
let log = DigestItem::PreRuntime(sp_consensus_babe::BABE_ENGINE_ID, digest_data.encode());
|
||||
Digest { logs: vec![log] }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_randomness_is_correct() {
|
||||
let s = compute_randomness([0; RANDOMNESS_LENGTH], 0, std::iter::empty(), None);
|
||||
@@ -132,3 +113,24 @@ fn authority_index() {
|
||||
"Trivially invalid authorities are ignored")
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_predict_next_epoch_change() {
|
||||
new_test_ext(vec![]).execute_with(|| {
|
||||
assert_eq!(<Test as Trait>::EpochDuration::get(), 3);
|
||||
// this sets the genesis slot to 6;
|
||||
go_to_block(1, 6);
|
||||
assert_eq!(Babe::genesis_slot(), 6);
|
||||
assert_eq!(Babe::current_slot(), 6);
|
||||
assert_eq!(Babe::epoch_index(), 0);
|
||||
|
||||
progress_to_block(5);
|
||||
|
||||
assert_eq!(Babe::epoch_index(), 5 / 3);
|
||||
assert_eq!(Babe::current_slot(), 10);
|
||||
|
||||
// next epoch change will be at
|
||||
assert_eq!(Babe::current_epoch_start(), 9); // next change will be 12, 2 slots from now
|
||||
assert_eq!(Babe::next_expected_epoch_change(System::block_number()), Some(5 + 2));
|
||||
})
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ use frame_support::{
|
||||
ChangeMembers, OnUnbalanced, WithdrawReason, Contains, BalanceStatus
|
||||
}
|
||||
};
|
||||
use sp_phragmen::ExtendedBalance;
|
||||
use sp_phragmen::{build_support_map, ExtendedBalance};
|
||||
use frame_system::{self as system, ensure_signed, ensure_root};
|
||||
|
||||
const MODULE_ID: LockIdentifier = *b"phrelect";
|
||||
@@ -692,12 +692,18 @@ impl<T: Trait> Module<T> {
|
||||
.filter_map(|(m, a)| if a.is_zero() { None } else { Some(m) } )
|
||||
.collect::<Vec<T::AccountId>>();
|
||||
|
||||
let support_map = sp_phragmen::build_support_map::<_, _, _, T::CurrencyToVote, Perbill>(
|
||||
&new_set,
|
||||
&phragmen_result.assignments,
|
||||
Self::locked_stake_of,
|
||||
let stake_of = |who: &T::AccountId| -> ExtendedBalance {
|
||||
<T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(
|
||||
Self::locked_stake_of(who)
|
||||
) as ExtendedBalance
|
||||
};
|
||||
let staked_assignments = sp_phragmen::assignment_ratio_to_staked(
|
||||
phragmen_result.assignments,
|
||||
stake_of,
|
||||
);
|
||||
|
||||
let (support_map, _) = build_support_map::<T::AccountId>(&new_set, &staked_assignments);
|
||||
|
||||
let to_balance = |e: ExtendedBalance|
|
||||
<T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(e);
|
||||
let new_set_with_stake = new_set
|
||||
|
||||
@@ -141,6 +141,7 @@ impl pallet_session::Trait for Runtime {
|
||||
type Keys = UintAuthorityId;
|
||||
type Event = ();
|
||||
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
|
||||
type NextSessionRotation = pallet_session::PeriodicSessions<Period, Offset>;
|
||||
}
|
||||
|
||||
impl pallet_session::historical::Trait for Runtime {
|
||||
|
||||
@@ -26,11 +26,12 @@ mod tests;
|
||||
|
||||
use sp_std::vec::Vec;
|
||||
use frame_support::{
|
||||
decl_module, decl_event, decl_storage, Parameter,
|
||||
decl_module, decl_event, decl_storage, Parameter, debug,
|
||||
weights::{Weight, SimpleDispatchInfo, WeighData},
|
||||
};
|
||||
use sp_runtime::traits::Hash;
|
||||
use sp_runtime::{traits::Hash, Perbill};
|
||||
use sp_staking::{
|
||||
SessionIndex,
|
||||
offence::{Offence, ReportOffence, Kind, OnOffenceHandler, OffenceDetails, OffenceError},
|
||||
};
|
||||
use codec::{Encode, Decode};
|
||||
@@ -42,6 +43,13 @@ type OpaqueTimeSlot = Vec<u8>;
|
||||
/// A type alias for a report identifier.
|
||||
type ReportIdOf<T> = <T as frame_system::Trait>::Hash;
|
||||
|
||||
/// Type of data stored as a deferred offence
|
||||
type DeferredOffenceOf<T> = (
|
||||
Vec<OffenceDetails<<T as frame_system::Trait>::AccountId, <T as Trait>::IdentificationTuple>>,
|
||||
Vec<Perbill>,
|
||||
SessionIndex,
|
||||
);
|
||||
|
||||
/// Offences trait
|
||||
pub trait Trait: frame_system::Trait {
|
||||
/// The overarching event type.
|
||||
@@ -59,6 +67,10 @@ decl_storage! {
|
||||
map hasher(twox_64_concat) ReportIdOf<T>
|
||||
=> Option<OffenceDetails<T::AccountId, T::IdentificationTuple>>;
|
||||
|
||||
/// Deferred reports that have been rejected by the offence handler and need to be submitted
|
||||
/// at a later time.
|
||||
DeferredOffences get(deferred_offences): Vec<DeferredOffenceOf<T>>;
|
||||
|
||||
/// A vector of reports of the same kind that happened at the same time slot.
|
||||
ConcurrentReportsIndex:
|
||||
double_map hasher(twox_64_concat) Kind, hasher(twox_64_concat) OpaqueTimeSlot
|
||||
@@ -77,13 +89,13 @@ decl_storage! {
|
||||
decl_event!(
|
||||
pub enum Event {
|
||||
/// There is an offence reported of the given `kind` happened at the `session_index` and
|
||||
/// (kind-specific) time slot. This event is not deposited for duplicate slashes.
|
||||
Offence(Kind, OpaqueTimeSlot),
|
||||
/// (kind-specific) time slot. This event is not deposited for duplicate slashes. last
|
||||
/// element indicates of the offence was applied (true) or queued (false).
|
||||
Offence(Kind, OpaqueTimeSlot, bool),
|
||||
}
|
||||
);
|
||||
|
||||
decl_module! {
|
||||
/// Offences module, currently just responsible for taking offence reports.
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
fn deposit_event() = default;
|
||||
|
||||
@@ -94,6 +106,27 @@ decl_module! {
|
||||
|
||||
SimpleDispatchInfo::default().weigh_data(())
|
||||
}
|
||||
|
||||
fn on_initialize(now: T::BlockNumber) -> Weight {
|
||||
// only decode storage if we can actually submit anything again.
|
||||
if T::OnOffenceHandler::can_report() {
|
||||
<DeferredOffences<T>>::mutate(|deferred| {
|
||||
// keep those that fail to be reported again. An error log is emitted here; this
|
||||
// should not happen if staking's `can_report` is implemented properly.
|
||||
deferred.retain(|(o, p, s)| {
|
||||
T::OnOffenceHandler::on_offence(&o, &p, *s).map_err(|_| {
|
||||
debug::native::error!(
|
||||
target: "pallet-offences",
|
||||
"re-submitting a deferred slash returned Err at {}. This should not happen with pallet-staking",
|
||||
now,
|
||||
);
|
||||
}).is_err()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
SimpleDispatchInfo::default().weigh_data(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,9 +152,6 @@ where
|
||||
None => return Err(OffenceError::DuplicateReport),
|
||||
};
|
||||
|
||||
// Deposit the event.
|
||||
Self::deposit_event(Event::Offence(O::ID, time_slot.encode()));
|
||||
|
||||
let offenders_count = concurrent_offenders.len() as u32;
|
||||
|
||||
// The amount new offenders are slashed
|
||||
@@ -130,17 +160,42 @@ where
|
||||
let slash_perbill: Vec<_> = (0..concurrent_offenders.len())
|
||||
.map(|_| new_fraction.clone()).collect();
|
||||
|
||||
T::OnOffenceHandler::on_offence(
|
||||
let applied = Self::report_or_store_offence(
|
||||
&concurrent_offenders,
|
||||
&slash_perbill,
|
||||
offence.session_index(),
|
||||
);
|
||||
|
||||
// Deposit the event.
|
||||
Self::deposit_event(Event::Offence(O::ID, time_slot.encode(), applied));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
/// Tries (without checking) to report an offence. Stores them in [`DeferredOffences`] in case
|
||||
/// it fails. Returns false in case it has to store the offence.
|
||||
fn report_or_store_offence(
|
||||
concurrent_offenders: &[OffenceDetails<T::AccountId, T::IdentificationTuple>],
|
||||
slash_perbill: &[Perbill],
|
||||
session_index: SessionIndex,
|
||||
) -> bool {
|
||||
match T::OnOffenceHandler::on_offence(
|
||||
&concurrent_offenders,
|
||||
&slash_perbill,
|
||||
session_index,
|
||||
) {
|
||||
Ok(_) => true,
|
||||
Err(_) => {
|
||||
<DeferredOffences<T>>::mutate(|d|
|
||||
d.push((concurrent_offenders.to_vec(), slash_perbill.to_vec(), session_index))
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the ID for the given report properties.
|
||||
///
|
||||
/// The report id depends on the offence kind, time slot and the id of offender.
|
||||
|
||||
@@ -43,6 +43,7 @@ pub struct OnOffenceHandler;
|
||||
|
||||
thread_local! {
|
||||
pub static ON_OFFENCE_PERBILL: RefCell<Vec<Perbill>> = RefCell::new(Default::default());
|
||||
pub static CAN_REPORT: RefCell<bool> = RefCell::new(true);
|
||||
}
|
||||
|
||||
impl<Reporter, Offender> offence::OnOffenceHandler<Reporter, Offender> for OnOffenceHandler {
|
||||
@@ -50,11 +51,25 @@ impl<Reporter, Offender> offence::OnOffenceHandler<Reporter, Offender> for OnOff
|
||||
_offenders: &[OffenceDetails<Reporter, Offender>],
|
||||
slash_fraction: &[Perbill],
|
||||
_offence_session: SessionIndex,
|
||||
) {
|
||||
ON_OFFENCE_PERBILL.with(|f| {
|
||||
*f.borrow_mut() = slash_fraction.to_vec();
|
||||
});
|
||||
) -> Result<(), ()> {
|
||||
if <Self as offence::OnOffenceHandler<Reporter, Offender>>::can_report() {
|
||||
ON_OFFENCE_PERBILL.with(|f| {
|
||||
*f.borrow_mut() = slash_fraction.to_vec();
|
||||
});
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
fn can_report() -> bool {
|
||||
CAN_REPORT.with(|c| *c.borrow())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_can_report(can_report: bool) {
|
||||
CAN_REPORT.with(|c| *c.borrow_mut() = can_report);
|
||||
}
|
||||
|
||||
pub fn with_on_offence_fractions<R, F: FnOnce(&mut Vec<Perbill>) -> R>(f: F) -> R {
|
||||
|
||||
@@ -21,9 +21,10 @@
|
||||
use super::*;
|
||||
use crate::mock::{
|
||||
Offences, System, Offence, TestEvent, KIND, new_test_ext, with_on_offence_fractions,
|
||||
offence_reports,
|
||||
offence_reports, set_can_report,
|
||||
};
|
||||
use sp_runtime::Perbill;
|
||||
use frame_support::traits::OnInitialize;
|
||||
use frame_system::{EventRecord, Phase};
|
||||
|
||||
#[test]
|
||||
@@ -130,7 +131,7 @@ fn should_deposit_event() {
|
||||
System::events(),
|
||||
vec![EventRecord {
|
||||
phase: Phase::Initialization,
|
||||
event: TestEvent::offences(crate::Event::Offence(KIND, time_slot.encode())),
|
||||
event: TestEvent::offences(crate::Event::Offence(KIND, time_slot.encode(), true)),
|
||||
topics: vec![],
|
||||
}]
|
||||
);
|
||||
@@ -165,7 +166,7 @@ fn doesnt_deposit_event_for_dups() {
|
||||
System::events(),
|
||||
vec![EventRecord {
|
||||
phase: Phase::Initialization,
|
||||
event: TestEvent::offences(crate::Event::Offence(KIND, time_slot.encode())),
|
||||
event: TestEvent::offences(crate::Event::Offence(KIND, time_slot.encode(), true)),
|
||||
topics: vec![],
|
||||
}]
|
||||
);
|
||||
@@ -212,3 +213,54 @@ fn should_properly_count_offences() {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_queue_and_resubmit_rejected_offence() {
|
||||
new_test_ext().execute_with(|| {
|
||||
set_can_report(false);
|
||||
|
||||
// will get deferred
|
||||
let offence = Offence {
|
||||
validator_set_count: 5,
|
||||
time_slot: 42,
|
||||
offenders: vec![5],
|
||||
};
|
||||
Offences::report_offence(vec![], offence).unwrap();
|
||||
assert_eq!(Offences::deferred_offences().len(), 1);
|
||||
// event also indicates unapplied.
|
||||
assert_eq!(
|
||||
System::events(),
|
||||
vec![EventRecord {
|
||||
phase: Phase::Initialization,
|
||||
event: TestEvent::offences(crate::Event::Offence(KIND, 42u128.encode(), false)),
|
||||
topics: vec![],
|
||||
}]
|
||||
);
|
||||
|
||||
// will not dequeue
|
||||
Offences::on_initialize(2);
|
||||
|
||||
// again
|
||||
let offence = Offence {
|
||||
validator_set_count: 5,
|
||||
time_slot: 62,
|
||||
offenders: vec![5],
|
||||
};
|
||||
Offences::report_offence(vec![], offence).unwrap();
|
||||
assert_eq!(Offences::deferred_offences().len(), 2);
|
||||
|
||||
set_can_report(true);
|
||||
|
||||
// can be submitted
|
||||
let offence = Offence {
|
||||
validator_set_count: 5,
|
||||
time_slot: 72,
|
||||
offenders: vec![5],
|
||||
};
|
||||
Offences::report_offence(vec![], offence).unwrap();
|
||||
assert_eq!(Offences::deferred_offences().len(), 2);
|
||||
|
||||
Offences::on_initialize(3);
|
||||
assert_eq!(Offences::deferred_offences().len(), 0);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -102,13 +102,15 @@
|
||||
use sp_std::{prelude::*, marker::PhantomData, ops::{Sub, Rem}};
|
||||
use codec::Decode;
|
||||
use sp_runtime::{KeyTypeId, Perbill, RuntimeAppPublic, BoundToRuntimeAppPublic};
|
||||
use sp_runtime::traits::{Convert, Zero, Member, OpaqueKeys};
|
||||
use sp_runtime::traits::{Convert, Zero, Member, OpaqueKeys, Saturating};
|
||||
use sp_staking::SessionIndex;
|
||||
use frame_support::{
|
||||
ensure, decl_module, decl_event, decl_storage, decl_error, ConsensusEngineId, Parameter,
|
||||
weights::{Weight, SimpleDispatchInfo, WeighData},
|
||||
traits::{Get, FindAuthor, ValidatorRegistration},
|
||||
traits::{
|
||||
Get, FindAuthor, ValidatorRegistration, EstimateNextSessionRotation, EstimateNextNewSession,
|
||||
},
|
||||
dispatch::{self, DispatchResult, DispatchError},
|
||||
weights::{Weight, SimpleDispatchInfo, WeighData},
|
||||
};
|
||||
use frame_system::{self as system, ensure_signed};
|
||||
|
||||
@@ -147,6 +149,29 @@ impl<
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
BlockNumber: Rem<Output=BlockNumber> + Sub<Output=BlockNumber> + Zero + PartialOrd + Saturating + Clone,
|
||||
Period: Get<BlockNumber>,
|
||||
Offset: Get<BlockNumber>,
|
||||
> EstimateNextSessionRotation<BlockNumber> for PeriodicSessions<Period, Offset> {
|
||||
fn estimate_next_session_rotation(now: BlockNumber) -> Option<BlockNumber> {
|
||||
let offset = Offset::get();
|
||||
let period = Period::get();
|
||||
Some(if now > offset {
|
||||
let block_after_last_session = (now.clone() - offset) % period.clone();
|
||||
if block_after_last_session > Zero::zero() {
|
||||
now.saturating_add(
|
||||
period.saturating_sub(block_after_last_session)
|
||||
)
|
||||
} else {
|
||||
Zero::zero()
|
||||
}
|
||||
} else {
|
||||
offset
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for managing creation of new validator set.
|
||||
pub trait SessionManager<ValidatorId> {
|
||||
/// Plan a new session, and optionally provide the new validator set.
|
||||
@@ -330,6 +355,11 @@ pub trait Trait: frame_system::Trait {
|
||||
/// Indicator for when to end the session.
|
||||
type ShouldEndSession: ShouldEndSession<Self::BlockNumber>;
|
||||
|
||||
/// Something that can predict the next session rotation. This should typically come from the
|
||||
/// same logical unit that provides [`ShouldEndSession`], yet, it gives a best effort estimate.
|
||||
/// It is helpful to implement [`EstimateNextNewSession`].
|
||||
type NextSessionRotation: EstimateNextSessionRotation<Self::BlockNumber>;
|
||||
|
||||
/// Handler for managing new session.
|
||||
type SessionManager: SessionManager<Self::ValidatorId>;
|
||||
|
||||
@@ -735,3 +765,11 @@ impl<T: Trait, Inner: FindAuthor<u32>> FindAuthor<T::ValidatorId>
|
||||
validators.get(i as usize).map(|k| k.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> EstimateNextNewSession<T::BlockNumber> for Module<T> {
|
||||
/// This session module always calls new_session and next_session at the same time, hence we
|
||||
/// do a simple proxy and pass the function to next rotation.
|
||||
fn estimate_next_new_session(now: T::BlockNumber) -> Option<T::BlockNumber> {
|
||||
T::NextSessionRotation::estimate_next_session_rotation(now)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@ impl Trait for Test {
|
||||
type Keys = MockSessionKeys;
|
||||
type Event = ();
|
||||
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
|
||||
type NextSessionRotation = ();
|
||||
}
|
||||
|
||||
#[cfg(feature = "historical")]
|
||||
|
||||
@@ -11,7 +11,6 @@ description = "FRAME pallet staking"
|
||||
[dependencies]
|
||||
serde = { version = "1.0.101", optional = true }
|
||||
codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] }
|
||||
sp-keyring = { version = "2.0.0-alpha.5", optional = true, path = "../../primitives/keyring" }
|
||||
sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" }
|
||||
sp-phragmen = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/phragmen" }
|
||||
sp-io ={ path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.5"}
|
||||
@@ -21,7 +20,15 @@ frame-support = { version = "2.0.0-alpha.5", default-features = false, path = ".
|
||||
frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" }
|
||||
pallet-session = { version = "2.0.0-alpha.5", features = ["historical"], path = "../session", default-features = false }
|
||||
pallet-authorship = { version = "2.0.0-alpha.5", default-features = false, path = "../authorship" }
|
||||
sp-application-crypto = { version = "2.0.0-alpha.4", default-features = false, path = "../../primitives/application-crypto" }
|
||||
static_assertions = "1.1.0"
|
||||
|
||||
# Optional imports for tesing-utils feature
|
||||
pallet-indices = { version = "2.0.0-alpha.4", optional = true, path = "../indices", default-features = false }
|
||||
sp-core = { version = "2.0.0-alpha.4", optional = true, path = "../../primitives/core", default-features = false }
|
||||
rand = { version = "0.7.3", optional = true, default-features = false }
|
||||
|
||||
# Optional imports for benchmarking
|
||||
frame-benchmarking = { version = "2.0.0-alpha.5", default-features = false, path = "../benchmarking", optional = true }
|
||||
rand_chacha = { version = "0.2", default-features = false, optional = true }
|
||||
|
||||
@@ -33,13 +40,20 @@ pallet-staking-reward-curve = { version = "2.0.0-alpha.5", path = "../staking/r
|
||||
substrate-test-utils = { version = "2.0.0-alpha.5", path = "../../test-utils" }
|
||||
frame-benchmarking = { version = "2.0.0-alpha.5", path = "../benchmarking" }
|
||||
rand_chacha = { version = "0.2" }
|
||||
parking_lot = "0.10.0"
|
||||
env_logger = "0.7.1"
|
||||
hex = "0.4"
|
||||
|
||||
[features]
|
||||
testing-utils = [
|
||||
"std",
|
||||
"pallet-indices/std",
|
||||
"sp-core/std",
|
||||
"rand/std",
|
||||
]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"serde",
|
||||
"sp-keyring",
|
||||
"codec/std",
|
||||
"sp-std/std",
|
||||
"sp-phragmen/std",
|
||||
@@ -50,6 +64,8 @@ std = [
|
||||
"pallet-session/std",
|
||||
"frame-system/std",
|
||||
"pallet-authorship/std",
|
||||
"sp-application-crypto/std",
|
||||
"sp-core/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"rand_chacha",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
target
|
||||
corpus
|
||||
artifacts
|
||||
Generated
+2189
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "pallet-staking-fuzz"
|
||||
version = "0.0.0"
|
||||
authors = ["Automatically generated"]
|
||||
publish = false
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.3"
|
||||
codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] }
|
||||
pallet-staking = { version = "2.0.0-alpha.2", path = "..", features = ["testing-utils"] }
|
||||
pallet-staking-reward-curve = { version = "2.0.0-alpha.2", path = "../reward-curve" }
|
||||
pallet-session = { version = "2.0.0-alpha.2", path = "../../session" }
|
||||
pallet-indices = { version = "2.0.0-alpha.2", path = "../../indices" }
|
||||
pallet-balances = { version = "2.0.0-alpha.2", path = "../../balances" }
|
||||
pallet-timestamp = { version = "2.0.0-alpha.2", path = "../../timestamp" }
|
||||
frame-system = { version = "2.0.0-alpha.2", path = "../../system" }
|
||||
frame-support = { version = "2.0.0-alpha.2", path = "../../support" }
|
||||
sp-std = { version = "2.0.0-alpha.2", path = "../../../primitives/std" }
|
||||
sp-io ={ version = "2.0.0-alpha.2", path = "../../../primitives/io" }
|
||||
sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" }
|
||||
sp-phragmen = { version = "2.0.0-alpha.2", path = "../../../primitives/phragmen" }
|
||||
sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" }
|
||||
rand = "0.7.3"
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[[bin]]
|
||||
name = "submit_solution"
|
||||
path = "fuzz_targets/submit_solution.rs"
|
||||
@@ -0,0 +1,182 @@
|
||||
// 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/>.
|
||||
|
||||
//! Mock file for staking fuzzing.
|
||||
|
||||
use sp_runtime::traits::{Convert, SaturatedConversion};
|
||||
use frame_support::{impl_outer_origin, impl_outer_dispatch, parameter_types};
|
||||
|
||||
type AccountId = u64;
|
||||
type AccountIndex = u32;
|
||||
type BlockNumber = u64;
|
||||
type Balance = u64;
|
||||
|
||||
type System = frame_system::Module<Test>;
|
||||
type Balances = pallet_balances::Module<Test>;
|
||||
type Staking = pallet_staking::Module<Test>;
|
||||
type Indices = pallet_indices::Module<Test>;
|
||||
type Session = pallet_session::Module<Test>;
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Test where system = frame_system {}
|
||||
}
|
||||
|
||||
impl_outer_dispatch! {
|
||||
pub enum Call for Test where origin: Origin {
|
||||
staking::Staking,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CurrencyToVoteHandler;
|
||||
impl Convert<u64, u64> for CurrencyToVoteHandler {
|
||||
fn convert(x: u64) -> u64 {
|
||||
x
|
||||
}
|
||||
}
|
||||
impl Convert<u128, u64> for CurrencyToVoteHandler {
|
||||
fn convert(x: u128) -> u64 {
|
||||
x.saturated_into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
pub struct Test;
|
||||
|
||||
impl frame_system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = AccountIndex;
|
||||
type BlockNumber = BlockNumber;
|
||||
type Call = Call;
|
||||
type Hash = sp_core::H256;
|
||||
type Hashing = ::sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = Indices;
|
||||
type Header = sp_runtime::testing::Header;
|
||||
type Event = ();
|
||||
type BlockHashCount = ();
|
||||
type MaximumBlockWeight = ();
|
||||
type AvailableBlockRatio = ();
|
||||
type MaximumBlockLength = ();
|
||||
type Version = ();
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = (Balances,);
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: Balance = 10;
|
||||
}
|
||||
impl pallet_balances::Trait for Test {
|
||||
type Balance = Balance;
|
||||
type Event = ();
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
}
|
||||
impl pallet_indices::Trait for Test {
|
||||
type AccountIndex = AccountIndex;
|
||||
type Event = ();
|
||||
type Currency = Balances;
|
||||
type Deposit = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const MinimumPeriod: u64 = 5;
|
||||
}
|
||||
impl pallet_timestamp::Trait for Test {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = ();
|
||||
type MinimumPeriod = MinimumPeriod;
|
||||
}
|
||||
impl pallet_session::historical::Trait for Test {
|
||||
type FullIdentification = pallet_staking::Exposure<AccountId, Balance>;
|
||||
type FullIdentificationOf = pallet_staking::ExposureOf<Test>;
|
||||
}
|
||||
|
||||
sp_runtime::impl_opaque_keys! {
|
||||
pub struct SessionKeys {
|
||||
pub foo: sp_runtime::testing::UintAuthorityId,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestSessionHandler;
|
||||
impl pallet_session::SessionHandler<AccountId> for TestSessionHandler {
|
||||
const KEY_TYPE_IDS: &'static [sp_runtime::KeyTypeId] = &[];
|
||||
|
||||
fn on_genesis_session<Ks: sp_runtime::traits::OpaqueKeys>(_validators: &[(AccountId, Ks)]) {}
|
||||
|
||||
fn on_new_session<Ks: sp_runtime::traits::OpaqueKeys>(
|
||||
_: bool,
|
||||
_: &[(AccountId, Ks)],
|
||||
_: &[(AccountId, Ks)],
|
||||
) {}
|
||||
|
||||
fn on_disabled(_: usize) {}
|
||||
}
|
||||
|
||||
impl pallet_session::Trait for Test {
|
||||
type SessionManager = pallet_session::historical::NoteHistoricalRoot<Test, Staking>;
|
||||
type Keys = SessionKeys;
|
||||
type ShouldEndSession = pallet_session::PeriodicSessions<(), ()>;
|
||||
type NextSessionRotation = pallet_session::PeriodicSessions<(), ()>;
|
||||
type SessionHandler = TestSessionHandler;
|
||||
type Event = ();
|
||||
type ValidatorId = AccountId;
|
||||
type ValidatorIdOf = pallet_staking::StashOf<Test>;
|
||||
type DisabledValidatorsThreshold = ();
|
||||
}
|
||||
pallet_staking_reward_curve::build! {
|
||||
const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!(
|
||||
min_inflation: 0_025_000,
|
||||
max_inflation: 0_100_000,
|
||||
ideal_stake: 0_500_000,
|
||||
falloff: 0_050_000,
|
||||
max_piece_count: 40,
|
||||
test_precision: 0_005_000,
|
||||
);
|
||||
}
|
||||
parameter_types! {
|
||||
pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
|
||||
pub const MaxNominatorRewardedPerValidator: u32 = 64;
|
||||
}
|
||||
|
||||
pub type Extrinsic = sp_runtime::testing::TestXt<Call, ()>;
|
||||
type SubmitTransaction = frame_system::offchain::TransactionSubmitter<
|
||||
sp_runtime::testing::UintAuthorityId,
|
||||
Test,
|
||||
Extrinsic,
|
||||
>;
|
||||
|
||||
impl pallet_staking::Trait for Test {
|
||||
type Currency = Balances;
|
||||
type Time = pallet_timestamp::Module<Self>;
|
||||
type CurrencyToVote = CurrencyToVoteHandler;
|
||||
type RewardRemainder = ();
|
||||
type Event = ();
|
||||
type Slash = ();
|
||||
type Reward = ();
|
||||
type SessionsPerEra = ();
|
||||
type SlashDeferDuration = ();
|
||||
type SlashCancelOrigin = frame_system::EnsureRoot<Self::AccountId>;
|
||||
type BondingDuration = ();
|
||||
type SessionInterface = Self;
|
||||
type RewardCurve = RewardCurve;
|
||||
type NextNewSession = Session;
|
||||
type ElectionLookahead = ();
|
||||
type Call = Call;
|
||||
type SubmitTransaction = SubmitTransaction;
|
||||
type KeyType = sp_runtime::testing::UintAuthorityId;
|
||||
type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// 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/>.
|
||||
|
||||
//! Fuzzing for staking pallet.
|
||||
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use mock::Test;
|
||||
use pallet_staking::testing_utils::{
|
||||
self, USER, get_seq_phragmen_solution, get_weak_solution, setup_chain_stakers,
|
||||
set_validator_count, signed_account,
|
||||
};
|
||||
use frame_support::assert_ok;
|
||||
use sp_runtime::{traits::Dispatchable, DispatchError};
|
||||
|
||||
mod mock;
|
||||
|
||||
#[repr(u32)]
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Mode {
|
||||
/// Initial submission. This will be rather cheap.
|
||||
InitialSubmission,
|
||||
/// A better submission that will replace the previous ones. This is the most expensive.
|
||||
StrongerSubmission,
|
||||
/// A weak submission that will be rejected. This will be rather cheap.
|
||||
WeakerSubmission,
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> Result<sp_io::TestExternalities, std::string::String> {
|
||||
frame_system::GenesisConfig::default().build_storage::<mock::Test>().map(Into::into)
|
||||
}
|
||||
|
||||
fuzz_target!(|do_reduce: bool| {
|
||||
let ext = new_test_ext();
|
||||
let mode: Mode = unsafe { std::mem::transmute(testing_utils::random(0, 2)) };
|
||||
let num_validators = testing_utils::random(50, 500);
|
||||
let num_nominators = testing_utils::random(200, 2000);
|
||||
let edge_per_voter = testing_utils::random(1, 16);
|
||||
let to_elect = testing_utils::random(10, num_validators);
|
||||
|
||||
println!("+++ instance with params {} / {} / {} / {:?} / {}",
|
||||
num_nominators,
|
||||
num_validators,
|
||||
edge_per_voter,
|
||||
mode,
|
||||
to_elect,
|
||||
);
|
||||
|
||||
ext.unwrap_or_default().execute_with(|| {
|
||||
// initial setup
|
||||
set_validator_count::<Test>(to_elect);
|
||||
setup_chain_stakers::<Test>(
|
||||
num_validators,
|
||||
num_nominators,
|
||||
edge_per_voter,
|
||||
);
|
||||
|
||||
println!("++ Chain setup done.");
|
||||
|
||||
// stuff to submit
|
||||
let (winners, compact, score) = match mode {
|
||||
Mode::InitialSubmission => {
|
||||
/* No need to setup anything */
|
||||
get_seq_phragmen_solution::<Test>(do_reduce)
|
||||
},
|
||||
Mode::StrongerSubmission => {
|
||||
let (winners, compact, score) = get_weak_solution::<Test>(false);
|
||||
assert_ok!(
|
||||
<pallet_staking::Module<Test>>::submit_election_solution(
|
||||
signed_account::<Test>(USER),
|
||||
winners,
|
||||
compact,
|
||||
score,
|
||||
)
|
||||
);
|
||||
get_seq_phragmen_solution::<Test>(do_reduce)
|
||||
},
|
||||
Mode::WeakerSubmission => {
|
||||
let (winners, compact, score) = get_seq_phragmen_solution::<Test>(do_reduce);
|
||||
assert_ok!(
|
||||
<pallet_staking::Module<Test>>::submit_election_solution(
|
||||
signed_account::<Test>(USER),
|
||||
winners,
|
||||
compact,
|
||||
score,
|
||||
)
|
||||
);
|
||||
get_weak_solution::<Test>(false)
|
||||
}
|
||||
};
|
||||
|
||||
println!("++ Submission ready.");
|
||||
|
||||
// must have chosen correct number of winners.
|
||||
assert_eq!(winners.len() as u32, <pallet_staking::Module<Test>>::validator_count());
|
||||
|
||||
// final call and origin
|
||||
let call = pallet_staking::Call::<Test>::submit_election_solution(
|
||||
winners,
|
||||
compact,
|
||||
score,
|
||||
);
|
||||
let caller = signed_account::<Test>(USER);
|
||||
|
||||
// actually submit
|
||||
match mode {
|
||||
Mode::WeakerSubmission => {
|
||||
assert_eq!(
|
||||
call.dispatch(caller.into()).unwrap_err(),
|
||||
DispatchError::Module { index: 0, error: 11, message: Some("PhragmenWeakSubmission") },
|
||||
);
|
||||
},
|
||||
_ => assert_ok!(call.dispatch(caller.into())),
|
||||
};
|
||||
})
|
||||
});
|
||||
@@ -402,7 +402,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn create_validators_with_nominators_for_era_works() {
|
||||
ExtBuilder::default().stakers(false).build().execute_with(|| {
|
||||
ExtBuilder::default().has_stakers(false).build().execute_with(|| {
|
||||
let v = 10;
|
||||
let n = 100;
|
||||
|
||||
@@ -418,7 +418,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn create_validator_with_nominators_works() {
|
||||
ExtBuilder::default().stakers(false).build().execute_with(|| {
|
||||
ExtBuilder::default().has_stakers(false).build().execute_with(|| {
|
||||
let n = 10;
|
||||
|
||||
let validator = create_validator_with_nominators::<Test>(
|
||||
@@ -441,7 +441,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn create_nominator_with_validators_works() {
|
||||
ExtBuilder::default().stakers(false).build().execute_with(|| {
|
||||
ExtBuilder::default().has_stakers(false).build().execute_with(|| {
|
||||
let v = 5;
|
||||
|
||||
let (nominator, validators) = create_nominator_with_validators::<Test>(v).unwrap();
|
||||
|
||||
+1046
-200
File diff suppressed because it is too large
Load Diff
@@ -17,57 +17,74 @@
|
||||
//! Test utilities
|
||||
|
||||
use std::{collections::{HashSet, HashMap}, cell::RefCell};
|
||||
use sp_runtime::{Perbill, KeyTypeId};
|
||||
use sp_runtime::Perbill;
|
||||
use sp_runtime::curve::PiecewiseLinear;
|
||||
use sp_runtime::traits::{IdentityLookup, Convert, OpaqueKeys, SaturatedConversion};
|
||||
use sp_runtime::testing::{Header, UintAuthorityId};
|
||||
use sp_runtime::traits::{IdentityLookup, Convert, SaturatedConversion, Zero};
|
||||
use sp_runtime::testing::{Header, UintAuthorityId, TestXt};
|
||||
use sp_staking::{SessionIndex, offence::{OffenceDetails, OnOffenceHandler}};
|
||||
use sp_core::{H256, crypto::key_types};
|
||||
use sp_io;
|
||||
use sp_core::H256;
|
||||
use frame_support::{
|
||||
assert_ok, impl_outer_origin, parameter_types, StorageValue, StorageMap,
|
||||
StorageDoubleMap, IterableStorageMap,
|
||||
traits::{Currency, Get, FindAuthor, OnFinalize, OnInitialize}, weights::Weight,
|
||||
assert_ok, impl_outer_origin, parameter_types, impl_outer_dispatch, impl_outer_event,
|
||||
StorageValue, StorageMap, StorageDoubleMap, IterableStorageMap,
|
||||
traits::{Currency, Get, FindAuthor, OnFinalize, OnInitialize},
|
||||
weights::Weight,
|
||||
};
|
||||
use frame_system::offchain::TransactionSubmitter;
|
||||
use sp_io;
|
||||
use sp_phragmen::{
|
||||
build_support_map, evaluate_support, reduce, ExtendedBalance, StakedAssignment, PhragmenScore,
|
||||
};
|
||||
use crate::{
|
||||
EraIndex, GenesisConfig, Module, Trait, StakerStatus, ValidatorPrefs, RewardDestination,
|
||||
Nominators, inflation, SessionInterface, Exposure, ErasStakers, ErasRewardPoints
|
||||
Nominators, inflation, SessionInterface, Exposure, ErasStakers, ErasRewardPoints,
|
||||
CompactAssignments, ValidatorIndex, NominatorIndex, Validators, OffchainAccuracy,
|
||||
};
|
||||
|
||||
/// The AccountId alias in this test module.
|
||||
pub type AccountId = u64;
|
||||
pub type BlockNumber = u64;
|
||||
pub type Balance = u64;
|
||||
pub(crate) type AccountId = u64;
|
||||
pub(crate) type AccountIndex = u64;
|
||||
pub(crate) type BlockNumber = u64;
|
||||
pub(crate) type Balance = u64;
|
||||
|
||||
/// Simple structure that exposes how u64 currency can be represented as... u64.
|
||||
pub struct CurrencyToVoteHandler;
|
||||
impl Convert<u64, u64> for CurrencyToVoteHandler {
|
||||
fn convert(x: u64) -> u64 { x }
|
||||
fn convert(x: u64) -> u64 {
|
||||
x
|
||||
}
|
||||
}
|
||||
impl Convert<u128, u64> for CurrencyToVoteHandler {
|
||||
fn convert(x: u128) -> u64 { x.saturated_into() }
|
||||
fn convert(x: u128) -> u64 {
|
||||
x.saturated_into()
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static SESSION: RefCell<(Vec<AccountId>, HashSet<AccountId>)> = RefCell::new(Default::default());
|
||||
static SESSION_PER_ERA: RefCell<SessionIndex> = RefCell::new(3);
|
||||
static EXISTENTIAL_DEPOSIT: RefCell<u64> = RefCell::new(0);
|
||||
static SLASH_DEFER_DURATION: RefCell<EraIndex> = RefCell::new(0);
|
||||
static ELECTION_LOOKAHEAD: RefCell<BlockNumber> = RefCell::new(0);
|
||||
static PERIOD: RefCell<BlockNumber> = RefCell::new(1);
|
||||
}
|
||||
|
||||
pub struct TestSessionHandler;
|
||||
impl pallet_session::SessionHandler<AccountId> for TestSessionHandler {
|
||||
const KEY_TYPE_IDS: &'static [KeyTypeId] = &[key_types::DUMMY];
|
||||
/// Another session handler struct to test on_disabled.
|
||||
pub struct OtherSessionHandler;
|
||||
impl pallet_session::OneSessionHandler<AccountId> for OtherSessionHandler {
|
||||
type Key = UintAuthorityId;
|
||||
|
||||
fn on_genesis_session<Ks: OpaqueKeys>(_validators: &[(AccountId, Ks)]) {}
|
||||
fn on_genesis_session<'a, I: 'a>(_: I)
|
||||
where I: Iterator<Item=(&'a AccountId, Self::Key)>, AccountId: 'a {}
|
||||
|
||||
fn on_new_session<Ks: OpaqueKeys>(
|
||||
_changed: bool,
|
||||
validators: &[(AccountId, Ks)],
|
||||
_queued_validators: &[(AccountId, Ks)],
|
||||
) {
|
||||
SESSION.with(|x|
|
||||
*x.borrow_mut() = (validators.iter().map(|x| x.0.clone()).collect(), HashSet::new())
|
||||
);
|
||||
fn on_new_session<'a, I: 'a>(_: bool, validators: I, _: I,)
|
||||
where I: Iterator<Item=(&'a AccountId, Self::Key)>, AccountId: 'a
|
||||
{
|
||||
SESSION.with(|x| {
|
||||
*x.borrow_mut() = (
|
||||
validators.map(|x| x.0.clone()).collect(),
|
||||
HashSet::new(),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
fn on_disabled(validator_index: usize) {
|
||||
@@ -79,6 +96,10 @@ impl pallet_session::SessionHandler<AccountId> for TestSessionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler {
|
||||
type Public = UintAuthorityId;
|
||||
}
|
||||
|
||||
pub fn is_disabled(controller: AccountId) -> bool {
|
||||
let stash = Staking::ledger(&controller).unwrap().stash;
|
||||
SESSION.with(|d| d.borrow().1.contains(&stash))
|
||||
@@ -91,6 +112,32 @@ impl Get<u64> for ExistentialDeposit {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SessionsPerEra;
|
||||
impl Get<SessionIndex> for SessionsPerEra {
|
||||
fn get() -> SessionIndex {
|
||||
SESSION_PER_ERA.with(|v| *v.borrow())
|
||||
}
|
||||
}
|
||||
impl Get<BlockNumber> for SessionsPerEra {
|
||||
fn get() -> BlockNumber {
|
||||
SESSION_PER_ERA.with(|v| *v.borrow() as BlockNumber)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ElectionLookahead;
|
||||
impl Get<BlockNumber> for ElectionLookahead {
|
||||
fn get() -> BlockNumber {
|
||||
ELECTION_LOOKAHEAD.with(|v| *v.borrow())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Period;
|
||||
impl Get<BlockNumber> for Period {
|
||||
fn get() -> BlockNumber {
|
||||
PERIOD.with(|v| *v.borrow())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SlashDeferDuration;
|
||||
impl Get<EraIndex> for SlashDeferDuration {
|
||||
fn get() -> EraIndex {
|
||||
@@ -98,23 +145,47 @@ impl Get<EraIndex> for SlashDeferDuration {
|
||||
}
|
||||
}
|
||||
|
||||
impl_outer_origin!{
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Test where system = frame_system {}
|
||||
}
|
||||
|
||||
impl_outer_dispatch! {
|
||||
pub enum Call for Test where origin: Origin {
|
||||
staking::Staking,
|
||||
}
|
||||
}
|
||||
|
||||
mod staking {
|
||||
// Re-export needed for `impl_outer_event!`.
|
||||
pub use super::super::*;
|
||||
}
|
||||
use frame_system as system;
|
||||
use pallet_balances as balances;
|
||||
use pallet_session as session;
|
||||
|
||||
impl_outer_event! {
|
||||
pub enum MetaEvent for Test {
|
||||
system<T>,
|
||||
balances<T>,
|
||||
session,
|
||||
staking<T>,
|
||||
}
|
||||
}
|
||||
|
||||
/// Author of block is always 11
|
||||
pub struct Author11;
|
||||
impl FindAuthor<u64> for Author11 {
|
||||
fn find_author<'a, I>(_digests: I) -> Option<u64>
|
||||
where I: 'a + IntoIterator<Item=(frame_support::ConsensusEngineId, &'a [u8])>
|
||||
where I: 'a + IntoIterator<Item = (frame_support::ConsensusEngineId, &'a [u8])>,
|
||||
{
|
||||
Some(11)
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
pub struct Test;
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: Weight = 1024;
|
||||
@@ -123,15 +194,15 @@ parameter_types! {
|
||||
}
|
||||
impl frame_system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type Index = AccountIndex;
|
||||
type BlockNumber = BlockNumber;
|
||||
type Call = ();
|
||||
type Call = Call;
|
||||
type Hash = H256;
|
||||
type Hashing = ::sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type Event = MetaEvent;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type AvailableBlockRatio = AvailableBlockRatio;
|
||||
@@ -144,26 +215,31 @@ impl frame_system::Trait for Test {
|
||||
}
|
||||
impl pallet_balances::Trait for Test {
|
||||
type Balance = Balance;
|
||||
type Event = MetaEvent;
|
||||
type DustRemoval = ();
|
||||
type Event = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
}
|
||||
parameter_types! {
|
||||
pub const Period: BlockNumber = 1;
|
||||
pub const Offset: BlockNumber = 0;
|
||||
pub const UncleGenerations: u64 = 0;
|
||||
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(25);
|
||||
}
|
||||
sp_runtime::impl_opaque_keys! {
|
||||
pub struct SessionKeys {
|
||||
pub other: OtherSessionHandler,
|
||||
}
|
||||
}
|
||||
impl pallet_session::Trait for Test {
|
||||
type Event = ();
|
||||
type SessionManager = pallet_session::historical::NoteHistoricalRoot<Test, Staking>;
|
||||
type Keys = SessionKeys;
|
||||
type ShouldEndSession = pallet_session::PeriodicSessions<Period, Offset>;
|
||||
type SessionHandler = (OtherSessionHandler,);
|
||||
type Event = MetaEvent;
|
||||
type ValidatorId = AccountId;
|
||||
type ValidatorIdOf = crate::StashOf<Test>;
|
||||
type ShouldEndSession = pallet_session::PeriodicSessions<Period, Offset>;
|
||||
type SessionManager = pallet_session::historical::NoteHistoricalRoot<Test, Staking>;
|
||||
type SessionHandler = TestSessionHandler;
|
||||
type Keys = UintAuthorityId;
|
||||
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
|
||||
type NextSessionRotation = pallet_session::PeriodicSessions<Period, Offset>;
|
||||
}
|
||||
|
||||
impl pallet_session::historical::Trait for Test {
|
||||
@@ -195,17 +271,17 @@ pallet_staking_reward_curve::build! {
|
||||
);
|
||||
}
|
||||
parameter_types! {
|
||||
pub const SessionsPerEra: SessionIndex = 3;
|
||||
pub const BondingDuration: EraIndex = 3;
|
||||
pub const RewardCurve: &'static PiecewiseLinear<'static> = &I_NPOS;
|
||||
pub const MaxNominatorRewardedPerValidator: u32 = 64;
|
||||
}
|
||||
|
||||
impl Trait for Test {
|
||||
type Currency = Balances;
|
||||
type UnixTime = Timestamp;
|
||||
type CurrencyToVote = CurrencyToVoteHandler;
|
||||
type RewardRemainder = ();
|
||||
type Event = ();
|
||||
type Event = MetaEvent;
|
||||
type Slash = ();
|
||||
type Reward = ();
|
||||
type SessionsPerEra = SessionsPerEra;
|
||||
@@ -214,11 +290,21 @@ impl Trait for Test {
|
||||
type BondingDuration = BondingDuration;
|
||||
type SessionInterface = Self;
|
||||
type RewardCurve = RewardCurve;
|
||||
type NextNewSession = Session;
|
||||
type ElectionLookahead = ElectionLookahead;
|
||||
type Call = Call;
|
||||
type SubmitTransaction = SubmitTransaction;
|
||||
type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator;
|
||||
}
|
||||
|
||||
pub type Extrinsic = TestXt<Call, ()>;
|
||||
type SubmitTransaction = TransactionSubmitter<(), Test, Extrinsic>;
|
||||
|
||||
pub struct ExtBuilder {
|
||||
existential_deposit: u64,
|
||||
session_length: BlockNumber,
|
||||
election_lookahead: BlockNumber,
|
||||
session_per_era: SessionIndex,
|
||||
existential_deposit: Balance,
|
||||
validator_pool: bool,
|
||||
nominate: bool,
|
||||
validator_count: u32,
|
||||
@@ -226,13 +312,16 @@ pub struct ExtBuilder {
|
||||
slash_defer_duration: EraIndex,
|
||||
fair: bool,
|
||||
num_validators: Option<u32>,
|
||||
invulnerables: Vec<u64>,
|
||||
stakers: bool,
|
||||
invulnerables: Vec<AccountId>,
|
||||
has_stakers: bool,
|
||||
}
|
||||
|
||||
impl Default for ExtBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
session_length: 1,
|
||||
election_lookahead: 0,
|
||||
session_per_era: 3,
|
||||
existential_deposit: 1,
|
||||
validator_pool: false,
|
||||
nominate: true,
|
||||
@@ -242,7 +331,7 @@ impl Default for ExtBuilder {
|
||||
fair: true,
|
||||
num_validators: None,
|
||||
invulnerables: vec![],
|
||||
stakers: true,
|
||||
has_stakers: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,18 +373,40 @@ impl ExtBuilder {
|
||||
self.invulnerables = invulnerables;
|
||||
self
|
||||
}
|
||||
pub fn set_associated_consts(&self) {
|
||||
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
|
||||
SLASH_DEFER_DURATION.with(|v| *v.borrow_mut() = self.slash_defer_duration);
|
||||
}
|
||||
|
||||
pub fn stakers(mut self, has_stakers: bool) -> Self {
|
||||
self.stakers = has_stakers;
|
||||
pub fn session_per_era(mut self, length: SessionIndex) -> Self {
|
||||
self.session_per_era = length;
|
||||
self
|
||||
}
|
||||
pub fn election_lookahead(mut self, look: BlockNumber) -> Self {
|
||||
self.election_lookahead = look;
|
||||
self
|
||||
}
|
||||
pub fn session_length(mut self, length: BlockNumber) -> Self {
|
||||
self.session_length = length;
|
||||
self
|
||||
}
|
||||
pub fn has_stakers(mut self, has: bool) -> Self {
|
||||
self.has_stakers = has;
|
||||
self
|
||||
}
|
||||
pub fn offchain_phragmen_ext(self) -> Self {
|
||||
self.session_per_era(4)
|
||||
.session_length(5)
|
||||
.election_lookahead(3)
|
||||
}
|
||||
pub fn set_associated_constants(&self) {
|
||||
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
|
||||
SLASH_DEFER_DURATION.with(|v| *v.borrow_mut() = self.slash_defer_duration);
|
||||
SESSION_PER_ERA.with(|v| *v.borrow_mut() = self.session_per_era);
|
||||
ELECTION_LOOKAHEAD.with(|v| *v.borrow_mut() = self.election_lookahead);
|
||||
PERIOD.with(|v| *v.borrow_mut() = self.session_length);
|
||||
}
|
||||
pub fn build(self) -> sp_io::TestExternalities {
|
||||
self.set_associated_consts();
|
||||
let mut storage = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
let _ = env_logger::try_init();
|
||||
self.set_associated_constants();
|
||||
let mut storage = frame_system::GenesisConfig::default()
|
||||
.build_storage::<Test>()
|
||||
.unwrap();
|
||||
let balance_factor = if self.existential_deposit > 1 {
|
||||
256
|
||||
} else {
|
||||
@@ -307,7 +418,7 @@ impl ExtBuilder {
|
||||
.map(|x| ((x + 1) * 10 + 1) as u64)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let _ = pallet_balances::GenesisConfig::<Test>{
|
||||
let _ = pallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![
|
||||
(1, 10 * balance_factor),
|
||||
(2, 20 * balance_factor),
|
||||
@@ -329,7 +440,7 @@ impl ExtBuilder {
|
||||
}.assimilate_storage(&mut storage);
|
||||
|
||||
let mut stakers = vec![];
|
||||
if self.stakers {
|
||||
if self.has_stakers {
|
||||
let stake_21 = if self.fair { 1000 } else { 2000 };
|
||||
let stake_31 = if self.validator_pool { balance_factor * 1000 } else { 1 };
|
||||
let status_41 = if self.validator_pool {
|
||||
@@ -355,18 +466,21 @@ impl ExtBuilder {
|
||||
invulnerables: self.invulnerables,
|
||||
slash_reward_fraction: Perbill::from_percent(10),
|
||||
..Default::default()
|
||||
}.assimilate_storage(&mut storage);
|
||||
}
|
||||
.assimilate_storage(&mut storage);
|
||||
|
||||
let _ = pallet_session::GenesisConfig::<Test> {
|
||||
keys: validators.iter().map(|x| (*x, *x, UintAuthorityId(*x))).collect(),
|
||||
keys: validators.iter().map(|x| (
|
||||
*x,
|
||||
*x,
|
||||
SessionKeys { other: UintAuthorityId(*x) }
|
||||
)).collect(),
|
||||
}.assimilate_storage(&mut storage);
|
||||
|
||||
let mut ext = sp_io::TestExternalities::from(storage);
|
||||
ext.execute_with(|| {
|
||||
let validators = Session::validators();
|
||||
SESSION.with(|x|
|
||||
*x.borrow_mut() = (validators.clone(), HashSet::new())
|
||||
);
|
||||
SESSION.with(|x| *x.borrow_mut() = (validators.clone(), HashSet::new()));
|
||||
});
|
||||
ext
|
||||
}
|
||||
@@ -378,6 +492,10 @@ pub type Session = pallet_session::Module<Test>;
|
||||
pub type Timestamp = pallet_timestamp::Module<Test>;
|
||||
pub type Staking = Module<Test>;
|
||||
|
||||
pub fn active_era() -> EraIndex {
|
||||
Staking::active_era().unwrap().index
|
||||
}
|
||||
|
||||
pub fn check_exposure_all(era: EraIndex) {
|
||||
ErasStakers::<Test>::iter_prefix(era).for_each(check_exposure)
|
||||
}
|
||||
@@ -390,8 +508,9 @@ pub fn check_nominator_all(era: EraIndex) {
|
||||
/// Check for each selected validator: expo.total = Sum(expo.other) + expo.own
|
||||
pub fn check_exposure(expo: Exposure<AccountId, Balance>) {
|
||||
assert_eq!(
|
||||
expo.total as u128, expo.own as u128 + expo.others.iter().map(|e| e.value as u128).sum::<u128>(),
|
||||
"wrong total exposure {:?}", expo,
|
||||
expo.total as u128,
|
||||
expo.own as u128 + expo.others.iter().map(|e| e.value as u128).sum::<u128>(),
|
||||
"wrong total exposure",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -400,17 +519,18 @@ pub fn check_exposure(expo: Exposure<AccountId, Balance>) {
|
||||
pub fn check_nominator_exposure(era: EraIndex, stash: AccountId) {
|
||||
assert_is_stash(stash);
|
||||
let mut sum = 0;
|
||||
ErasStakers::<Test>::iter_prefix(era)
|
||||
.for_each(|exposure| {
|
||||
exposure.others.iter()
|
||||
.filter(|i| i.who == stash)
|
||||
.for_each(|i| sum += i.value)
|
||||
});
|
||||
Session::validators()
|
||||
.iter()
|
||||
.map(|v| Staking::eras_stakers(era, v))
|
||||
.for_each(|e| e.others.iter().filter(|i| i.who == stash).for_each(|i| sum += i.value));
|
||||
let nominator_stake = Staking::slashable_balance_of(&stash);
|
||||
// a nominator cannot over-spend.
|
||||
assert!(
|
||||
nominator_stake >= sum,
|
||||
"failed: Nominator({}) stake({}) >= sum divided({})", stash, nominator_stake, sum,
|
||||
"failed: Nominator({}) stake({}) >= sum divided({})",
|
||||
stash,
|
||||
nominator_stake,
|
||||
sum,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -426,20 +546,41 @@ pub fn assert_ledger_consistent(stash: AccountId) {
|
||||
assert_eq!(real_total, ledger.total);
|
||||
}
|
||||
|
||||
pub fn bond_validator(acc: u64, val: u64) {
|
||||
// a = controller
|
||||
// a + 1 = stash
|
||||
let _ = Balances::make_free_balance_be(&(acc + 1), val);
|
||||
assert_ok!(Staking::bond(Origin::signed(acc + 1), acc, val, RewardDestination::Controller));
|
||||
assert_ok!(Staking::validate(Origin::signed(acc), ValidatorPrefs::default()));
|
||||
pub fn bond_validator(stash: u64, ctrl: u64, val: u64) {
|
||||
let _ = Balances::make_free_balance_be(&stash, val);
|
||||
assert_ok!(Staking::bond(
|
||||
Origin::signed(stash),
|
||||
ctrl,
|
||||
val,
|
||||
RewardDestination::Controller,
|
||||
));
|
||||
assert_ok!(Staking::validate(
|
||||
Origin::signed(ctrl),
|
||||
ValidatorPrefs::default()
|
||||
));
|
||||
}
|
||||
|
||||
pub fn bond_nominator(acc: u64, val: u64, target: Vec<u64>) {
|
||||
// a = controller
|
||||
// a + 1 = stash
|
||||
let _ = Balances::make_free_balance_be(&(acc + 1), val);
|
||||
assert_ok!(Staking::bond(Origin::signed(acc + 1), acc, val, RewardDestination::Controller));
|
||||
assert_ok!(Staking::nominate(Origin::signed(acc), target));
|
||||
pub fn bond_nominator(stash: u64, ctrl: u64, val: u64, target: Vec<u64>) {
|
||||
let _ = Balances::make_free_balance_be(&stash, val);
|
||||
assert_ok!(Staking::bond(
|
||||
Origin::signed(stash),
|
||||
ctrl,
|
||||
val,
|
||||
RewardDestination::Controller,
|
||||
));
|
||||
assert_ok!(Staking::nominate(Origin::signed(ctrl), target));
|
||||
}
|
||||
|
||||
pub fn run_to_block(n: BlockNumber) {
|
||||
Staking::on_finalize(System::block_number());
|
||||
for b in System::block_number() + 1..=n {
|
||||
System::set_block_number(b);
|
||||
Session::on_initialize(b);
|
||||
Staking::on_initialize(b);
|
||||
if b != n {
|
||||
Staking::on_finalize(System::block_number());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance_session() {
|
||||
@@ -448,19 +589,21 @@ pub fn advance_session() {
|
||||
}
|
||||
|
||||
pub fn start_session(session_index: SessionIndex) {
|
||||
assert_eq!(<Period as Get<BlockNumber>>::get(), 1, "start_session can only be used with session length 1.");
|
||||
for i in Session::current_index()..session_index {
|
||||
Staking::on_finalize(System::block_number());
|
||||
System::set_block_number((i + 1).into());
|
||||
Timestamp::set_timestamp(System::block_number() * 1000);
|
||||
Session::on_initialize(System::block_number());
|
||||
Staking::on_initialize(System::block_number());
|
||||
}
|
||||
|
||||
assert_eq!(Session::current_index(), session_index);
|
||||
}
|
||||
|
||||
pub fn start_era(era_index: EraIndex) {
|
||||
start_session((era_index * 3).into());
|
||||
assert_eq!(Staking::active_era().unwrap().index, era_index);
|
||||
start_session((era_index * <SessionsPerEra as Get<u32>>::get()).into());
|
||||
assert_eq!(Staking::current_era().unwrap(), era_index);
|
||||
}
|
||||
|
||||
pub fn current_total_payout_for_duration(duration: u64) -> u64 {
|
||||
@@ -473,33 +616,45 @@ pub fn current_total_payout_for_duration(duration: u64) -> u64 {
|
||||
}
|
||||
|
||||
pub fn reward_all_elected() {
|
||||
let rewards = <Test as Trait>::SessionInterface::validators().into_iter()
|
||||
let rewards = <Test as Trait>::SessionInterface::validators()
|
||||
.into_iter()
|
||||
.map(|v| (v, 1));
|
||||
|
||||
<Module<Test>>::reward_by_ids(rewards)
|
||||
}
|
||||
|
||||
pub fn validator_controllers() -> Vec<AccountId> {
|
||||
Session::validators().into_iter().map(|s| Staking::bonded(&s).expect("no controller for validator")).collect()
|
||||
Session::validators()
|
||||
.into_iter()
|
||||
.map(|s| Staking::bonded(&s).expect("no controller for validator"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn on_offence_in_era(
|
||||
offenders: &[OffenceDetails<AccountId, pallet_session::historical::IdentificationTuple<Test>>],
|
||||
offenders: &[OffenceDetails<
|
||||
AccountId,
|
||||
pallet_session::historical::IdentificationTuple<Test>,
|
||||
>],
|
||||
slash_fraction: &[Perbill],
|
||||
era: EraIndex,
|
||||
) {
|
||||
let bonded_eras = crate::BondedEras::get();
|
||||
for &(bonded_era, start_session) in bonded_eras.iter() {
|
||||
if bonded_era == era {
|
||||
Staking::on_offence(offenders, slash_fraction, start_session);
|
||||
return
|
||||
let _ = Staking::on_offence(offenders, slash_fraction, start_session).unwrap();
|
||||
return;
|
||||
} else if bonded_era > era {
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if Staking::active_era().unwrap().index == era {
|
||||
Staking::on_offence(offenders, slash_fraction, Staking::eras_start_session_index(era).unwrap());
|
||||
let _ =
|
||||
Staking::on_offence(
|
||||
offenders,
|
||||
slash_fraction,
|
||||
Staking::eras_start_session_index(era).unwrap()
|
||||
).unwrap();
|
||||
} else {
|
||||
panic!("cannot slash in era {}", era);
|
||||
}
|
||||
@@ -513,6 +668,193 @@ pub fn on_offence_now(
|
||||
on_offence_in_era(offenders, slash_fraction, now)
|
||||
}
|
||||
|
||||
// winners will be chosen by simply their unweighted total backing stake. Nominator stake is
|
||||
// distributed evenly.
|
||||
pub fn horrible_phragmen_with_post_processing(
|
||||
do_reduce: bool,
|
||||
) -> (CompactAssignments, Vec<ValidatorIndex>, PhragmenScore) {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
let mut backing_stake_of: BTreeMap<AccountId, Balance> = BTreeMap::new();
|
||||
|
||||
// self stake
|
||||
<Validators<Test>>::iter().for_each(|(who, _p)| {
|
||||
*backing_stake_of.entry(who).or_insert(Zero::zero()) += Staking::slashable_balance_of(&who)
|
||||
});
|
||||
|
||||
// add nominator stuff
|
||||
<Nominators<Test>>::iter().for_each(|(who, nomination)| {
|
||||
nomination.targets.iter().for_each(|v| {
|
||||
*backing_stake_of.entry(*v).or_insert(Zero::zero()) +=
|
||||
Staking::slashable_balance_of(&who)
|
||||
})
|
||||
});
|
||||
|
||||
// elect winners
|
||||
let mut sorted: Vec<AccountId> = backing_stake_of.keys().cloned().collect();
|
||||
sorted.sort_by_key(|x| backing_stake_of.get(x).unwrap());
|
||||
let winners: Vec<AccountId> = sorted
|
||||
.iter()
|
||||
.cloned()
|
||||
.take(Staking::validator_count() as usize)
|
||||
.collect();
|
||||
|
||||
// create assignments
|
||||
let mut staked_assignment: Vec<StakedAssignment<AccountId>> = Vec::new();
|
||||
<Nominators<Test>>::iter().for_each(|(who, nomination)| {
|
||||
let mut dist: Vec<(AccountId, ExtendedBalance)> = Vec::new();
|
||||
nomination.targets.iter().for_each(|v| {
|
||||
if winners.iter().find(|w| *w == v).is_some() {
|
||||
dist.push((*v, ExtendedBalance::zero()));
|
||||
}
|
||||
});
|
||||
|
||||
if dist.len() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// assign real stakes. just split the stake.
|
||||
let stake = Staking::slashable_balance_of(&who) as ExtendedBalance;
|
||||
let mut sum: ExtendedBalance = Zero::zero();
|
||||
let dist_len = dist.len();
|
||||
{
|
||||
dist.iter_mut().for_each(|(_, w)| {
|
||||
let partial = stake / (dist_len as ExtendedBalance);
|
||||
*w = partial;
|
||||
sum += partial;
|
||||
});
|
||||
}
|
||||
|
||||
// assign the leftover to last.
|
||||
{
|
||||
let leftover = stake - sum;
|
||||
let last = dist.last_mut().unwrap();
|
||||
last.1 += leftover;
|
||||
}
|
||||
|
||||
staked_assignment.push(StakedAssignment {
|
||||
who,
|
||||
distribution: dist,
|
||||
});
|
||||
});
|
||||
|
||||
// Ensure that this result is worse than seq-phragmen. Otherwise, it should not have been used
|
||||
// for testing.
|
||||
let score = {
|
||||
let (_, _, better_score) = prepare_submission_with(true, |_| {});
|
||||
|
||||
let support = build_support_map::<AccountId>(&winners, &staked_assignment).0;
|
||||
let score = evaluate_support(&support);
|
||||
|
||||
assert!(sp_phragmen::is_score_better(score, better_score));
|
||||
|
||||
score
|
||||
};
|
||||
|
||||
if do_reduce {
|
||||
reduce(&mut staked_assignment);
|
||||
}
|
||||
|
||||
let snapshot_validators = Staking::snapshot_validators().unwrap();
|
||||
let snapshot_nominators = Staking::snapshot_nominators().unwrap();
|
||||
let nominator_index = |a: &AccountId| -> Option<NominatorIndex> {
|
||||
snapshot_nominators.iter().position(|x| x == a).map(|i| i as NominatorIndex)
|
||||
};
|
||||
let validator_index = |a: &AccountId| -> Option<ValidatorIndex> {
|
||||
snapshot_validators.iter().position(|x| x == a).map(|i| i as ValidatorIndex)
|
||||
};
|
||||
|
||||
// convert back to ratio assignment. This takes less space.
|
||||
let assignments_reduced =
|
||||
sp_phragmen::assignment_staked_to_ratio::<AccountId, OffchainAccuracy>(staked_assignment);
|
||||
|
||||
let compact =
|
||||
CompactAssignments::from_assignment(assignments_reduced, nominator_index, validator_index)
|
||||
.unwrap();
|
||||
|
||||
// winner ids to index
|
||||
let winners = winners.into_iter().map(|w| validator_index(&w).unwrap()).collect::<Vec<_>>();
|
||||
|
||||
(compact, winners, score)
|
||||
}
|
||||
|
||||
// Note: this should always logically reproduce [`offchain_election::prepare_submission`], yet we
|
||||
// cannot do it since we want to have `tweak` injected into the process.
|
||||
pub fn prepare_submission_with(
|
||||
do_reduce: bool,
|
||||
tweak: impl FnOnce(&mut Vec<StakedAssignment<AccountId>>),
|
||||
) -> (CompactAssignments, Vec<ValidatorIndex>, PhragmenScore) {
|
||||
// run phragmen on the default stuff.
|
||||
let sp_phragmen::PhragmenResult {
|
||||
winners,
|
||||
assignments,
|
||||
} = Staking::do_phragmen::<OffchainAccuracy>().unwrap();
|
||||
let winners = winners.into_iter().map(|(w, _)| w).collect::<Vec<AccountId>>();
|
||||
|
||||
let stake_of = |who: &AccountId| -> ExtendedBalance {
|
||||
<CurrencyToVoteHandler as Convert<Balance, u64>>::convert(
|
||||
Staking::slashable_balance_of(&who)
|
||||
) as ExtendedBalance
|
||||
};
|
||||
let mut staked = sp_phragmen::assignment_ratio_to_staked(assignments, stake_of);
|
||||
|
||||
// apply custom tweaks. awesome for testing.
|
||||
tweak(&mut staked);
|
||||
|
||||
if do_reduce {
|
||||
reduce(&mut staked);
|
||||
}
|
||||
|
||||
// convert back to ratio assignment. This takes less space.
|
||||
let snapshot_validators = Staking::snapshot_validators().expect("snapshot not created.");
|
||||
let snapshot_nominators = Staking::snapshot_nominators().expect("snapshot not created.");
|
||||
let nominator_index = |a: &AccountId| -> Option<NominatorIndex> {
|
||||
snapshot_nominators
|
||||
.iter()
|
||||
.position(|x| x == a)
|
||||
.map_or_else(
|
||||
|| { println!("unable to find nominator index for {:?}", a); None },
|
||||
|i| Some(i as NominatorIndex),
|
||||
)
|
||||
};
|
||||
let validator_index = |a: &AccountId| -> Option<ValidatorIndex> {
|
||||
snapshot_validators
|
||||
.iter()
|
||||
.position(|x| x == a)
|
||||
.map_or_else(
|
||||
|| { println!("unable to find validator index for {:?}", a); None },
|
||||
|i| Some(i as ValidatorIndex),
|
||||
)
|
||||
};
|
||||
|
||||
let assignments_reduced = sp_phragmen::assignment_staked_to_ratio(staked);
|
||||
|
||||
// re-compute score by converting, yet again, into staked type
|
||||
let score = {
|
||||
let staked = sp_phragmen::assignment_ratio_to_staked(
|
||||
assignments_reduced.clone(),
|
||||
Staking::slashable_balance_of_extended,
|
||||
);
|
||||
|
||||
let (support_map, _) = build_support_map::<AccountId>(
|
||||
winners.as_slice(),
|
||||
staked.as_slice(),
|
||||
);
|
||||
evaluate_support::<AccountId>(&support_map)
|
||||
};
|
||||
|
||||
let compact =
|
||||
CompactAssignments::from_assignment(assignments_reduced, nominator_index, validator_index)
|
||||
.map_err(|e| { println!("error in compact: {:?}", e); e })
|
||||
.expect("Failed to create compact");
|
||||
|
||||
|
||||
// winner ids to index
|
||||
let winners = winners.into_iter().map(|w| validator_index(&w).unwrap()).collect::<Vec<_>>();
|
||||
|
||||
(compact, winners, score)
|
||||
}
|
||||
|
||||
/// Make all validator and nominator request their payment
|
||||
pub fn make_all_reward_payment(era: EraIndex) {
|
||||
let validators_with_reward = ErasRewardPoints::<Test>::get(era).individual.keys()
|
||||
@@ -544,3 +886,23 @@ pub fn make_all_reward_payment(era: EraIndex) {
|
||||
assert_ok!(Staking::payout_validator(Origin::signed(validator_controller), era));
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_session_era {
|
||||
($session:expr, $era:expr) => {
|
||||
assert_eq!(
|
||||
Session::current_index(),
|
||||
$session,
|
||||
"wrong session {} != {}",
|
||||
Session::current_index(),
|
||||
$session,
|
||||
);
|
||||
assert_eq!(
|
||||
Staking::active_era().unwrap().index,
|
||||
$era,
|
||||
"wrong active era {} != {}",
|
||||
Staking::active_era().unwrap().index,
|
||||
$era,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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/>.
|
||||
|
||||
//! Helpers for offchain worker election.
|
||||
|
||||
use crate::{
|
||||
Call, CompactAssignments, Module, NominatorIndex, OffchainAccuracy, Trait, ValidatorIndex,
|
||||
};
|
||||
use frame_system::offchain::SubmitUnsignedTransaction;
|
||||
use sp_phragmen::{
|
||||
build_support_map, evaluate_support, reduce, Assignment, ExtendedBalance, PhragmenResult,
|
||||
PhragmenScore,
|
||||
};
|
||||
use sp_runtime::offchain::storage::StorageValueRef;
|
||||
use sp_runtime::PerThing;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::{convert::TryInto, prelude::*};
|
||||
|
||||
/// Error types related to the offchain election machinery.
|
||||
#[derive(RuntimeDebug)]
|
||||
pub enum OffchainElectionError {
|
||||
/// Phragmen election returned None. This means less candidate that minimum number of needed
|
||||
/// validators were present. The chain is in trouble and not much that we can do about it.
|
||||
ElectionFailed,
|
||||
/// Submission to the transaction pool failed.
|
||||
PoolSubmissionFailed,
|
||||
/// The snapshot data is not available.
|
||||
SnapshotUnavailable,
|
||||
/// Error from phragmen crate. This usually relates to compact operation.
|
||||
PhragmenError(sp_phragmen::Error),
|
||||
/// One of the computed winners is invalid.
|
||||
InvalidWinner,
|
||||
}
|
||||
|
||||
impl From<sp_phragmen::Error> for OffchainElectionError {
|
||||
fn from(e: sp_phragmen::Error) -> Self {
|
||||
Self::PhragmenError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Storage key used to store the persistent offchain worker status.
|
||||
pub(crate) const OFFCHAIN_HEAD_DB: &[u8] = b"parity/staking-election/";
|
||||
/// The repeat threshold of the offchain worker. This means we won't run the offchain worker twice
|
||||
/// within a window of 5 blocks.
|
||||
pub(crate) const OFFCHAIN_REPEAT: u32 = 5;
|
||||
/// Default number of blocks for which the unsigned transaction should stay in the pool
|
||||
pub(crate) const DEFAULT_LONGEVITY: u64 = 25;
|
||||
|
||||
/// Checks if an execution of the offchain worker is permitted at the given block number, or not.
|
||||
///
|
||||
/// This essentially makes sure that we don't run on previous blocks in case of a re-org, and we
|
||||
/// don't run twice within a window of length [`OFFCHAIN_REPEAT`].
|
||||
///
|
||||
/// Returns `Ok(())` if offchain worker should happen, `Err(reason)` otherwise.
|
||||
pub(crate) fn set_check_offchain_execution_status<T: Trait>(
|
||||
now: T::BlockNumber,
|
||||
) -> Result<(), &'static str> {
|
||||
let storage = StorageValueRef::persistent(&OFFCHAIN_HEAD_DB);
|
||||
let threshold = T::BlockNumber::from(OFFCHAIN_REPEAT);
|
||||
|
||||
let mutate_stat =
|
||||
storage.mutate::<_, &'static str, _>(|maybe_head: Option<Option<T::BlockNumber>>| {
|
||||
match maybe_head {
|
||||
Some(Some(head)) if now < head => Err("fork."),
|
||||
Some(Some(head)) if now >= head && now <= head + threshold => {
|
||||
Err("recently executed.")
|
||||
}
|
||||
Some(Some(head)) if now > head + threshold => {
|
||||
// we can run again now. Write the new head.
|
||||
Ok(now)
|
||||
}
|
||||
_ => {
|
||||
// value doesn't exists. Probably this node just booted up. Write, and run
|
||||
Ok(now)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
match mutate_stat {
|
||||
// all good
|
||||
Ok(Ok(_)) => Ok(()),
|
||||
// failed to write.
|
||||
Ok(Err(_)) => Err("failed to write to offchain db."),
|
||||
// fork etc.
|
||||
Err(why) => Err(why),
|
||||
}
|
||||
}
|
||||
|
||||
/// The internal logic of the offchain worker of this module. This runs the phragmen election,
|
||||
/// compacts and reduces the solution, computes the score and submits it back to the chain as an
|
||||
/// unsigned transaction, without any signature.
|
||||
pub(crate) fn compute_offchain_election<T: Trait>() -> Result<(), OffchainElectionError> {
|
||||
// compute raw solution. Note that we use `OffchainAccuracy`.
|
||||
let PhragmenResult {
|
||||
winners,
|
||||
assignments,
|
||||
} = <Module<T>>::do_phragmen::<OffchainAccuracy>()
|
||||
.ok_or(OffchainElectionError::ElectionFailed)?;
|
||||
|
||||
// process and prepare it for submission.
|
||||
let (winners, compact, score) = prepare_submission::<T>(assignments, winners, true)?;
|
||||
|
||||
// defensive-only: active era can never be none except genesis.
|
||||
let era = <Module<T>>::active_era().map(|e| e.index).unwrap_or_default();
|
||||
|
||||
// send it.
|
||||
let call: <T as Trait>::Call = Call::submit_election_solution_unsigned(
|
||||
winners,
|
||||
compact,
|
||||
score,
|
||||
era,
|
||||
).into();
|
||||
|
||||
T::SubmitTransaction::submit_unsigned(call)
|
||||
.map_err(|_| OffchainElectionError::PoolSubmissionFailed)
|
||||
}
|
||||
|
||||
/// Takes a phragmen result and spits out some data that can be submitted to the chain.
|
||||
///
|
||||
/// This does a lot of stuff; read the inline comments.
|
||||
pub fn prepare_submission<T: Trait>(
|
||||
assignments: Vec<Assignment<T::AccountId, OffchainAccuracy>>,
|
||||
winners: Vec<(T::AccountId, ExtendedBalance)>,
|
||||
do_reduce: bool,
|
||||
) -> Result<(Vec<ValidatorIndex>, CompactAssignments, PhragmenScore), OffchainElectionError> where
|
||||
ExtendedBalance: From<<OffchainAccuracy as PerThing>::Inner>,
|
||||
{
|
||||
// make sure that the snapshot is available.
|
||||
let snapshot_validators =
|
||||
<Module<T>>::snapshot_validators().ok_or(OffchainElectionError::SnapshotUnavailable)?;
|
||||
let snapshot_nominators =
|
||||
<Module<T>>::snapshot_nominators().ok_or(OffchainElectionError::SnapshotUnavailable)?;
|
||||
|
||||
// all helper closures
|
||||
let nominator_index = |a: &T::AccountId| -> Option<NominatorIndex> {
|
||||
snapshot_nominators
|
||||
.iter()
|
||||
.position(|x| x == a)
|
||||
.and_then(|i| <usize as TryInto<NominatorIndex>>::try_into(i).ok())
|
||||
};
|
||||
let validator_index = |a: &T::AccountId| -> Option<ValidatorIndex> {
|
||||
snapshot_validators
|
||||
.iter()
|
||||
.position(|x| x == a)
|
||||
.and_then(|i| <usize as TryInto<ValidatorIndex>>::try_into(i).ok())
|
||||
};
|
||||
|
||||
// Clean winners.
|
||||
let winners = winners
|
||||
.into_iter()
|
||||
.map(|(w, _)| w)
|
||||
.collect::<Vec<T::AccountId>>();
|
||||
|
||||
// convert into absolute value and to obtain the reduced version.
|
||||
let mut staked = sp_phragmen::assignment_ratio_to_staked(
|
||||
assignments,
|
||||
<Module<T>>::slashable_balance_of_extended,
|
||||
);
|
||||
|
||||
if do_reduce {
|
||||
reduce(&mut staked);
|
||||
}
|
||||
|
||||
// Convert back to ratio assignment. This takes less space.
|
||||
let low_accuracy_assignment = sp_phragmen::assignment_staked_to_ratio(staked);
|
||||
|
||||
// convert back to staked to compute the score in the receiver's accuracy. This can be done
|
||||
// nicer, for now we do it as such since this code is not time-critical. This ensure that the
|
||||
// score _predicted_ here is the same as the one computed on chain and you will not get a
|
||||
// `PhragmenBogusScore` error. This is totally NOT needed if we don't do reduce. This whole
|
||||
// _accuracy glitch_ happens because reduce breaks that assumption of rounding and **scale**.
|
||||
// The initial phragmen results are computed in `OffchainAccuracy` and the initial `staked`
|
||||
// assignment set is also all multiples of this value. After reduce, this no longer holds. Hence
|
||||
// converting to ratio thereafter is not trivially reversible.
|
||||
let score = {
|
||||
let staked = sp_phragmen::assignment_ratio_to_staked(
|
||||
low_accuracy_assignment.clone(),
|
||||
<Module<T>>::slashable_balance_of_extended,
|
||||
);
|
||||
|
||||
let (support_map, _) = build_support_map::<T::AccountId>(&winners, &staked);
|
||||
evaluate_support::<T::AccountId>(&support_map)
|
||||
};
|
||||
|
||||
// compact encode the assignment.
|
||||
let compact = CompactAssignments::from_assignment(
|
||||
low_accuracy_assignment,
|
||||
nominator_index,
|
||||
validator_index,
|
||||
).map_err(|e| OffchainElectionError::from(e))?;
|
||||
|
||||
// winners to index. Use a simple for loop for a more expressive early exit in case of error.
|
||||
let mut winners_indexed: Vec<ValidatorIndex> = Vec::with_capacity(winners.len());
|
||||
for w in winners {
|
||||
if let Some(idx) = snapshot_validators.iter().position(|v| *v == w) {
|
||||
let compact_index: ValidatorIndex = idx
|
||||
.try_into()
|
||||
.map_err(|_| OffchainElectionError::InvalidWinner)?;
|
||||
winners_indexed.push(compact_index);
|
||||
} else {
|
||||
return Err(OffchainElectionError::InvalidWinner);
|
||||
}
|
||||
}
|
||||
|
||||
Ok((winners_indexed, compact, score))
|
||||
}
|
||||
@@ -16,11 +16,11 @@
|
||||
|
||||
//! A slashing implementation for NPoS systems.
|
||||
//!
|
||||
//! For the purposes of the economic model, it is easiest to think of each validator
|
||||
//! of a nominator which nominates only its own identity.
|
||||
//! For the purposes of the economic model, it is easiest to think of each validator as a nominator
|
||||
//! which nominates only its own identity.
|
||||
//!
|
||||
//! The act of nomination signals intent to unify economic identity with the validator - to take part in the
|
||||
//! rewards of a job well done, and to take part in the punishment of a job done badly.
|
||||
//! The act of nomination signals intent to unify economic identity with the validator - to take
|
||||
//! part in the rewards of a job well done, and to take part in the punishment of a job done badly.
|
||||
//!
|
||||
//! There are 3 main difficulties to account for with slashing in NPoS:
|
||||
//! - A nominator can nominate multiple validators and be slashed via any of them.
|
||||
@@ -52,7 +52,7 @@ use super::{
|
||||
EraIndex, Trait, Module, Store, BalanceOf, Exposure, Perbill, SessionInterface,
|
||||
NegativeImbalanceOf, UnappliedSlash,
|
||||
};
|
||||
use sp_runtime::{traits::{Zero, Saturating}, PerThing};
|
||||
use sp_runtime::{traits::{Zero, Saturating}, PerThing, RuntimeDebug};
|
||||
use frame_support::{
|
||||
StorageMap, StorageDoubleMap,
|
||||
traits::{Currency, OnUnbalanced, Imbalance},
|
||||
@@ -65,7 +65,7 @@ use codec::{Encode, Decode};
|
||||
const REWARD_F1: Perbill = Perbill::from_percent(50);
|
||||
|
||||
/// The index of a slashing span - unique to each stash.
|
||||
pub(crate) type SpanIndex = u32;
|
||||
pub type SpanIndex = u32;
|
||||
|
||||
// A range of start..end eras for a slashing span.
|
||||
#[derive(Encode, Decode)]
|
||||
@@ -83,7 +83,7 @@ impl SlashingSpan {
|
||||
}
|
||||
|
||||
/// An encoding of all of a nominator's slashing spans.
|
||||
#[derive(Encode, Decode)]
|
||||
#[derive(Encode, Decode, RuntimeDebug)]
|
||||
pub struct SlashingSpans {
|
||||
// the index of the current slashing span of the nominator. different for
|
||||
// every stash, resets when the account hits free balance 0.
|
||||
@@ -143,7 +143,7 @@ impl SlashingSpans {
|
||||
}
|
||||
|
||||
/// Yields the era index where the most recent non-zero slash occurred.
|
||||
pub(crate) fn last_nonzero_slash(&self) -> EraIndex {
|
||||
pub fn last_nonzero_slash(&self) -> EraIndex {
|
||||
self.last_nonzero_slash
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
// 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/>.
|
||||
|
||||
//! Testing utils for staking. Needs the `testing-utils` feature to be enabled.
|
||||
//!
|
||||
//! Note that these helpers should NOT be used with the actual crate tests, but are rather designed
|
||||
//! for when the module is being externally tested (i.e. fuzzing, benchmarking, e2e tests). Enabling
|
||||
//! this feature in the current crate's Cargo.toml will leak the all of this into a normal release
|
||||
//! build. Just don't do it.
|
||||
|
||||
use crate::*;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::assert_ok;
|
||||
use frame_system::RawOrigin;
|
||||
use pallet_indices::address::Address;
|
||||
use rand::Rng;
|
||||
use sp_core::hashing::blake2_256;
|
||||
use sp_phragmen::{
|
||||
build_support_map, evaluate_support, reduce, Assignment, PhragmenScore, StakedAssignment,
|
||||
};
|
||||
|
||||
const CTRL_PREFIX: u32 = 1000;
|
||||
const NOMINATOR_PREFIX: u32 = 1_000_000;
|
||||
|
||||
/// A dummy suer.
|
||||
pub const USER: u32 = 999_999_999;
|
||||
|
||||
/// Address type of the `T`
|
||||
pub type AddressOf<T> = Address<<T as frame_system::Trait>::AccountId, u32>;
|
||||
|
||||
/// Random number in the range `[a, b]`.
|
||||
pub fn random(a: u32, b: u32) -> u32 {
|
||||
rand::thread_rng().gen_range(a, b)
|
||||
}
|
||||
|
||||
/// Set the desired validator count, with related storage items.
|
||||
pub fn set_validator_count<T: Trait>(to_elect: u32) {
|
||||
ValidatorCount::put(to_elect);
|
||||
MinimumValidatorCount::put(to_elect / 2);
|
||||
<EraElectionStatus<T>>::put(ElectionStatus::Open(T::BlockNumber::from(1u32)));
|
||||
}
|
||||
|
||||
/// Build an account with the given index.
|
||||
pub fn account<T: Trait>(index: u32) -> T::AccountId {
|
||||
let entropy = (b"benchmark/staking", index).using_encoded(blake2_256);
|
||||
T::AccountId::decode(&mut &entropy[..]).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Build an address given Index
|
||||
pub fn address<T: Trait>(index: u32) -> AddressOf<T> {
|
||||
pallet_indices::address::Address::Id(account::<T>(index))
|
||||
}
|
||||
|
||||
/// Generate signed origin from `who`.
|
||||
pub fn signed<T: Trait>(who: T::AccountId) -> T::Origin {
|
||||
RawOrigin::Signed(who).into()
|
||||
}
|
||||
|
||||
/// Generate signed origin from `index`.
|
||||
pub fn signed_account<T: Trait>(index: u32) -> T::Origin {
|
||||
signed::<T>(account::<T>(index))
|
||||
}
|
||||
|
||||
/// Bond a validator.
|
||||
pub fn bond_validator<T: Trait>(stash: T::AccountId, ctrl: u32, val: BalanceOf<T>)
|
||||
where
|
||||
T::Lookup: StaticLookup<Source = AddressOf<T>>,
|
||||
{
|
||||
let _ = T::Currency::make_free_balance_be(&stash, val);
|
||||
assert_ok!(<Module<T>>::bond(
|
||||
signed::<T>(stash),
|
||||
address::<T>(ctrl),
|
||||
val,
|
||||
RewardDestination::Controller
|
||||
));
|
||||
assert_ok!(<Module<T>>::validate(
|
||||
signed_account::<T>(ctrl),
|
||||
ValidatorPrefs::default()
|
||||
));
|
||||
}
|
||||
|
||||
pub fn bond_nominator<T: Trait>(
|
||||
stash: T::AccountId,
|
||||
ctrl: u32,
|
||||
val: BalanceOf<T>,
|
||||
target: Vec<AddressOf<T>>,
|
||||
) where
|
||||
T::Lookup: StaticLookup<Source = AddressOf<T>>,
|
||||
{
|
||||
let _ = T::Currency::make_free_balance_be(&stash, val);
|
||||
assert_ok!(<Module<T>>::bond(
|
||||
signed::<T>(stash),
|
||||
address::<T>(ctrl),
|
||||
val,
|
||||
RewardDestination::Controller
|
||||
));
|
||||
assert_ok!(<Module<T>>::nominate(signed_account::<T>(ctrl), target));
|
||||
}
|
||||
|
||||
/// Bond `nun_validators` validators and `num_nominator` nominators with `edge_per_voter` random
|
||||
/// votes per nominator.
|
||||
pub fn setup_chain_stakers<T: Trait>(num_validators: u32, num_voters: u32, edge_per_voter: u32)
|
||||
where
|
||||
T::Lookup: StaticLookup<Source = AddressOf<T>>,
|
||||
{
|
||||
(0..num_validators).for_each(|i| {
|
||||
bond_validator::<T>(
|
||||
account::<T>(i),
|
||||
i + CTRL_PREFIX,
|
||||
<BalanceOf<T>>::from(random(1, 1000)) * T::Currency::minimum_balance(),
|
||||
);
|
||||
});
|
||||
|
||||
(0..num_voters).for_each(|i| {
|
||||
let mut targets: Vec<AddressOf<T>> = Vec::with_capacity(edge_per_voter as usize);
|
||||
let mut all_targets = (0..num_validators)
|
||||
.map(|t| address::<T>(t))
|
||||
.collect::<Vec<_>>();
|
||||
assert!(num_validators >= edge_per_voter);
|
||||
(0..edge_per_voter).for_each(|_| {
|
||||
let target = all_targets.remove(random(0, all_targets.len() as u32 - 1) as usize);
|
||||
targets.push(target);
|
||||
});
|
||||
bond_nominator::<T>(
|
||||
account::<T>(i + NOMINATOR_PREFIX),
|
||||
i + NOMINATOR_PREFIX + CTRL_PREFIX,
|
||||
<BalanceOf<T>>::from(random(1, 1000)) * T::Currency::minimum_balance(),
|
||||
targets,
|
||||
);
|
||||
});
|
||||
|
||||
<Module<T>>::create_stakers_snapshot();
|
||||
}
|
||||
|
||||
/// Build a _really bad_ but acceptable solution for election. This should always yield a solution
|
||||
/// which has a less score than the seq-phragmen.
|
||||
pub fn get_weak_solution<T: Trait>(
|
||||
do_reduce: bool,
|
||||
) -> (Vec<ValidatorIndex>, CompactAssignments, PhragmenScore) {
|
||||
let mut backing_stake_of: BTreeMap<T::AccountId, BalanceOf<T>> = BTreeMap::new();
|
||||
|
||||
// self stake
|
||||
<Validators<T>>::enumerate().for_each(|(who, _p)| {
|
||||
*backing_stake_of.entry(who.clone()).or_insert(Zero::zero()) +=
|
||||
<Module<T>>::slashable_balance_of(&who)
|
||||
});
|
||||
|
||||
// add nominator stuff
|
||||
<Nominators<T>>::enumerate().for_each(|(who, nomination)| {
|
||||
nomination.targets.into_iter().for_each(|v| {
|
||||
*backing_stake_of.entry(v).or_insert(Zero::zero()) +=
|
||||
<Module<T>>::slashable_balance_of(&who)
|
||||
})
|
||||
});
|
||||
|
||||
// elect winners
|
||||
let mut sorted: Vec<T::AccountId> = backing_stake_of.keys().cloned().collect();
|
||||
sorted.sort_by_key(|x| backing_stake_of.get(x).unwrap());
|
||||
let winners: Vec<T::AccountId> = sorted
|
||||
.iter()
|
||||
.cloned()
|
||||
.take(<Module<T>>::validator_count() as usize)
|
||||
.collect();
|
||||
|
||||
let mut staked_assignments: Vec<StakedAssignment<T::AccountId>> = Vec::new();
|
||||
<Nominators<T>>::enumerate().for_each(|(who, nomination)| {
|
||||
let mut dist: Vec<(T::AccountId, ExtendedBalance)> = Vec::new();
|
||||
nomination.targets.into_iter().for_each(|v| {
|
||||
if winners.iter().find(|&w| *w == v).is_some() {
|
||||
dist.push((v, ExtendedBalance::zero()));
|
||||
}
|
||||
});
|
||||
|
||||
if dist.len() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// assign real stakes. just split the stake.
|
||||
let stake = <T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(
|
||||
<Module<T>>::slashable_balance_of(&who),
|
||||
) as ExtendedBalance;
|
||||
|
||||
let mut sum: ExtendedBalance = Zero::zero();
|
||||
let dist_len = dist.len() as ExtendedBalance;
|
||||
|
||||
// assign main portion
|
||||
// only take the first half into account. This should highly imbalance stuff, which is good.
|
||||
dist.iter_mut()
|
||||
.take(if dist_len > 1 {
|
||||
(dist_len as usize) / 2
|
||||
} else {
|
||||
1
|
||||
})
|
||||
.for_each(|(_, w)| {
|
||||
let partial = stake / dist_len;
|
||||
*w = partial;
|
||||
sum += partial;
|
||||
});
|
||||
|
||||
// assign the leftover to last.
|
||||
let leftover = stake - sum;
|
||||
let last = dist.last_mut().unwrap();
|
||||
last.1 += leftover;
|
||||
|
||||
staked_assignments.push(StakedAssignment {
|
||||
who,
|
||||
distribution: dist,
|
||||
});
|
||||
});
|
||||
|
||||
// add self support to winners.
|
||||
winners.iter().for_each(|w| {
|
||||
staked_assignments.push(StakedAssignment {
|
||||
who: w.clone(),
|
||||
distribution: vec![(
|
||||
w.clone(),
|
||||
<T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(
|
||||
<Module<T>>::slashable_balance_of(&w),
|
||||
) as ExtendedBalance,
|
||||
)],
|
||||
})
|
||||
});
|
||||
|
||||
if do_reduce {
|
||||
reduce(&mut staked_assignments);
|
||||
}
|
||||
|
||||
// helpers for building the compact
|
||||
let snapshot_validators = <Module<T>>::snapshot_validators().unwrap();
|
||||
let snapshot_nominators = <Module<T>>::snapshot_nominators().unwrap();
|
||||
|
||||
let nominator_index = |a: &T::AccountId| -> Option<NominatorIndex> {
|
||||
snapshot_nominators
|
||||
.iter()
|
||||
.position(|x| x == a)
|
||||
.and_then(|i| <usize as TryInto<NominatorIndex>>::try_into(i).ok())
|
||||
};
|
||||
let validator_index = |a: &T::AccountId| -> Option<ValidatorIndex> {
|
||||
snapshot_validators
|
||||
.iter()
|
||||
.position(|x| x == a)
|
||||
.and_then(|i| <usize as TryInto<ValidatorIndex>>::try_into(i).ok())
|
||||
};
|
||||
let stake_of = |who: &T::AccountId| -> ExtendedBalance {
|
||||
<T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(
|
||||
<Module<T>>::slashable_balance_of(who),
|
||||
) as ExtendedBalance
|
||||
};
|
||||
|
||||
// convert back to ratio assignment. This takes less space.
|
||||
let low_accuracy_assignment: Vec<Assignment<T::AccountId, OffchainAccuracy>> =
|
||||
staked_assignments
|
||||
.into_iter()
|
||||
.map(|sa| sa.into_assignment(true))
|
||||
.collect();
|
||||
|
||||
// re-calculate score based on what the chain will decode.
|
||||
let score = {
|
||||
let staked: Vec<StakedAssignment<T::AccountId>> = low_accuracy_assignment
|
||||
.iter()
|
||||
.map(|a| {
|
||||
let stake = stake_of(&a.who);
|
||||
a.clone().into_staked(stake, true)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let (support_map, _) =
|
||||
build_support_map::<T::AccountId>(winners.as_slice(), staked.as_slice());
|
||||
evaluate_support::<T::AccountId>(&support_map)
|
||||
};
|
||||
|
||||
// compact encode the assignment.
|
||||
let compact = CompactAssignments::from_assignment(
|
||||
low_accuracy_assignment,
|
||||
nominator_index,
|
||||
validator_index,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// winners to index.
|
||||
let winners = winners
|
||||
.into_iter()
|
||||
.map(|w| {
|
||||
snapshot_validators
|
||||
.iter()
|
||||
.position(|v| *v == w)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
})
|
||||
.collect::<Vec<ValidatorIndex>>();
|
||||
|
||||
(winners, compact, score)
|
||||
}
|
||||
|
||||
/// Create a solution for seq-phragmen. This uses the same internal function as used by the offchain
|
||||
/// worker code.
|
||||
pub fn get_seq_phragmen_solution<T: Trait>(
|
||||
do_reduce: bool,
|
||||
) -> (Vec<ValidatorIndex>, CompactAssignments, PhragmenScore) {
|
||||
let sp_phragmen::PhragmenResult {
|
||||
winners,
|
||||
assignments,
|
||||
} = <Module<T>>::do_phragmen::<OffchainAccuracy>().unwrap();
|
||||
|
||||
offchain_election::prepare_submission::<T>(assignments, winners, do_reduce).unwrap()
|
||||
}
|
||||
|
||||
/// Remove all validator, nominators, votes and exposures.
|
||||
pub fn clean<T: Trait>(era: EraIndex)
|
||||
where
|
||||
<T as frame_system::Trait>::AccountId: codec::EncodeLike<u32>,
|
||||
u32: codec::EncodeLike<T::AccountId>,
|
||||
{
|
||||
<Validators<T>>::enumerate().for_each(|(k, _)| {
|
||||
let ctrl = <Module<T>>::bonded(&k).unwrap();
|
||||
<Bonded<T>>::remove(&k);
|
||||
<Validators<T>>::remove(&k);
|
||||
<Ledger<T>>::remove(&ctrl);
|
||||
<ErasStakers<T>>::remove(k, era);
|
||||
});
|
||||
<Nominators<T>>::enumerate().for_each(|(k, _)| <Nominators<T>>::remove(k));
|
||||
<Ledger<T>>::remove_all();
|
||||
<Bonded<T>>::remove_all();
|
||||
<QueuedElected<T>>::kill();
|
||||
QueuedScore::kill();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ use sp_core::u32_trait::Value as U32;
|
||||
use sp_runtime::{
|
||||
RuntimeDebug,
|
||||
ConsensusEngineId, DispatchResult, DispatchError,
|
||||
traits::{MaybeSerializeDeserialize, AtLeast32Bit, Saturating, TrailingZeroInput},
|
||||
traits::{MaybeSerializeDeserialize, AtLeast32Bit, Saturating, TrailingZeroInput, Bounded},
|
||||
};
|
||||
use crate::dispatch::Parameter;
|
||||
use crate::storage::StorageMap;
|
||||
@@ -87,7 +87,7 @@ impl<
|
||||
Created: Happened<K>,
|
||||
Removed: Happened<K>,
|
||||
K: FullCodec,
|
||||
T: FullCodec
|
||||
T: FullCodec,
|
||||
> StoredMap<K, T> for StorageMapShim<S, Created, Removed, K, T> {
|
||||
fn get(k: &K) -> T { S::get(k) }
|
||||
fn is_explicit(k: &K) -> bool { S::contains_key(k) }
|
||||
@@ -138,6 +138,35 @@ impl<
|
||||
}
|
||||
}
|
||||
|
||||
/// Something that can estimate at which block the next session rotation will happen. This should
|
||||
/// be the same logical unit that dictates `ShouldEndSession` to the session module. No Assumptions
|
||||
/// are made about the scheduling of the sessions.
|
||||
pub trait EstimateNextSessionRotation<BlockNumber> {
|
||||
/// Return the block number at which the next session rotation is estimated to happen.
|
||||
///
|
||||
/// None should be returned if the estimation fails to come to an answer
|
||||
fn estimate_next_session_rotation(now: BlockNumber) -> Option<BlockNumber>;
|
||||
}
|
||||
|
||||
impl<BlockNumber: Bounded> EstimateNextSessionRotation<BlockNumber> for () {
|
||||
fn estimate_next_session_rotation(_: BlockNumber) -> Option<BlockNumber> {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Something that can estimate at which block the next `new_session` will be triggered. This must
|
||||
/// always be implemented by the session module.
|
||||
pub trait EstimateNextNewSession<BlockNumber> {
|
||||
/// Return the block number at which the next new session is estimated to happen.
|
||||
fn estimate_next_new_session(now: BlockNumber) -> Option<BlockNumber>;
|
||||
}
|
||||
|
||||
impl<BlockNumber: Bounded> EstimateNextNewSession<BlockNumber> for () {
|
||||
fn estimate_next_new_session(_: BlockNumber) -> Option<BlockNumber> {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Anything that can have a `::len()` method.
|
||||
pub trait Len {
|
||||
/// Return the length of data type.
|
||||
|
||||
@@ -95,7 +95,8 @@ impl<Public, Signature, TAnyAppPublic> Signer<Public, Signature> for TAnyAppPubl
|
||||
}
|
||||
|
||||
/// Retrieves a public key type for given `SignAndSubmitTransaction`.
|
||||
pub type PublicOf<T, Call, X> = <
|
||||
pub type PublicOf<T, Call, X> =
|
||||
<
|
||||
<X as SignAndSubmitTransaction<T, Call>>::CreateTransaction
|
||||
as
|
||||
CreateTransaction<T, <X as SignAndSubmitTransaction<T, Call>>::Extrinsic>
|
||||
@@ -109,7 +110,7 @@ pub type PublicOf<T, Call, X> = <
|
||||
/// you should use.
|
||||
pub trait SignAndSubmitTransaction<T: crate::Trait, Call> {
|
||||
/// Unchecked extrinsic type.
|
||||
type Extrinsic: ExtrinsicT<Call=Call> + codec::Encode;
|
||||
type Extrinsic: ExtrinsicT<Call=Call> + Encode;
|
||||
|
||||
/// A runtime-specific type to produce signed data for the extrinsic.
|
||||
type CreateTransaction: CreateTransaction<T, Self::Extrinsic>;
|
||||
@@ -156,7 +157,7 @@ pub trait SignAndSubmitTransaction<T: crate::Trait, Call> {
|
||||
/// you should use.
|
||||
pub trait SubmitUnsignedTransaction<T: crate::Trait, Call> {
|
||||
/// Unchecked extrinsic type.
|
||||
type Extrinsic: ExtrinsicT<Call=Call> + codec::Encode;
|
||||
type Extrinsic: ExtrinsicT<Call=Call> + Encode;
|
||||
|
||||
/// Submit given call to the transaction pool as unsigned transaction.
|
||||
///
|
||||
@@ -164,7 +165,8 @@ pub trait SubmitUnsignedTransaction<T: crate::Trait, Call> {
|
||||
/// and `Err` if transaction was rejected from the pool.
|
||||
fn submit_unsigned(call: impl Into<Call>) -> Result<(), ()> {
|
||||
let xt = Self::Extrinsic::new(call.into(), None).ok_or(())?;
|
||||
sp_io::offchain::submit_transaction(xt.encode())
|
||||
let encoded_xt = xt.encode();
|
||||
sp_io::offchain::submit_transaction(encoded_xt)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,7 +293,7 @@ impl<T, E, S, C, Call> SignAndSubmitTransaction<T, Call> for TransactionSubmitte
|
||||
T: crate::Trait,
|
||||
C: CreateTransaction<T, E>,
|
||||
S: Signer<<C as CreateTransaction<T, E>>::Public, <C as CreateTransaction<T, E>>::Signature>,
|
||||
E: ExtrinsicT<Call=Call> + codec::Encode,
|
||||
E: ExtrinsicT<Call=Call> + Encode,
|
||||
{
|
||||
type Extrinsic = E;
|
||||
type CreateTransaction = C;
|
||||
@@ -301,7 +303,7 @@ impl<T, E, S, C, Call> SignAndSubmitTransaction<T, Call> for TransactionSubmitte
|
||||
/// A blanket implementation to use the same submitter for unsigned transactions as well.
|
||||
impl<T, E, S, C, Call> SubmitUnsignedTransaction<T, Call> for TransactionSubmitter<S, C, E> where
|
||||
T: crate::Trait,
|
||||
E: ExtrinsicT<Call=Call> + codec::Encode,
|
||||
E: ExtrinsicT<Call=Call> + Encode,
|
||||
{
|
||||
type Extrinsic = E;
|
||||
}
|
||||
@@ -310,7 +312,7 @@ impl<T, E, S, C, Call> SubmitUnsignedTransaction<T, Call> for TransactionSubmitt
|
||||
impl<T, C, E, S, Call> SubmitSignedTransaction<T, Call> for TransactionSubmitter<S, C, E> where
|
||||
T: crate::Trait,
|
||||
C: CreateTransaction<T, E>,
|
||||
E: ExtrinsicT<Call=Call> + codec::Encode,
|
||||
E: ExtrinsicT<Call=Call> + Encode,
|
||||
S: Signer<<C as CreateTransaction<T, E>>::Public, <C as CreateTransaction<T, E>>::Signature>,
|
||||
// Make sure we can unwrap the app crypto key.
|
||||
S: RuntimeAppPublic + AppPublic + Into<<S as AppPublic>::Generic>,
|
||||
|
||||
@@ -106,11 +106,6 @@ impl<T: Trait> Module<T> {
|
||||
///
|
||||
/// All dispatchables must be annotated with weight and will have some fee info. This function
|
||||
/// always returns.
|
||||
// NOTE: we can actually make it understand `ChargeTransactionPayment`, but would be some hassle
|
||||
// for sure. We have to make it aware of the index of `ChargeTransactionPayment` in `Extra`.
|
||||
// Alternatively, we could actually execute the tx's per-dispatch and record the balance of the
|
||||
// sender before and after the pipeline.. but this is way too much hassle for a very very little
|
||||
// potential gain in the future.
|
||||
pub fn query_info<Extrinsic: GetDispatchInfo>(
|
||||
unchecked_extrinsic: Extrinsic,
|
||||
len: u32,
|
||||
@@ -119,6 +114,11 @@ impl<T: Trait> Module<T> {
|
||||
T: Send + Sync,
|
||||
BalanceOf<T>: Send + Sync,
|
||||
{
|
||||
// NOTE: we can actually make it understand `ChargeTransactionPayment`, but would be some
|
||||
// hassle for sure. We have to make it aware of the index of `ChargeTransactionPayment` in
|
||||
// `Extra`. Alternatively, we could actually execute the tx's per-dispatch and record the
|
||||
// balance of the sender before and after the pipeline.. but this is way too much hassle for
|
||||
// a very very little potential gain in the future.
|
||||
let dispatch_info = <Extrinsic as GetDispatchInfo>::get_dispatch_info(&unchecked_extrinsic);
|
||||
|
||||
let partial_fee =
|
||||
|
||||
+401
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}};
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
+1602
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),]
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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
@@ -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()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user