From c33ebfceb4cece7295e4b4930cfa635fc7ab0f0c Mon Sep 17 00:00:00 2001 From: zqhxuyuan Date: Fri, 18 Aug 2023 00:25:18 +0800 Subject: [PATCH] split lotter repo Signed-off-by: zqhxuyuan --- Cargo.lock | 188 ++- node/Cargo.toml | 3 +- pallets/pallet-lottery/Cargo.toml | 82 -- pallets/pallet-lottery/src/benchmarks.rs | 345 ----- pallets/pallet-lottery/src/lib.rs | 1243 ----------------- pallets/pallet-lottery/src/mock.rs | 567 -------- pallets/pallet-lottery/src/rpc.rs | 113 -- pallets/pallet-lottery/src/runtime.rs | 23 - .../src/staking/deposit_strategies.rs | 268 ---- pallets/pallet-lottery/src/staking/mod.rs | 354 ----- .../src/staking/withdraw_strategies.rs | 99 -- pallets/pallet-lottery/src/tests.rs | 744 ---------- pallets/pallet-lottery/src/weights.rs | 352 ----- pallets/randomness/Cargo.toml | 57 - pallets/randomness/README.md | 115 -- pallets/randomness/src/benchmarks.rs | 73 - pallets/randomness/src/lib.rs | 203 --- pallets/randomness/src/mock.rs | 148 -- pallets/randomness/src/tests.rs | 25 - pallets/randomness/src/types.rs | 53 - pallets/randomness/src/weights.rs | 81 -- runtime/calamari/Cargo.toml | 5 +- runtime/manta/Cargo.toml | 5 +- 23 files changed, 144 insertions(+), 5002 deletions(-) delete mode 100644 pallets/pallet-lottery/Cargo.toml delete mode 100644 pallets/pallet-lottery/src/benchmarks.rs delete mode 100644 pallets/pallet-lottery/src/lib.rs delete mode 100644 pallets/pallet-lottery/src/mock.rs delete mode 100644 pallets/pallet-lottery/src/rpc.rs delete mode 100644 pallets/pallet-lottery/src/runtime.rs delete mode 100644 pallets/pallet-lottery/src/staking/deposit_strategies.rs delete mode 100644 pallets/pallet-lottery/src/staking/mod.rs delete mode 100644 pallets/pallet-lottery/src/staking/withdraw_strategies.rs delete mode 100644 pallets/pallet-lottery/src/tests.rs delete mode 100644 pallets/pallet-lottery/src/weights.rs delete mode 100644 pallets/randomness/Cargo.toml delete mode 100644 pallets/randomness/README.md delete mode 100644 pallets/randomness/src/benchmarks.rs delete mode 100644 pallets/randomness/src/lib.rs delete mode 100644 pallets/randomness/src/mock.rs delete mode 100644 pallets/randomness/src/tests.rs delete mode 100644 pallets/randomness/src/types.rs delete mode 100644 pallets/randomness/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 533b3d9b0..f3eb76fc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1118,8 +1118,8 @@ dependencies = [ "hex-literal", "lazy_static", "log", - "manta-collator-selection", - "manta-primitives", + "manta-collator-selection 4.4.0", + "manta-primitives 4.4.0", "nimbus-primitives", "orml-traits", "orml-xtokens", @@ -1142,7 +1142,7 @@ dependencies = [ "pallet-membership", "pallet-multisig", "pallet-name-service", - "pallet-parachain-staking", + "pallet-parachain-staking 4.4.0", "pallet-preimage", "pallet-randomness", "pallet-ranked-collective", @@ -1164,11 +1164,11 @@ dependencies = [ "polkadot-primitives", "polkadot-runtime-parachains", "reqwest", - "runtime-common", + "runtime-common 4.4.0", "scale-info", "serde", "serde_json", - "session-key-primitives", + "session-key-primitives 4.4.0", "smallvec", "sp-api", "sp-application-crypto", @@ -1203,7 +1203,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "manta-primitives", + "manta-primitives 4.4.0", "pallet-balances", "pallet-timestamp", "parity-scale-codec", @@ -4185,8 +4185,8 @@ dependencies = [ "frame-support", "frame-system", "lazy_static", - "manta-collator-selection", - "manta-primitives", + "manta-collator-selection 4.4.0", + "manta-primitives 4.4.0", "manta-runtime", "nimbus-primitives", "orml-traits", @@ -4200,7 +4200,7 @@ dependencies = [ "pallet-manta-sbt", "pallet-manta-support", "pallet-membership", - "pallet-parachain-staking", + "pallet-parachain-staking 4.4.0", "pallet-scheduler", "pallet-session", "pallet-transaction-payment", @@ -4212,9 +4212,9 @@ dependencies = [ "polkadot-core-primitives", "polkadot-parachain", "polkadot-runtime-parachains", - "runtime-common", + "runtime-common 4.4.0", "scale-info", - "session-key-primitives", + "session-key-primitives 4.4.0", "sp-arithmetic", "sp-core", "sp-io", @@ -5323,7 +5323,7 @@ dependencies = [ "hex-literal", "jsonrpsee", "log", - "manta-primitives", + "manta-primitives 4.4.0", "manta-runtime", "nimbus-consensus", "nimbus-primitives", @@ -5333,7 +5333,7 @@ dependencies = [ "pallet-lottery", "pallet-manta-pay", "pallet-manta-sbt", - "pallet-parachain-staking", + "pallet-parachain-staking 4.4.0", "pallet-transaction-payment-rpc", "parity-scale-codec", "polkadot-cli", @@ -5361,7 +5361,7 @@ dependencies = [ "sc-transaction-pool", "sc-transaction-pool-api", "serde", - "session-key-primitives", + "session-key-primitives 4.4.0", "sp-api", "sp-application-crypto", "sp-arithmetic", @@ -5412,7 +5412,7 @@ dependencies = [ "frame-support", "frame-system", "log", - "manta-primitives", + "manta-primitives 4.4.0", "nimbus-primitives", "pallet-aura", "pallet-authorship", @@ -5433,6 +5433,27 @@ dependencies = [ "sp-tracing", ] +[[package]] +name = "manta-collator-selection" +version = "4.4.0" +source = "git+https://github.com/Manta-Network/Manta.git#0814f5d3c1490be7f6a04d0b41593c3da93e5800" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "nimbus-primitives", + "pallet-authorship", + "pallet-session", + "parity-scale-codec", + "scale-info", + "serde", + "sp-arithmetic", + "sp-runtime", + "sp-staking", + "sp-std", +] + [[package]] name = "manta-crypto" version = "0.5.15" @@ -5512,6 +5533,27 @@ dependencies = [ "xcm-executor", ] +[[package]] +name = "manta-primitives" +version = "4.4.0" +source = "git+https://github.com/Manta-Network/Manta.git#0814f5d3c1490be7f6a04d0b41593c3da93e5800" +dependencies = [ + "frame-support", + "frame-system", + "log", + "orml-traits", + "parity-scale-codec", + "scale-info", + "smallvec", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", + "xcm-builder", + "xcm-executor", +] + [[package]] name = "manta-runtime" version = "4.4.0" @@ -5534,8 +5576,8 @@ dependencies = [ "frame-try-runtime", "hex-literal", "log", - "manta-collator-selection", - "manta-primitives", + "manta-collator-selection 4.4.0", + "manta-primitives 4.4.0", "nimbus-primitives", "orml-traits", "orml-xtokens", @@ -5557,7 +5599,7 @@ dependencies = [ "pallet-membership", "pallet-multisig", "pallet-name-service", - "pallet-parachain-staking", + "pallet-parachain-staking 4.4.0", "pallet-preimage", "pallet-randomness", "pallet-scheduler", @@ -5577,10 +5619,10 @@ dependencies = [ "polkadot-parachain", "polkadot-primitives", "polkadot-runtime-parachains", - "runtime-common", + "runtime-common 4.4.0", "scale-info", "serde", - "session-key-primitives", + "session-key-primitives 4.4.0", "smallvec", "sp-api", "sp-application-crypto", @@ -6500,7 +6542,7 @@ dependencies = [ "frame-support", "frame-system", "log", - "manta-primitives", + "manta-primitives 4.4.0", "orml-traits", "pallet-assets", "pallet-balances", @@ -6862,7 +6904,7 @@ dependencies = [ "frame-system", "hex-literal", "log", - "manta-primitives", + "manta-primitives 4.4.0", "orml-traits", "pallet-asset-manager", "pallet-assets", @@ -6882,7 +6924,7 @@ name = "pallet-farming-rpc-api" version = "4.4.0" dependencies = [ "jsonrpsee", - "manta-primitives", + "manta-primitives 4.4.0", "pallet-farming-rpc-runtime-api", "parity-scale-codec", "serde", @@ -6897,7 +6939,7 @@ dependencies = [ name = "pallet-farming-rpc-runtime-api" version = "4.4.0" dependencies = [ - "manta-primitives", + "manta-primitives 4.4.0", "parity-scale-codec", "sp-api", "sp-std", @@ -6999,37 +7041,28 @@ dependencies = [ [[package]] name = "pallet-lottery" -version = "4.4.0" +version = "1.0.0" +source = "git+https://github.com/jumboshrimpslab/lottery-polkadot.git#903c0c30044dc78917c80cca8e1971965a390a75" dependencies = [ - "calamari-runtime", "frame-benchmarking", "frame-support", "frame-system", "function_name", "jsonrpsee", - "lazy_static", "log", - "manta-collator-selection", - "manta-primitives", - "pallet-balances", - "pallet-parachain-staking", - "pallet-preimage", - "pallet-randomness", - "pallet-scheduler", - "pallet-transaction-payment", + "manta-primitives 4.4.0 (git+https://github.com/Manta-Network/Manta.git)", + "pallet-parachain-staking 4.4.0 (git+https://github.com/Manta-Network/Manta.git)", "parity-scale-codec", "rand 0.8.5", - "runtime-common", + "runtime-common 4.4.0 (git+https://github.com/Manta-Network/Manta.git)", "scale-info", - "session-key-primitives", - "similar-asserts", + "session-key-primitives 4.4.0 (git+https://github.com/Manta-Network/Manta.git)", "sp-api", "sp-arithmetic", "sp-blockchain", "sp-core", "sp-io", "sp-runtime", - "sp-staking", "sp-std", ] @@ -7048,7 +7081,7 @@ dependencies = [ "manta-accounting", "manta-crypto", "manta-pay", - "manta-primitives", + "manta-primitives 4.4.0", "manta-util", "pallet-asset-manager", "pallet-assets", @@ -7084,7 +7117,7 @@ dependencies = [ "manta-accounting", "manta-crypto", "manta-pay", - "manta-primitives", + "manta-primitives 4.4.0", "manta-util", "pallet-asset-manager", "pallet-assets", @@ -7120,7 +7153,7 @@ dependencies = [ "manta-accounting", "manta-crypto", "manta-pay", - "manta-primitives", + "manta-primitives 4.4.0", "manta-util", "parity-scale-codec", "rand_chacha 0.3.1", @@ -7189,7 +7222,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "manta-primitives", + "manta-primitives 4.4.0", "pallet-balances", "pallet-manta-support", "parity-scale-codec", @@ -7312,8 +7345,8 @@ dependencies = [ "frame-support", "frame-system", "log", - "manta-collator-selection", - "manta-primitives", + "manta-collator-selection 4.4.0", + "manta-primitives 4.4.0", "pallet-balances", "pallet-session", "parity-scale-codec", @@ -7329,6 +7362,28 @@ dependencies = [ "substrate-fixed 0.5.9 (git+https://github.com/Manta-Network/substrate-fixed.git?tag=v0.5.9)", ] +[[package]] +name = "pallet-parachain-staking" +version = "4.4.0" +source = "git+https://github.com/Manta-Network/Manta.git#0814f5d3c1490be7f6a04d0b41593c3da93e5800" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "manta-collator-selection 4.4.0 (git+https://github.com/Manta-Network/Manta.git)", + "manta-primitives 4.4.0 (git+https://github.com/Manta-Network/Manta.git)", + "pallet-session", + "parity-scale-codec", + "scale-info", + "serde", + "sp-arithmetic", + "sp-runtime", + "sp-staking", + "sp-std", + "substrate-fixed 0.5.9 (git+https://github.com/Manta-Network/substrate-fixed.git?tag=v0.5.9)", +] + [[package]] name = "pallet-preimage" version = "4.0.0-dev" @@ -7364,20 +7419,19 @@ dependencies = [ [[package]] name = "pallet-randomness" version = "4.4.0" +source = "git+https://github.com/jumboshrimpslab/lottery-polkadot.git#903c0c30044dc78917c80cca8e1971965a390a75" dependencies = [ - "derive_more", "frame-benchmarking", "frame-support", "frame-system", "hex", "log", - "manta-primitives", + "manta-primitives 4.4.0 (git+https://github.com/Manta-Network/Manta.git)", "nimbus-primitives", - "pallet-balances", "parity-scale-codec", "scale-info", "serde", - "session-key-primitives", + "session-key-primitives 4.4.0 (git+https://github.com/Manta-Network/Manta.git)", "sp-core", "sp-io", "sp-runtime", @@ -7683,7 +7737,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "manta-primitives", + "manta-primitives 4.4.0", "pallet-balances", "parity-scale-codec", "scale-info", @@ -10061,7 +10115,7 @@ dependencies = [ "frame-support", "frame-system", "lazy_static", - "manta-primitives", + "manta-primitives 4.4.0", "orml-traits", "orml-xtokens", "pallet-asset-manager", @@ -10088,6 +10142,18 @@ dependencies = [ "xcm-simulator", ] +[[package]] +name = "runtime-common" +version = "4.4.0" +source = "git+https://github.com/Manta-Network/Manta.git#0814f5d3c1490be7f6a04d0b41593c3da93e5800" +dependencies = [ + "frame-support", + "manta-primitives 4.4.0 (git+https://github.com/Manta-Network/Manta.git)", + "pallet-transaction-payment", + "sp-runtime", + "sp-std", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -11659,7 +11725,25 @@ name = "session-key-primitives" version = "4.4.0" dependencies = [ "async-trait", - "manta-primitives", + "manta-primitives 4.4.0", + "nimbus-primitives", + "parity-scale-codec", + "scale-info", + "serde", + "sp-application-crypto", + "sp-consensus-aura", + "sp-core", + "sp-inherents", + "sp-runtime", +] + +[[package]] +name = "session-key-primitives" +version = "4.4.0" +source = "git+https://github.com/Manta-Network/Manta.git#0814f5d3c1490be7f6a04d0b41593c3da93e5800" +dependencies = [ + "async-trait", + "manta-primitives 4.4.0 (git+https://github.com/Manta-Network/Manta.git)", "nimbus-primitives", "parity-scale-codec", "scale-info", diff --git a/node/Cargo.toml b/node/Cargo.toml index 52ee47a1c..d293340a0 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -112,12 +112,13 @@ xcm = { git = "https://github.com/paritytech/polkadot.git", branch = "release-v0 calamari-runtime = { path = '../runtime/calamari' } manta-primitives = { path = '../primitives/manta' } manta-runtime = { path = '../runtime/manta' } -pallet-lottery = { path = '../pallets/pallet-lottery', features = ["rpc"] } pallet-manta-pay = { path = '../pallets/manta-pay', features = ["rpc", "runtime"] } pallet-manta-sbt = { path = '../pallets/manta-sbt', features = ["rpc", "runtime"] } pallet-parachain-staking = { path = '../pallets/parachain-staking' } session-key-primitives = { path = '../primitives/session-keys' } +pallet-lottery = { git = 'https://github.com/jumboshrimpslab/lottery-polkadot.git', features = ["rpc"] } + [build-dependencies] substrate-build-script-utils = { git = 'https://github.com/paritytech/substrate.git', branch = "polkadot-v0.9.37" } diff --git a/pallets/pallet-lottery/Cargo.toml b/pallets/pallet-lottery/Cargo.toml deleted file mode 100644 index 3e04c15e2..000000000 --- a/pallets/pallet-lottery/Cargo.toml +++ /dev/null @@ -1,82 +0,0 @@ -[package] -authors = ["Manta Network"] -description = 'Pallet implementing an APY-maximizing no-loss lottery' -edition = "2021" -homepage = 'https://manta.network' -license = 'GPL-3.0' -name = 'pallet-lottery' -repository = 'https://github.com/Manta-Network/Manta/' -version = '4.4.0' - -[dependencies] -codec = { version = '3.4.0', default-features = false, features = ['derive'], package = 'parity-scale-codec' } -function_name = "0.3" -jsonrpsee = { version = "0.16.2", features = ["server", "macros"], optional = true } -log = { version = "0.4.0", default-features = false } -scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } - -# Substrate dependencies -frame-support = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.37" } -frame-system = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.37" } -sp-api = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.37" } -sp-arithmetic = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.37" } -sp-blockchain = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.37", optional = true } -sp-core = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.37" } -sp-io = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.37" } -sp-runtime = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.37" } -sp-std = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.37" } - -# Self dependencies -manta-primitives = { path = "../../primitives/manta", default-features = false } -pallet-parachain-staking = { path = '../parachain-staking', default-features = false } -runtime-common = { path = "../../runtime/common", default-features = false } -session-key-primitives = { path = '../../primitives/session-keys', default-features = false } - -# Benchmarking dependencies -frame-benchmarking = { git = 'https://github.com/paritytech/substrate.git', branch = "polkadot-v0.9.37", default-features = false, optional = true } -rand = { version = "0.8.5", default-features = false, optional = true } - -[dev-dependencies] -calamari-runtime = { path = "../../runtime/calamari", default-features = false } -lazy_static = "1.4.0" -manta-collator-selection = { path = "../collator-selection", default-features = false } -pallet-balances = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.37" } -pallet-preimage = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.37" } -pallet-randomness = { path = '../randomness', default-features = false } -pallet-scheduler = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.37" } -pallet-transaction-payment = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.37" } -rand = "0.8" -similar-asserts = "1.1.0" -sp-staking = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.37" } - -[features] -default = ["std"] -# RPC Interface -rpc = [ - "jsonrpsee", - "sp-blockchain", -] -runtime-benchmarks = [ - 'frame-benchmarking/runtime-benchmarks', - 'frame-support/runtime-benchmarks', - 'pallet-parachain-staking/runtime-benchmarks', - 'rand/std_rng', -] -std = [ - "manta-primitives/std", - "pallet-parachain-staking/std", - "pallet-randomness/std", - "calamari-runtime/std", - "session-key-primitives/std", - "sp-core/std", - "sp-std/std", - "sp-io/std", - "sp-runtime/std", - 'frame-benchmarking/std', - "frame-support/std", - "frame-system/std", -] -try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", -] diff --git a/pallets/pallet-lottery/src/benchmarks.rs b/pallets/pallet-lottery/src/benchmarks.rs deleted file mode 100644 index 577e17cbd..000000000 --- a/pallets/pallet-lottery/src/benchmarks.rs +++ /dev/null @@ -1,345 +0,0 @@ -// Copyright 2020-2023 Manta Network. -// This file is part of Manta. -// -// Manta 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. -// -// Manta 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 Manta. If not, see . - -#![cfg(feature = "runtime-benchmarks")] - -//! Benchmarking -use crate::{Call, Config, Pallet, Request}; -use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, Zero}; -use frame_support::{ - assert_ok, - traits::{Currency, EstimateCallFee, Get, OnFinalize, OnInitialize}, -}; -use frame_system::RawOrigin; -use pallet_parachain_staking::{ - benchmarks::{create_funded_collator, create_funded_user, parachain_staking_on_finalize}, - BalanceOf, Pallet as Staking, -}; -use sp_runtime::Saturating; - -const MAX_COLLATOR_COUNT: u32 = 63; -const USER_SEED: u32 = 696969; - -/// Run to end block and author -fn roll_rounds_and_author(rounds: u32) { - let total_rounds = rounds + 1u32; - let round_length: T::BlockNumber = Staking::::round().length.into(); - let mut now = >::block_number() + 1u32.into(); - let end = Staking::::round().first + (round_length * total_rounds.into()); - while now < end { - let use_first_collator_to_author = - Staking::::selected_candidates().first().unwrap().clone(); - parachain_staking_on_finalize::(use_first_collator_to_author.clone()); - >::on_finalize(>::block_number()); - >::set_block_number( - >::block_number() + 1u32.into(), - ); - >::on_initialize(>::block_number()); - Staking::::on_initialize(>::block_number()); - now += 1u32.into(); - } -} - -fn fund_lottery_account(bal: BalanceOf) { - ::Currency::deposit_creating( - &Pallet::::account_id(), - bal, - ); -} - -fn register_collators(number: u32) { - let original_collator_count = Staking::::candidate_pool().len() as u32; - let mut collator_seed: u32 = 444; - for _ in 0..number { - assert_ok!(create_funded_collator::( - "collator", - collator_seed, - Zero::zero(), - true, - original_collator_count + number - )); - collator_seed += 1; - } -} - -fn deposit_prior_users(number: u32, amount: BalanceOf) { - for user in 0..number { - >::set_block_number(user.into()); - let (depositor, _) = create_funded_user::("depositor", USER_SEED - 1 - user, amount); - assert_ok!(Pallet::::deposit( - RawOrigin::Signed(depositor).into(), - amount - )); - } -} - -benchmarks! { - // USER DISPATCHABLES - - deposit { - let x in 0u32..1_000u32; // other users that have already deposited to the lottery previously - let y in 0..MAX_COLLATOR_COUNT; // registered collators - - fund_lottery_account::(Pallet::::gas_reserve()); - let min_delegator_bond = <::MinDelegatorStk as Get>>::get(); - let original_collator_count = Staking::::candidate_pool().len() as u32; - let deposit_amount: BalanceOf = min_delegator_bond * 10_000u32.into(); - // fill collators - register_collators::(y); - assert_eq!(Staking::::candidate_pool().len() as u32, original_collator_count + y); - assert_eq!(Pallet::::total_pot(), Zero::zero()); - - let original_staked_amount = Staking::::total(); - deposit_prior_users::(x,deposit_amount); - - let (caller, _) = create_funded_user::("caller", USER_SEED, deposit_amount); - }: _(RawOrigin::Signed(caller.clone()), deposit_amount) - verify { - assert_eq!(Pallet::::active_balance_per_user(caller), deposit_amount); - assert_eq!(Pallet::::total_pot(), deposit_amount.saturating_mul((x+1).into())); - assert_eq!(Staking::::total(), original_staked_amount + deposit_amount.saturating_mul((x+1).into())); - } - - request_withdraw{ - let x in 0..1_000; // other users that have already deposited to the lottery previously - let y in 0..MAX_COLLATOR_COUNT; // registered collators - - fund_lottery_account::(Pallet::::gas_reserve()); - let min_delegator_bond = <::MinDelegatorStk as Get>>::get(); - let original_collator_count = Staking::::candidate_pool().len() as u32; - let deposit_amount: BalanceOf = min_delegator_bond * 10_000u32.into(); - // fill collators - register_collators::(y); - assert_eq!(Staking::::candidate_pool().len() as u32, original_collator_count + y); - - let original_staked_amount = Staking::::total(); - deposit_prior_users::(x,deposit_amount); - - let (caller, _) = create_funded_user::("caller", USER_SEED, deposit_amount); - assert_ok!(Pallet::::deposit(RawOrigin::Signed(caller.clone()).into(), deposit_amount)); - assert_eq!(Pallet::::active_balance_per_user(caller.clone()), deposit_amount); - }: _(RawOrigin::Signed(caller.clone()), deposit_amount) - verify { - assert!(Pallet::::active_balance_per_user(caller.clone()).is_zero()); - let now = >::block_number(); - let should_be_request = Request { - user: caller.clone(), - block: now, - balance: deposit_amount, - }; - let mut request_queue = Pallet::::withdrawal_request_queue(); - assert_eq!(request_queue.len(),1usize); - assert_eq!(request_queue.pop().unwrap(), should_be_request); - } - - claim_my_winnings { - let y in 0..MAX_COLLATOR_COUNT; // registered collators - - // NOTE: We fund 2x gas reserve to have 1x gas reserve to pay out as winnings - fund_lottery_account::(Pallet::::gas_reserve().saturating_add(Pallet::::gas_reserve())); - - let min_delegator_bond = <::MinDelegatorStk as Get>>::get(); - let original_collator_count = Staking::::candidate_pool().len() as u32; - let deposit_amount: BalanceOf = min_delegator_bond * 10_000u32.into(); - // fill collators - register_collators::(y); - assert_eq!(Staking::::candidate_pool().len() as u32, original_collator_count + y); - - let (caller, _) = create_funded_user::("caller", USER_SEED, deposit_amount); - assert_ok!(Pallet::::deposit(RawOrigin::Signed(caller.clone()).into(), deposit_amount)); - assert_eq!(Pallet::::active_balance_per_user(caller.clone()), deposit_amount); - roll_rounds_and_author::(2); - assert_ok!(Pallet::::draw_lottery(RawOrigin::Root.into())); - // should have won now - let unclaimed_winnings = Pallet::::total_unclaimed_winnings(); - let account_balance_before = ::Currency::free_balance(&caller); - let fee_estimate = T::EstimateCallFee::estimate_call_fee(&Call::::claim_my_winnings { }, None::.into()); - assert!(!unclaimed_winnings.is_zero()); - assert_eq!(unclaimed_winnings,Pallet::::unclaimed_winnings_by_account(caller.clone()).unwrap()); - }: _(RawOrigin::Signed(caller.clone())) - verify { - assert!(Pallet::::total_unclaimed_winnings().is_zero()); - let account_balance_after = ::Currency::free_balance(&caller); - assert!(Pallet::::unclaimed_winnings_by_account(caller.clone()).is_none()); - assert!(account_balance_after >= account_balance_before + unclaimed_winnings - fee_estimate); - assert!(account_balance_after <= account_balance_before + unclaimed_winnings); - } - - // ROOT DISPATCHABLES - start_lottery { - fund_lottery_account::(Pallet::::gas_reserve()); - }: _(RawOrigin::Root) - verify { - assert!(Pallet::::next_drawing_at().is_some()); - } - - stop_lottery { - fund_lottery_account::(Pallet::::gas_reserve()); - assert_ok!(Pallet::::start_lottery(RawOrigin::Root.into())); - }: _(RawOrigin::Root) - verify { - assert!(Pallet::::next_drawing_at().is_none()); - } - - draw_lottery { - let x in 0..1_000; // other users that have already deposited to the lottery previously - let y in 0..MAX_COLLATOR_COUNT; // registered collators - - // NOTE: We fund 2x gas reserve to have 1x gas reserve to pay out as winnings - fund_lottery_account::(Pallet::::gas_reserve().saturating_add(Pallet::::gas_reserve())); - - let min_delegator_bond = <::MinDelegatorStk as Get>>::get(); - let original_collator_count = Staking::::candidate_pool().len() as u32; - let deposit_amount: BalanceOf = min_delegator_bond * 10_000u32.into(); - // fill collators - register_collators::(y); - assert_eq!(Staking::::candidate_pool().len() as u32, original_collator_count + y); - - deposit_prior_users::(x,deposit_amount); - - let (caller, _) = create_funded_user::("caller", USER_SEED, deposit_amount); - assert_ok!(Pallet::::deposit(RawOrigin::Signed(caller.clone()).into(), deposit_amount)); - assert_eq!(Pallet::::active_balance_per_user(&caller), deposit_amount); - // roll_rounds_and_author::(2); - }: _(RawOrigin::Root) - verify { - // someone should have won now - let unclaimed_winnings = Pallet::::total_unclaimed_winnings(); - assert!(!unclaimed_winnings.is_zero()); - } - - process_matured_withdrawals { - }: _(RawOrigin::Root) - verify { - } - set_min_deposit { - assert_ok!(Pallet::::set_min_withdraw(RawOrigin::Root.into(),u32::MAX.into())); - }: _(RawOrigin::Root,u32::MAX.into()) - verify { - } - set_min_withdraw { - }: _(RawOrigin::Root,u32::MAX.into()) - verify { - } - set_gas_reserve { - }: _(RawOrigin::Root,u32::MAX.into()) - verify { - } - // rebalance_stake { - // }: _() - // verify { - // } - - // liquidate_lottery { - // }: _(RawOrigin::Root) - // verify { - // } -} - -#[cfg(test)] -mod tests { - use crate::{benchmarks::*, mock::Test}; - use frame_support::assert_ok; - use sp_io::TestExternalities; - - pub fn new_test_ext() -> TestExternalities { - crate::mock::ExtBuilder::default() - .with_balances(vec![ - (1, 10_000_000_000_000_000_000u128), - (2, 10_000_000_000_000_000_000u128), - (3, 10_000_000_000_000_000_000u128), - (4, 10_000_000_000_000_000_000u128), - (5, 10_000_000_000_000_000_000u128), - ]) - .with_candidates(vec![ - (1, 5_000_000_000_000_000_000u128), - (2, 5_000_000_000_000_000_000u128), - (3, 5_000_000_000_000_000_000u128), - (4, 5_000_000_000_000_000_000u128), - (5, 5_000_000_000_000_000_000u128), - ]) - .with_funded_lottery_account(10_000_000_000_000_000u128) - .with_inflation(Default::default()) - // NOTE: using default (=0) inflation means the lottery will not generate income on round change - .build() - } - #[test] - fn parachain_staking_is_set_up_correctly() { - new_test_ext().execute_with(|| { - assert_eq!(Staking::::selected_candidates().len(), 5); - }); - } - #[test] - fn bench_deposit() { - new_test_ext().execute_with(|| { - assert_ok!(Pallet::::test_benchmark_deposit()); - }); - } - #[test] - fn bench_request_withdraw() { - new_test_ext().execute_with(|| { - assert_ok!(Pallet::::test_benchmark_request_withdraw()); - }); - } - #[test] - fn bench_claim_my_winnings() { - new_test_ext().execute_with(|| { - assert_ok!(Pallet::::test_benchmark_claim_my_winnings()); - }); - } - // #[test] - // fn bench_rebalance_stake() { - // new_test_ext().execute_with(|| { - // assert_ok!(Pallet::::test_benchmark_rebalance_stake()); - // }); - // } - #[test] - fn bench_start_lottery() { - new_test_ext().execute_with(|| { - assert_ok!(Pallet::::test_benchmark_start_lottery()); - }); - } - #[test] - fn bench_stop_lottery() { - new_test_ext().execute_with(|| { - assert_ok!(Pallet::::test_benchmark_stop_lottery()); - }); - } - #[test] - fn bench_draw_lottery() { - new_test_ext().execute_with(|| { - assert_ok!(Pallet::::test_benchmark_draw_lottery()); - }); - } - // #[test] - // fn bench_process_matured_withdrawals() { - // new_test_ext().execute_with(|| { - // assert_ok!(Pallet::::test_benchmark_process_matured_withdrawals()); - // }); - // } - // #[test] - // fn bench_liquidate_lottery() { - // new_test_ext().execute_with(|| { - // assert_ok!(Pallet::::test_benchmark_liquidate_lottery()); - // }); - // } -} - -impl_benchmark_test_suite!( - Pallet, - crate::benchmarks::tests::new_test_ext(), - crate::mock::Test -); diff --git a/pallets/pallet-lottery/src/lib.rs b/pallets/pallet-lottery/src/lib.rs deleted file mode 100644 index cd4cec5d1..000000000 --- a/pallets/pallet-lottery/src/lib.rs +++ /dev/null @@ -1,1243 +0,0 @@ -// Copyright 2020-2023 Manta Network. -// This file is part of Manta. -// -// Manta 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. -// -// Manta 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 Manta. If not, see . - -//! # No-Loss-Lottery Module -//! -//! ## Overview -//! -//! This pallet implements a no-loss-lottery by taking user deposits, generating excess funds by staking funds with [`pallet_parachain_staking`] -//! and periodically selects a winner from participating users weighted by their deposit amount to receive a claim to the -//! accrued excess funds. -//! Funds withdrawn from the the lottery are subject to a timelock determined by parachain-staking before they can be claimed. -//! -//! ### Lottery Rules -//! 1. A drawing is scheduled to happen every [`Config::DrawingInterval`] blocks. -//! 2. A designated manager can start & stop the drawings as well as rebalance the stake to improve the yield generated through staking -//! 3. In order to prevent gaming of the lottery drawing mechanism, no modifications to this pallet are allowed [`Config::DrawingFreezeout`] blocks before a drawing -//! This is needed e.g. using BABE Randomness, where the randomness will be known a day before the scheduled drawing -//! 4. Winnings must be claimed manually by the winner but there is no time limit for claiming winnings -//! 5. Deposits are instantly staked by the pallet -//! 6. Withdrawals must wait for a timelock imposed by [`pallet_parachain_staking`] and are paid out automatically (via scheduler) in the first lottery drawing after it expires -//! 7. The [`Config::ManageOrigin`] must at the same time be allowed to use [`frame_support::traits::schedule::Named`] e.g. `ScheduleOrigin` in `pallet_scheduler` -//! -//! ## Dependencies -//! 1. To enable fair winner selection, a fair and low-influience randomness provider implementing [`frame_support::traits::Randomness`], e.g. pallet_randomness -//! 2. To schedule automatic drawings, a scheduling pallet implementing [`frame_support::traits::schedule::Named`], e.g. pallet_scheduler -//! 3. To generate lottery revenue, [`pallet_parachain_staking`] -//! -//! ## Interface -//! -//! This pallet contains extrinsics callable by any user and a second set of extrinsic callable only by a *Lottery Manager* Origin -//! configurable as [`Config::ManageOrigin`] in the [`Config`] struct of this pallet. -//! -//! ### User Dispatchable Functions -//! * [`Call::deposit`]: Allows any user to deposit tokens into the lottery -//! * [`Call::request_withdraw`]: Allows any user to request return of their deposited tokens to own wallet -//! * [`Call::claim_my_winnings`]: Allows any user to transfer any accrued winnings into their wallet -//! -//! ### Manager Dispatchable Functions -//! * [`Call::start_lottery`]: Schedules periodic lottery drawings to occur each [`Config::DrawingInterval`] -//! * [`Call::stop_lottery`]: Cancels the current drawing and stops scheduling new drawings -//! * [`Call::draw_lottery`]: Immediately executes a lottery drawing ( can be called manually even if lottery is stopped ) -//! * [`Call::process_matured_withdrawals`]: Immediately transfer funds of all matured withdrawals to their respective owner's wallets -//! * [`Call::liquidate_lottery`]: Unstakes all lottery funds and schedules [`Call::process_matured_withdrawals`] after the timelock period -//! * [`Call::rebalance_stake`]: Immediately unstakes overweight collators (with low APY) for later restaking into underweight collators (with high APY) -//! -//! ### Important state queries callable via RPC -//! * [`Pallet::next_drawing_at`]: Block number where the next drawing will happen -//! * [`Pallet::not_in_drawing_freezeout`]: False if deposits/withdrawals are currently frozen -//! * [`Pallet::current_prize_pool`]: Token amount currently in the pallet the winner would get if the drawing was now -//! Call these from a frontend as e.g. -//! ```bash -//! curl http://localhost:9933 -H "Content-Type:application/json;charset=utf-8" -d '{"jsonrpc":"2.0","id":1,"method":"lottery_next_drawing_at","params": []}' -//! curl http://localhost:9933 -H "Content-Type:application/json;charset=utf-8" -d '{"jsonrpc":"2.0","id":1,"method":"lottery_current_prize_pool","params": []}' -//! curl http://localhost:9933 -H "Content-Type:application/json;charset=utf-8" -d '{"jsonrpc":"2.0","id":1,"method":"lottery_not_in_drawing_freezeout","params": []}' -//! ``` -//! -//! Please refer to [`Pallet`] for more documentation on each function. -//! Furthermore, the storage items containing all relevant information about lottery state can be queried via e.g. the [polkadot.js API](https://polkadot.js.org/docs/api) - -#![cfg_attr(not(feature = "std"), no_std)] - -mod staking; - -#[cfg(feature = "rpc")] -pub mod rpc; -pub mod runtime; - -#[cfg(any(test, feature = "runtime-benchmarks"))] -mod benchmarks; -#[cfg(test)] -mod mock; -#[cfg(test)] -mod tests; - -pub mod weights; -pub use weights::WeightInfo; - -pub use pallet::*; -#[frame_support::pallet] -pub mod pallet { - use super::*; - pub use ::function_name::named; - use frame_support::{ - ensure, log, - pallet_prelude::*, - traits::{ - schedule::{v2::Named as ScheduleNamed, DispatchTime, MaybeHashed, LOWEST_PRIORITY}, - ExistenceRequirement::KeepAlive, - *, - }, - PalletId, - }; - use frame_system::{pallet_prelude::*, RawOrigin}; - use pallet_parachain_staking::BalanceOf; - use sp_arithmetic::traits::SaturatedConversion; - use sp_core::U256; - use sp_runtime::{ - traits::{AccountIdConversion, CheckedAdd, CheckedSub, Dispatchable, Saturating, Zero}, - ArithmeticError, DispatchResult, - }; - use sp_std::prelude::*; - - const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); - - pub type CallOf = ::RuntimeCall; - - #[pallet::config] - pub trait Config: frame_system::Config + pallet_parachain_staking::Config { - /// The aggregated `RuntimeCall` type. - type RuntimeCall: Parameter - + Dispatchable - + From>; - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// The Scheduler. - type Scheduler: ScheduleNamed< - Self::BlockNumber, - CallOf, - Self::PalletsOrigin, - Hash = Self::Hash, - >; - // Randomness source to use for determining lottery winner - type RandomnessSource: Randomness; - /// Something that can estimate the cost of sending an extrinsic - type EstimateCallFee: frame_support::traits::EstimateCallFee< - pallet_parachain_staking::Call, - BalanceOf, - > + frame_support::traits::EstimateCallFee, BalanceOf>; - /// Origin that can manage lottery parameters and start/stop drawings - type ManageOrigin: EnsureOrigin; - /// Overarching type of all pallets origins. - type PalletsOrigin: From>; - /// Account Identifier from which the internal Pot is generated. - #[pallet::constant] - type LotteryPot: Get; - /// Time in blocks between lottery drawings - #[pallet::constant] - type DrawingInterval: Get; - /// Time in blocks *before* a drawing in - /// Depending on the randomness source, the winner might be established before the drawing, this prevents modification of the eligible winning set after the winner - /// has been established but before it is selected by [`Call::draw_lottery`] which modifications of the win-eligble pool are prevented - #[pallet::constant] - type DrawingFreezeout: Get; - /// Time in blocks until a collator is done unstaking - #[pallet::constant] - type UnstakeLockTime: Get; // XXX: could maybe alculate this from staking LeaveDelayRounds * DefaultBlocksPerRound - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; - } - - #[pallet::pallet] - #[pallet::storage_version(STORAGE_VERSION)] - #[pallet::without_storage_info] - pub struct Pallet(PhantomData); - - // Configurable (constant) storage items - - /// NOTE: how much KMA to keep in the pallet for gas - /// This must be initialized at genesis, otherwise the pallet will run out of gas at the first drawing - #[pallet::storage] - #[pallet::getter(fn gas_reserve)] - pub(super) type GasReserve = StorageValue<_, BalanceOf, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn min_deposit)] - pub(super) type MinDeposit = StorageValue<_, BalanceOf, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn min_withdraw)] - pub(super) type MinWithdraw = StorageValue<_, BalanceOf, ValueQuery>; - - // Dynamic Storage Items - - /// sum of all user's deposits, to ensure balance never drops below - /// Incremented on [`Call::deposit`] - /// Decremented on withdrawal to user wallet in [`Call::process_matured_withdrawals`] - #[pallet::storage] - #[pallet::getter(fn sum_of_deposits)] - pub(super) type SumOfDeposits = StorageValue<_, BalanceOf, ValueQuery>; - - /// Total number of token eligible to win in the current drawing cycle - /// Incremented on [`Call::deposit`] - /// Decremented on [`Call::request_withdraw`] - #[pallet::storage] - #[pallet::getter(fn total_pot)] - pub(super) type TotalPot = StorageValue<_, BalanceOf, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn total_users)] - pub(super) type TotalUsers = StorageValue<_, u32, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn is_rebalancing)] - pub(super) type RebalanceInProgress = StorageValue<_, bool, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn active_balance_per_user)] - pub(super) type ActiveBalancePerUser = - StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn unclaimed_winnings_by_account)] - pub(super) type UnclaimedWinningsByAccount = - StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf, OptionQuery>; - - /// Free balance in the pallet that belongs to a previous lottery winner - /// Incremented on winner election in the course of a drawing - /// Decremented on transfer of winnings to ower wallet in [`Call::claim_my_winnings`] - #[pallet::storage] - #[pallet::getter(fn total_unclaimed_winnings)] - pub(super) type TotalUnclaimedWinnings = StorageValue<_, BalanceOf, ValueQuery>; - - /// Free balance in the pallet that was unstaked from a collator and is needed for future withdrawal requests - /// Incremented on successful unstaking of a collator - /// Decremented on transfer of funds to withdrawer and on restaking of funds a collator - #[pallet::storage] - #[pallet::getter(fn unlocked_unstaking_funds)] - pub(super) type UnlockedUnstakingFunds = StorageValue<_, BalanceOf, ValueQuery>; - - #[derive(Clone, Encode, Decode, TypeInfo)] - pub(super) struct UnstakingCollator { - pub account: AccountId, - pub since: BlockNumber, - } - - #[pallet::storage] - pub(super) type UnstakingCollators = - StorageValue<_, Vec>, ValueQuery>; - - /// This is balance unstaked from a collator that is not needed to service user's withdrawal requests - /// Incremented on initiation of a collator unstake in [`Call::request_withdraw`] - /// Decremented on [`Call::request_withdraw`] (no collator unstake) and [`Call::rebalance_stake`] (restaking of surplus funds) - #[pallet::storage] - #[pallet::getter(fn surplus_unstaking_balance)] - pub(super) type SurplusUnstakingBalance = StorageValue<_, BalanceOf, ValueQuery>; - - #[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, TypeInfo)] - pub struct Request { - pub user: AccountId, - pub block: BlockNumber, - pub balance: Balance, - } - - #[pallet::storage] - #[pallet::getter(fn withdrawal_request_queue)] - pub(super) type WithdrawalRequestQueue = - StorageValue<_, Vec>>, ValueQuery>; - - /// Incremented whenever delegating tokens to a collator - /// Collators are removed from here when their funds are unlocked in [`Call::finish_unstaking_collators`] - #[pallet::storage] - #[pallet::getter(fn staked_collators)] - pub(super) type StakedCollators = - StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf, ValueQuery>; - - #[pallet::genesis_config] - pub struct GenesisConfig { - /// amount of token to keep in the pot for paying gas fees - pub gas_reserve: BalanceOf, - pub min_deposit: BalanceOf, - pub min_withdraw: BalanceOf, - } - - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - Self { - min_deposit: 1u32.into(), - min_withdraw: 1u32.into(), - gas_reserve: 10_000u32.into(), - } - } - } - - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { - #[inline] - fn build(&self) { - GasReserve::::set(self.gas_reserve); - MinDeposit::::set(self.min_deposit); - MinWithdraw::::set(self.min_withdraw); - } - } - - #[pallet::event] - #[pallet::generate_deposit(fn deposit_event)] - pub enum Event { - LotteryStarted, - LotteryStopped, - LotteryWinner { - account: T::AccountId, - amount: BalanceOf, - }, - Deposited { - account: T::AccountId, - amount: BalanceOf, - }, - ScheduledWithdraw { - account: T::AccountId, - amount: BalanceOf, - }, - Withdrawn { - account: T::AccountId, - amount: BalanceOf, - }, - Claimed { - account: T::AccountId, - amount: BalanceOf, - }, - } - - #[pallet::error] - pub enum Error { - /// Lottery has not been started - LotteryNotStarted, - /// Lottery has already been started - LotteryIsRunning, - /// Pre-drawing freeze in effect, can't modify balances - TooCloseToDrawing, - /// FATAL: Assigning/Transferring winning claims - /// would **remove** user deposited funds from pallet - PotBalanceTooLow, - /// FATAL: Can't stake the requested amount with available funds - PotBalanceTooLowToStake, - /// Pallet balance is lower than the needed gas fee buffer - PotBalanceBelowGasReserve, - /// Pallet balance is too low to submit a needed transaction - PotBalanceTooLowToPayTxFee, - /// No funds eligible to win - NobodyPlaying, - /// No funds to win in pool - NothingToWin, - /// Fatal: No winner could be selected - NoWinnerFound, - /// Deposit amount is below minimum amount - DepositBelowMinAmount, - /// Requested Withdrawal amount is below minimum - WithdrawBelowMinAmount, - /// Requested to withdraw more than you deposited - WithdrawAboveDeposit, - /// No deposits found for this account - NoDepositForAccount, - /// Fatal: No collators found to assign this deposit to - NoCollatorForDeposit, - /// Fatal: No collators found to assign this deposit to - NoCollatorForStake, - /// Fatal: No collators found to withdraw from - NoCollatorForWithdrawal, - /// Fatal: A calculation that must not be negative would underflow - ArithmeticUnderflow, - /// Fatal: A calculation that would overflow - ArithmeticOverflow, - /// Fatal: Pallet configuration violates sanity checks - PalletMisconfigured, - /// Fatal: Could not schedule lottery drawings - CouldNotSchedule, - /// Fatal: Functionality not yet supported - NotImplemented, - } - - #[pallet::call] - impl Pallet { - /// Allows any user to deposit tokens into the lottery - /// - /// # Arguments - /// - /// * `amount` - The amount of tokens to be deposited. - #[pallet::call_index(0)] - #[pallet::weight(::WeightInfo::deposit(Pallet::::total_users(), pallet_parachain_staking::Pallet::::selected_candidates().len() as u32))] - pub fn deposit(origin: OriginFor, amount: BalanceOf) -> DispatchResult { - let caller_account = ensure_signed(origin)?; - ensure!( - amount >= Self::min_deposit(), - Error::::DepositBelowMinAmount - ); - ensure!( - Self::not_in_drawing_freezeout(), - Error::::TooCloseToDrawing - ); - ensure! { // Sanity check: make sure we dont accept deposits that will fail in staking - Self::min_deposit() >= ::MinDelegation::get(), - Error::::PalletMisconfigured - }; - - // Transfer funds to pot - ::Currency::transfer( - &caller_account, - &Self::account_id(), - amount, - KeepAlive, - )?; - - // Attempt to stake them - let collator_balance_pairs = Self::calculate_deposit_distribution(amount); - ensure!( - !collator_balance_pairs.is_empty(), - Error::::NoCollatorForDeposit - ); - for (some_collator, balance) in collator_balance_pairs { - // TODO: What if the `balance` is below `MinDelegation`a on a new collator? this will fail - Self::do_stake_one_collator(some_collator, balance)?; - } - - // Add to active funds - ActiveBalancePerUser::::mutate(caller_account.clone(), |balance| *balance += amount); - TotalPot::::mutate(|balance| *balance += amount); - TotalUsers::::mutate(|users| *users += 1); - SumOfDeposits::::mutate(|balance| *balance += amount); - Self::deposit_event(Event::Deposited { - account: caller_account, - amount, - }); - Ok(()) - } - - /// Requests a withdrawal of `amount` from the caller's active funds. - /// - /// Withdrawal is not immediate as funds are subject to a timelock imposed by [`pallet_parachain_staking`] - /// It will be executed with the first [`Call::draw_lottery`] call after timelock expires - /// - /// Withdrawals can NOT be cancelled because they inflict ecomomic damage on the lottery through collator unstaking - /// and the user causing this damage must be subjected to economic consequences for doing so - /// - /// The withdrawal is paid from [`SurplusUnstakingBalance`] - /// If this balance is too low to handle the request, another collator is unstaked - /// - /// # Arguments - /// - /// * `amount` - the amount of funds to withdraw - /// - /// # Errors - /// - /// Returns an error if: - /// * `amount` is below the minimum withdraw amount - /// * `amount` is larger than the user's total deposit - /// * It is too close to the drawing - /// * The user has no or not enough active funds - /// * There are any arithmetic underflows - #[pallet::call_index(1)] - #[pallet::weight(::WeightInfo::request_withdraw(Pallet::::total_users(), pallet_parachain_staking::Pallet::::selected_candidates().len() as u32))] - pub fn request_withdraw(origin: OriginFor, amount: BalanceOf) -> DispatchResult { - let caller = ensure_signed(origin)?; - - ensure!( - amount >= Self::min_withdraw(), - Error::::WithdrawBelowMinAmount - ); - ensure!( - Self::not_in_drawing_freezeout(), - Error::::TooCloseToDrawing - ); - let now = >::block_number(); - log::debug!("Requesting withdraw of {:?} tokens", amount); - // Ensure user has enough funds active and mark them as offboarding (remove from `ActiveBalancePerUser`) - ActiveBalancePerUser::::try_mutate_exists(caller.clone(), |maybe_balance| { - match maybe_balance { - None => Err(Error::::NoDepositForAccount), - Some(balance) => { - // Withdraw only what's active - ensure!(*balance >= amount, Error::::WithdrawAboveDeposit); - // Mark funds as offboarding - WithdrawalRequestQueue::::mutate(|withdraw_vec| { - withdraw_vec.push(Request { - user: caller.clone(), - block: now, - balance: amount, - }) - }); - // store reduced balance - *maybe_balance = match balance - .checked_sub(&amount) - .ok_or(Error::::ArithmeticUnderflow)? - { - new_balance if new_balance.is_zero() => { - // remove user if this was his last remaining funds - TotalUsers::::try_mutate(|users| { - *users = (*users) - .checked_sub(1u32) - .ok_or(Error::::ArithmeticUnderflow)?; - Ok(()) - })?; - None - } - new_balance => Some(new_balance), - }; - TotalPot::::try_mutate(|pot| { - *pot = (*pot) - .checked_sub(&amount) - .ok_or(Error::::ArithmeticUnderflow)?; - Ok(()) - })?; - Ok(()) - } - } - })?; - - // Unstaking workflow - // 1. See if this withdrawal can be serviced with left-over balance from an already unstaking collator, if so deduct remaining balance and schedule the request - // 2. If it can't, find the collator with the smallest delegation that is able to handle this withdrawal request and fully unstake it - // 3. Add balance overshoot to "remaining balance" to handle further requests from - - // If the withdrawal fits in the currently unstaking funds, do nothing else - SurplusUnstakingBalance::::try_mutate(|remaining_balance| { - match (*remaining_balance).checked_sub(&amount){ - Some(subtracted) => { - *remaining_balance = subtracted; - Ok(()) - } - _ => { - Err("not enough left to handle this request from current unstaking funds") - } - } - }) - .or_else(|_| { - // Withdrawal needs extra collators to unstake to have enough funds to serve withdrawals, do it - let reserve = SurplusUnstakingBalance::::get(); - let mut remaining_to_withdraw = amount - reserve; - - // unstake collators as necessary. This updates `SurplusUnstakingBalance` - for collator_to_unstake in Self::calculate_withdrawal_distribution(remaining_to_withdraw){ - let our_stake = StakedCollators::::get(collator_to_unstake.clone()); - remaining_to_withdraw = remaining_to_withdraw.saturating_sub(our_stake); - // The following call updates `SurplusUnstakingBalance` with newly unstaked funds - Self::do_unstake_collator(now,collator_to_unstake)?; - } - if !remaining_to_withdraw.is_zero() { - return Err("FATAL: Didn't unstake the full requested balance (or more)"); - } - SurplusUnstakingBalance::::try_mutate(|remaining_balance| { - match (*remaining_balance).checked_sub(&amount){ - Some(subtracted) => { - *remaining_balance = subtracted; - Ok(()) - } - _ => { - Err("not enough unstaking balance to handle request after unstaking additional collators") - } - } - }) - })?; - // END UNSTAKING SECTION - Self::deposit_event(Event::ScheduledWithdraw { - account: caller, - amount, - }); - Ok(()) - } - - /// Allows the caller to transfer any of the account's previously unclaimed winnings to his their wallet - /// - /// # Errors - /// - /// CannotLookup: The caller has no unclaimed winnings. - #[pallet::call_index(2)] - #[pallet::weight(::WeightInfo::claim_my_winnings(pallet_parachain_staking::Pallet::::selected_candidates().len() as u32))] - pub fn claim_my_winnings(origin: OriginFor) -> DispatchResult { - let caller = ensure_signed(origin)?; - match UnclaimedWinningsByAccount::::take(caller.clone()) { - Some(winnings) => { - // Never pay out winnings if it would reduce pallet funds below total number of user's deposits - let all_funds_in_pallet = - ::Currency::total_balance( - &Self::account_id(), - ); - ensure!( - // Sanity check: Never pay out funds that would draw on user deposits - all_funds_in_pallet.saturating_sub(winnings) >= Self::sum_of_deposits(), - Error::::PotBalanceTooLow - ); - - TotalUnclaimedWinnings::::try_mutate(|old| { - *old = (*old) - .checked_sub(&winnings) - .ok_or(ArithmeticError::Underflow)?; - Ok::<(), ArithmeticError>(()) - })?; - ::Currency::transfer( - &Self::account_id(), - &caller, - winnings, - KeepAlive, - )?; // NOTE: If the transfer fails, the TXN get rolled back and the winnings stay in the map for claiming later - Self::deposit_event(Event::Claimed { - account: caller, - amount: winnings, - }); - Ok(()) - } - None => Err(DispatchError::CannotLookup), - } - } - - /// Maximizes staking APY and thus accrued winnings by removing staked tokens from overallocated/inactive - /// collators and adding to underallocated ones. - /// - /// Can only be called by the account set as [`Config::ManageOrigin`] - /// - /// This function should be called when the pallet's staked tokens are staked with overweight collators - /// or collators that became inactive or left the staking set. - /// This will withdraw the tokens from overallocated and inactive collators and wait until the funds are unlocked, - /// then re-allocate them to underallocated collators. - /// - /// Note that this operation can run in parallel with a drawing, but it will reduce the staking revenue - /// generated in that drawing by the amount of funds being rebalanced. - /// - /// # Errors - /// - /// * BadOrigin: Caller is not ManageOrigin - /// * TODO: Amount of tokens to be rebalanced would be too low. - #[pallet::call_index(3)] - #[pallet::weight(0)] - pub fn rebalance_stake(origin: OriginFor) -> DispatchResult { - T::ManageOrigin::ensure_origin(origin.clone())?; - Err(crate::pallet::DispatchError::Other( - Error::::NotImplemented.into(), - )) - - // withdraw from overallocated collators, wait until funds unlock, re-allocate to underallocated collators - // TODO: find some balancing algorithm that does this or just reuse the one we use for deposits - - // Self::deposit_event(Event::StartedRebalance(amount)); - // Ok(()) - } - - /// Starts the lottery by scheduling a [`Call::draw_lottery`] call - /// - /// Can only be called by the account set as [`Config::ManageOrigin`] - /// - /// # Errors - /// - /// Returns an error if: - /// * BadOrigin: Caller is not ManageOrigin - /// * The pallet does not have enough funds to pay for gas fees for at least the first drawing. - /// * The drawing interval is zero or negative. - /// * The Scheduler implementation failed to schedule the [`Call::draw_lottery`] call. - /// - /// # Details - /// - /// This function schedules a [`Call::draw_lottery`] call with a delay specified by the [`Config::DrawingInterval`] configuration - /// using [`frame_support::traits::schedule::Named`] with the lottery pallet's pallet ID configured with [`Config::LotteryPot`] as identifier. - /// If the lottery is already started, this function will fail. - /// - /// You can always learn what block the next drawing - if any - will happen by calling [`Self::next_drawing_at`] - #[pallet::call_index(4)] - #[pallet::weight(::WeightInfo::start_lottery())] - pub fn start_lottery(origin: OriginFor) -> DispatchResult { - T::ManageOrigin::ensure_origin(origin.clone())?; - ensure!( - Self::next_drawing_at().is_none(), - Error::::LotteryIsRunning - ); - // Pallet has enough funds to pay gas fees for at least the first drawing - ensure!( - Self::surplus_funds() >= Self::gas_reserve(), - Error::::PotBalanceBelowGasReserve - ); - // NOTE: If more than gas_reserve is in the pallet, the full excess will be paid out to the winner of the next drawing! This is intended to dope the winning balance with extra rewards - - let drawing_interval = ::DrawingInterval::get(); - ensure!( - drawing_interval > 0u32.into(), - Error::::PalletMisconfigured - ); - let lottery_drawing_call: CallOf = Call::draw_lottery {}.into(); - T::Scheduler::schedule_named( - Self::lottery_schedule_id(), - DispatchTime::After(drawing_interval), - Some((drawing_interval, u32::MAX)), // XXX: Seems scheduler has no way to schedule infinite amount - LOWEST_PRIORITY, - frame_support::dispatch::RawOrigin::Root.into(), - MaybeHashed::Value(lottery_drawing_call), - ) - .map_err(|_| Error::::CouldNotSchedule)?; - - Self::deposit_event(Event::LotteryStarted); - Ok(()) - } - - /// Stops the ongoing lottery and cancels the scheduled and any future drawings. - /// - /// This function cancels the scheduled drawing. Does not prevent users from interacting with the pallet - /// - /// Can only be called by the account set as [`Config::ManageOrigin`] - /// - /// # Errors - /// - /// * BadOrigin: Caller is not manager - /// * LotteryNotStarted: Nothing to stop - /// - #[pallet::call_index(5)] - #[pallet::weight(::WeightInfo::stop_lottery())] - pub fn stop_lottery(origin: OriginFor) -> DispatchResult { - T::ManageOrigin::ensure_origin(origin.clone())?; - T::Scheduler::cancel_named(Self::lottery_schedule_id()) - .map_err(|_| Error::::LotteryNotStarted)?; - Self::deposit_event(Event::LotteryStopped); - Ok(()) - } - - /// Draws a lottery winner and allows them to claim their winnings later. Only the [`Config::ManageOrigin`] can execute this function. - /// - /// Can only be called by the account set as [`Config::ManageOrigin`] - /// - /// # Errors - /// - /// ## Operational - /// * BadOrigin: Caller is not ManageOrigin - /// * PotBalanceBelowGasReserve: The balance of the pot is below the gas reserve so no winner will be paid out - /// - /// ## Fatal - /// * ArithmeticError::Underflow: An underflow occurred when calculating the payout. - /// * PotBalanceTooLow: The balance of the pot is too low. - /// * NoWinnerFound: Nobody was selected as winner - #[pallet::call_index(6)] - #[pallet::weight(::WeightInfo::draw_lottery(Pallet::::total_users(), pallet_parachain_staking::Pallet::::selected_candidates().len() as u32))] - pub fn draw_lottery(origin: OriginFor) -> DispatchResult { - T::ManageOrigin::ensure_origin(origin.clone())?; - let now = >::block_number(); - log::trace!("Drawing lottery called at block {:?}", now.clone()); - - let total_funds_in_pallet = - ::Currency::total_balance( - &Self::account_id(), - ); - // all surplus tokens accrued at this point can be paid out to the winner - let winning_claim = Self::current_prize_pool(); - let participating_funds = Self::total_pot(); - log::debug!( - "drawing: total funds: {:?}, participating funds: {:?}, surplus funds/winner payout: {:?}", - total_funds_in_pallet.clone(), - participating_funds.clone(), - winning_claim.clone() - ); - // If there's nothing to win or nobody is playing we skip the drawing logic - if !winning_claim.is_zero() && !participating_funds.is_zero() { - ensure!( - // Sanity check: Prevent allocating funds as winnings to a user that would have to be paid from user deposits - Self::sum_of_deposits() // all users' deposits (staked and unstaking) - .saturating_add(Self::total_unclaimed_winnings()) // all prior winnings - .saturating_add(winning_claim) // and the current winner's new claim - <= total_funds_in_pallet, // don't exceed funds in the pallet - Error::::PotBalanceTooLow - ); - Self::select_winner(winning_claim)?; - } else { - log::debug!( - "drawing: skipped due to zero winning claim {:?} or participating funds {:?}", - winning_claim, - participating_funds, - ); - } - // unstake, pay out tokens due for withdrawals and restake excess funds - // At this point, all excess funds except for `gas_reserve` have been reserved for the current winner - Self::process_matured_withdrawals(origin)?; - Ok(()) - } - - /// This function transfers all withdrawals to user's wallets that are payable from unstaked collators whose timelock expired - /// - /// Can only be called by the account set as [`Config::ManageOrigin`] - /// - /// # Errors - /// - /// * BadOrigin: Caller is not ManageOrigin - /// * errors defined by the do_process_matured_withdrawals function. - #[pallet::call_index(7)] - #[pallet::weight(::WeightInfo::process_matured_withdrawals())] - pub fn process_matured_withdrawals(origin: OriginFor) -> DispatchResult { - log::trace!("process_matured_withdrawals"); - T::ManageOrigin::ensure_origin(origin.clone())?; - Self::finish_unstaking_collators(); - Self::do_process_matured_withdrawals()?; - Self::do_rebalance_remaining_funds()?; - Ok(()) - } - - /// Liquidates all funds held in the lottery pallet, unstaking collators, returning user deposits and paying out winnings - /// - /// Can only be called by the account set as [`Config::ManageOrigin`] - /// - /// Due to staking timelock, this schedules the payout of user deposits after timelock has expired. - /// NOTE: TODO: Any interaction with this pallet is disallowed while a liquidation is ongoing - /// - /// # Errors - /// - /// * BadOrigin: Caller is not ManageOrigin - /// * Fails if a lottery has not been stopped and a drawing is ongoing - #[pallet::call_index(8)] - #[pallet::weight(0)] - pub fn liquidate_lottery(origin: OriginFor) -> DispatchResult { - T::ManageOrigin::ensure_origin(origin.clone())?; - - ensure!( - Self::next_drawing_at().is_none(), - Error::::LotteryIsRunning - ); - - Err(crate::pallet::DispatchError::Other( - Error::::NotImplemented.into(), - )) - - // TODO: Unstake all collators, schedule return of all user deposits - // for collator in collators_we_staked_to { - // do_unstake(collator); - // } - // TODO: Lock everything until this process is finished - - // TODO: return user deposits and pay out winnings, deposit event - - // Ok(()) - } - #[pallet::call_index(9)] - #[pallet::weight(::WeightInfo::set_min_deposit())] - pub fn set_min_deposit(origin: OriginFor, min_deposit: BalanceOf) -> DispatchResult { - T::ManageOrigin::ensure_origin(origin.clone())?; - ensure!( - min_deposit >= Self::min_withdraw(), - Error::::PalletMisconfigured - ); - MinDeposit::::set(min_deposit); - Ok(()) - } - #[pallet::call_index(10)] - #[pallet::weight(::WeightInfo::set_min_withdraw())] - pub fn set_min_withdraw( - origin: OriginFor, - min_withdraw: BalanceOf, - ) -> DispatchResult { - T::ManageOrigin::ensure_origin(origin.clone())?; - MinWithdraw::::set(min_withdraw); - Ok(()) - } - #[pallet::call_index(11)] - #[pallet::weight(::WeightInfo::set_gas_reserve())] - pub fn set_gas_reserve(origin: OriginFor, gas_reserve: BalanceOf) -> DispatchResult { - T::ManageOrigin::ensure_origin(origin.clone())?; - GasReserve::::set(gas_reserve); - Ok(()) - } - } - - impl Pallet { - /// Get a unique, inaccessible account id from the `PotId`. - pub(crate) fn account_id() -> T::AccountId { - T::LotteryPot::get().into_account_truncating() - } - /// Get an identifier for scheduling drawings from the `PotId`. - fn lottery_schedule_id() -> Vec { - T::LotteryPot::get().0.to_vec() - } - fn select_winning_balance( - max_winning_balance: BalanceOf, - ) -> Result, Error> { - const MAX_NUMBER_OF_RESAMPLES: u8 = 3; - let mut winning_number = 0; // XXX: This shouldn't need initialization but the compiler doesn't get it - for n in 0u8..MAX_NUMBER_OF_RESAMPLES { - let random: (T::Hash, BlockNumberFor); - #[cfg(feature = "runtime-benchmarks")] - { - use rand::{RngCore, SeedableRng}; - use sp_runtime::traits::Hash; - // XXX: Benchmarking randomness changes per block instead of per epoch - let mut rng = rand::rngs::StdRng::seed_from_u64( - >::block_number() - .try_into() - .unwrap_or(n as u64), - ); - let mut rnd = [0u8; 32]; - rng.fill_bytes(&mut rnd); - let randomness = T::Hashing::hash(&rnd); - random = (randomness, ::BlockNumber::zero()); - log::debug!("select-winner using randomness {:?}", random); - } - #[cfg(not(feature = "runtime-benchmarks"))] - { - random = T::RandomnessSource::random(&[n; 1]); - log::debug!("select-winner using randomness {:?}", random); - // TODO: The following check needs a change to pallet randomness but is static, - // so this can be done manually on deployment of the pallet - // ensure!( - // random.1 = randomness_established_at_block - // .saturating_add(::DrawingFreezeout::get()) - // < >::block_number(), - // Error::::PalletMisconfigured - // ); - } - let random_hash = random.0; - let as_number = U256::from_big_endian(random_hash.as_ref()); - winning_number = as_number.low_u128(); - // naive application of the modulo operation can bias the result, reject and resample if the number is larger than the maximum divisor of user array length in the u128 number range - debug_assert_eq!( - core::mem::size_of::>(), - core::mem::size_of::() - ); - let number_to_start_rejecting_at: u128 = u128::max_value().saturating_sub( - u128::max_value() % max_winning_balance.saturated_into::(), - ); - debug_assert!((number_to_start_rejecting_at - % max_winning_balance.saturated_into::()) - .is_zero()); - if winning_number.saturated_into::() < number_to_start_rejecting_at { - break; - } else { - // sample must be rejected because it can't be safely modulo'd, retry with high u128 - winning_number = (as_number >> 128).low_u128(); - let number_to_start_rejecting_at: u128 = u128::max_value().saturating_sub( - u128::max_value() % max_winning_balance.saturated_into::(), - ); - debug_assert!( - number_to_start_rejecting_at - <= max_winning_balance.saturated_into::() - ); - if winning_number.saturated_into::() < number_to_start_rejecting_at { - break; - } - if n + 1 < MAX_NUMBER_OF_RESAMPLES { - // if still not good, we need to re-request randomness with a changed nonce - continue; - } else { - // we loop this up to 3 times (yielding 6 possible values) before giving up, printing a warning and accepting that the result was biased - log::warn!("No unbiased random samples found after {:?} retries. using {:?} which will be subject to modulo bias",(n+1)*2,winning_number.clone()); - break; - } - } - } - // no risk of modulo bias here unless we ran out of retries above - let winning_balance: BalanceOf = BalanceOf::::try_from(winning_number) - .map_err(|_| Error::::ArithmeticOverflow)? - % max_winning_balance; - log::debug!( - "winning_number: {:?}, winning balance: {:?}", - winning_number, - winning_balance - ); - Ok(winning_balance) - } - fn select_winner(payout_for_winner: BalanceOf) -> DispatchResult { - if payout_for_winner.is_zero() { - return Err(Error::::NothingToWin.into()); - } - let participating_funds = Self::total_pot(); - if participating_funds.is_zero() { - return Err(Error::::NobodyPlaying.into()); - } - // Match random number to winner. We select a winning **balance** and then just add up accounts in the order they're stored until the sum of balance exceeds the winning amount - // IMPORTANT: This order and active balances must be locked to modification after the random seed is created (relay BABE randomness, 2 epochs ago) - let winning_balance = Self::select_winning_balance(participating_funds)?; - let mut maybe_winner: Option = None; - let mut count: BalanceOf = 0u32.into(); - for (account, balance) in ActiveBalancePerUser::::iter() { - count += balance; - if count >= winning_balance { - maybe_winner = Some(account); - break; - } - } - // Should be impossible: If no winner was selected, return Error - ensure!(maybe_winner.is_some(), Error::::NoWinnerFound); - let winner = maybe_winner.expect("we checked a winner exists before. qed"); - // Allow winner to manually claim their winnings later - UnclaimedWinningsByAccount::::mutate(winner.clone(), |maybe_balance| { - *maybe_balance = Some( - maybe_balance - .unwrap_or_else(|| 0u32.into()) - .saturating_add(payout_for_winner), - ); - }); - TotalUnclaimedWinnings::::try_mutate(|old| { - *old = (*old) - .checked_add(&payout_for_winner) - .ok_or(ArithmeticError::Overflow)?; - Ok::<(), ArithmeticError>(()) - })?; - log::debug!( - "winning of {:?} added to claim for account {:?}", - payout_for_winner, - winner - ); - Self::deposit_event(Event::LotteryWinner { - account: winner, - amount: payout_for_winner, - }); - Ok(()) - } - - /// Unstake any collators we can unstake - /// This is infallible, if any step fails we just leave the collator in the request queue - fn finish_unstaking_collators() { - let now = >::block_number(); - let mut unstaking = UnstakingCollators::::get(); - let original_len = unstaking.len(); - if unstaking.is_empty() { - return; - }; - // Unstake what we can (false), leave the rest (true) - unstaking.retain(|collator|{ - // Leave collators that are not finished unstaking alone - if collator.since + ::UnstakeLockTime::get() > now { - return true; - }; - // Recover funds locked in the collator - // There can only be one request per collator and it is always a full revoke_delegation call - let delegation_requests_against_this_collator = pallet_parachain_staking::Pallet::::delegation_scheduled_requests(collator.account.clone()); - let balance_to_unstake = match delegation_requests_against_this_collator.iter().find(|request|request.delegator == Self::account_id()){ - Some(our_request) if matches!(our_request.action, pallet_parachain_staking::DelegationAction::Revoke(_)) => { - if T::BlockNumber::from(our_request.when_executable) > now { - log::error!("Collator {:?} finished lottery unstaking timelock but not the pallet_parachain_staking one. leaving in queue", collator.account.clone()); - return true; - }; - our_request.action.amount() - } - _ => { - log::error!( "Expected revoke_delegation request not found on collator {:?}. Leaving in withdraw queue", collator.account.clone() ); - return true; - } - }; - // Ensure the pallet has enough gas to pay for this. Should never run out as long as its's called from `draw_lottery` - let fee_estimate : BalanceOf = T::EstimateCallFee::estimate_call_fee(&pallet_parachain_staking::Call::execute_delegation_request { delegator: Self::account_id() , candidate: collator.account.clone() }, None::.into()); - if Self::surplus_funds() <= fee_estimate{ - log::warn!("could not finish unstaking delegation because the pallet is out of funds to pay TX fees. Skipping"); - return true; - }; - match pallet_parachain_staking::Pallet::::execute_delegation_request( - RawOrigin::Signed(Self::account_id()).into(), - Self::account_id(), - collator.account.clone(), - ){ - Err(e) => { - log::error!("Collator finished unstaking timelock but could not be removed with error {:?}",e); - true - }, - Ok(_) => { - // collator was unstaked, its funds are now "free balance", we track it so it won't be given to the next winner - log::debug!("Unstaked {:?} from collator {:?}",balance_to_unstake,collator.account.clone()); - >::mutate(|unlocked| *unlocked = (*unlocked).saturating_add(balance_to_unstake)); - >::remove(collator.account.clone()); - // don't retain this collator in the unstaking collators vec - false - }, - } - }); - if original_len != unstaking.len() { - log::debug!( - "Finished unstaking {:?} out of {:?} requests", - original_len - unstaking.len(), - original_len - ); - UnstakingCollators::::put(unstaking); - } - } - - #[named] - fn do_rebalance_remaining_funds() -> DispatchResult { - log::trace!(function_name!()); - // NOTE: This fn assumes `finish_unstaking_collators` and `process_outstanding_withdrawals` - // were previously called and all unlockable funds are claimed - - // Only restake what - // - isn't needed to service the still outstanding withdrawal requests - // - is funds that were previously unstaked - // - is surplus funds (we may have some from `finish_unstaking_collators`) - // NOTE: Funds tracked in `surplus_unstaking_balance` might still be partially stake locked - let outstanding_balance_to_withdraw = >::get() - .iter() - .map(|request| request.balance) - .reduce(|acc, balance| acc + balance) - .unwrap_or_else(|| 0u32.into()); - let restakable_balance = - Self::unlocked_unstaking_funds().saturating_sub(outstanding_balance_to_withdraw); - if restakable_balance < Self::min_deposit() { - log::debug!( - "Restakable balance of {:?} is below staking minimum of {:?}. Not restaking", - restakable_balance, - Self::min_deposit() - ); - return Ok(()); - } - let collator_balance_pairs = Self::calculate_deposit_distribution(restakable_balance); - if collator_balance_pairs.is_empty() { - log::debug!( - "No collators for redepositing available (likely all currently unstaking)" - ); - return Ok(()); - } - for (collator, amount_to_stake) in collator_balance_pairs { - Self::do_stake_one_collator(collator.clone(), amount_to_stake)?; - log::debug!( - "Rebalanced {:?} to collator {:?}", - amount_to_stake, - collator - ); - } - SurplusUnstakingBalance::::try_mutate(|bal| -> DispatchResult { - *bal = (*bal) - .checked_sub(&restakable_balance) - .ok_or(Error::::ArithmeticUnderflow)?; - Ok(()) - })?; - UnlockedUnstakingFunds::::try_mutate(|unlocked| -> DispatchResult { - *unlocked = (*unlocked) - .checked_sub(&restakable_balance) - .ok_or(Error::::ArithmeticUnderflow)?; - Ok(()) - })?; - Ok(()) - } - - /// This fn schedules a single shot payout of all matured withdrawals - /// Main usage: Automatic execution in the course of a drawing - /// It can also be manually invoke by T::ManageOrigin to reprocess withdrawals that - /// previously failed, e.g. due to the pallet running out of gas funds - /// A withdrawal is considered "matured" if its staking timelock expired - #[named] - fn do_process_matured_withdrawals() -> DispatchResult { - log::trace!(function_name!()); - if >::get().is_empty() { - return Ok(()); // nothing to do - } - let now = >::block_number(); - log::debug!( - "Serving withdrawals from unlocked unstaking funds of {:?}", - Self::unlocked_unstaking_funds() - ); - // Pay down the list from top (oldest) to bottom until we've paid out everyone or run out of available funds - >::mutate(|request_vec| -> Result<(), DispatchError> { - let mut left_overs: Vec> = Vec::new(); - for request in request_vec.iter() { - let funds_available_to_withdraw = Self::unlocked_unstaking_funds(); - // Don't pay anyone unless we have surplus funds - if funds_available_to_withdraw.is_zero() { - left_overs.push((*request).clone()); - continue; - } - // Don't pay anyone still timelocked - if request.block + ::UnstakeLockTime::get() > now { - left_overs.push((*request).clone()); - continue; - } - // stop paying people if we've run out of free funds. - // The assumption is the collators serving these requests will - // finish unstaking next round ( next lottery drawing ) - if request.balance > funds_available_to_withdraw { - left_overs.push((*request).clone()); - continue; - } - // we know we can pay this out, do it - >::mutate(|sum| *sum = (*sum).saturating_sub(request.balance)); - log::debug!( - "Transferring {:?} to {:?}", - request.balance.clone(), - request.user.clone() - ); - ::Currency::transfer( - &Self::account_id(), - &request.user, - request.balance, - KeepAlive, - )?; - >::try_mutate(|funds| -> DispatchResult { - *funds = (*funds) - .checked_sub(&request.balance) - .ok_or(Error::::ArithmeticUnderflow)?; - Ok(()) - })?; - Self::deposit_event(Event::Withdrawn { - account: request.user.clone(), - amount: request.balance, - }); - } - log::debug!( - "Have {:?} requests, {:?} free unstaking and {:?} surplus funds left over after transfers", - left_overs.len(), - Self::unlocked_unstaking_funds(), - Self::surplus_funds() - ); - // Update T::WithdrawalRequestQueue by mutating `request_vec` if we paid at least one guy - if left_overs.len() != (*request_vec).len() { - request_vec.clear(); - request_vec.append(&mut left_overs); - } - Ok(()) - })?; - Ok(()) - } - } - - impl Pallet { - // public getters for lottery state - /// Returns the block the next drawing will execute, if any - pub fn next_drawing_at() -> Option { - T::Scheduler::next_dispatch_time(Self::lottery_schedule_id()).ok() - } - /// funds in the lottery that are not staked, unstaked-pending-restaking or assigned to previous winners ( can be used to pay TX fees ) - pub(crate) fn surplus_funds() -> BalanceOf { - // Returns all funds the pallet holds that are not locked in staking - // Notably excludes `ActiveBalancePerUser` and the still locked part of `SurplusUnstakingBalance` - let non_staked_funds = - pallet_parachain_staking::Pallet::::get_delegator_stakable_free_balance( - &Self::account_id(), - ); - // unclaimed winnings are unlocked balance sitting in the pallet until a user claims, ensure we don't touch these - let unclaimed = Self::total_unclaimed_winnings(); - // Parts of `SurplusUnstakingBalance` become unlocked in `finish_unstaking_collators` once out of staking timelock - // It is possible they are only partially restaked in the same TX, the other part staying unlocked, - // waiting to serve a pending withdrawal in the next cycle. - // These free funds must not be touched until then, so we don't consider this balance a surplus - let unlocked = Self::unlocked_unstaking_funds(); - - non_staked_funds - .saturating_sub(unclaimed) - .saturating_sub(unlocked) - } - /// funds in the lottery pallet that are not needed/reserved for anything and can be paid to the next winner - pub fn current_prize_pool() -> BalanceOf { - // Ensure we keep a gas reserve from the staking rewards to be able to pay tx fees for staking/unstaking and withdrawals - Self::surplus_funds().saturating_sub(Self::gas_reserve()) - } - /// Returns if we're within the pre-drawing time where deposits/withdrawals are frozen - pub fn not_in_drawing_freezeout() -> bool { - match Self::next_drawing_at() { - None => { - true // can't be frozen if lottery stopped - } - Some(drawing) => { - let now = >::block_number(); - now < drawing.saturating_sub(::DrawingFreezeout::get()) - } - } - } - } -} diff --git a/pallets/pallet-lottery/src/mock.rs b/pallets/pallet-lottery/src/mock.rs deleted file mode 100644 index 279d8a07c..000000000 --- a/pallets/pallet-lottery/src/mock.rs +++ /dev/null @@ -1,567 +0,0 @@ -// Copyright 2020-2023 Manta Network. -// This file is part of Manta. -// -// Manta 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. -// -// Manta 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 Manta. If not, see . - -//! Test utilities -use core::marker::PhantomData; - -use crate as pallet_lottery; -use crate::{pallet, Config}; -use calamari_runtime::currency::KMA; -use frame_support::{ - construct_runtime, parameter_types, - traits::{ConstU128, ConstU32, Everything, GenesisBuild, OnFinalize, OnInitialize}, - weights::Weight, -}; -use frame_system::pallet_prelude::*; -use manta_primitives::types::{BlockNumber, Header}; -use pallet_parachain_staking::{InflationInfo, Range}; -use sp_core::H256; - -use sp_runtime::{ - traits::{BlakeTwo256, Hash, IdentityLookup}, - Perbill, Percent, -}; - -pub type AccountId = u64; -pub type Balance = u128; - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -// Configure a mock runtime to test the pallet. -construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - ParachainStaking: pallet_parachain_staking::{Pallet, Call, Storage, Config, Event}, - Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event}, - BlockAuthor: block_author::{Pallet, Storage}, - CollatorSelection: manta_collator_selection::{Pallet, Call, Storage, Config, Event}, - Lottery: pallet_lottery::{Pallet, Call, Storage, Event, Config}, - Preimage: pallet_preimage::{Pallet, Call, Storage, Event}, - } -); - -// Randomness trait -pub struct TestRandomness { - _marker: PhantomData, -} -impl frame_support::traits::Randomness> - for TestRandomness -{ - fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor) { - use rand::{rngs::OsRng, RngCore}; - let mut digest: Vec<_> = [0u8; 32].into(); - OsRng.fill_bytes(&mut digest); - digest.extend_from_slice(subject); - let randomness = T::Hashing::hash(&digest); - // NOTE: Test randomness is always "fresh" assuming block_number is > DrawingFreezeout - let block_number = 0u32.into(); - (randomness, block_number) - } -} - -parameter_types! { - pub const BlockHashCount: BlockNumber = 250; - pub const MaximumBlockWeight: u64 = 1024; - pub const MaximumBlockLength: u32 = 2 * 1024; - pub const AvailableBlockRatio: Perbill = Perbill::one(); - pub const SS58Prefix: u8 = manta_primitives::constants::CALAMARI_SS58PREFIX; -} -impl frame_system::Config for Test { - type BaseCallFilter = Everything; - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = BlockNumber; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Header = Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type BlockWeights = (); - type BlockLength = (); - type SS58Prefix = SS58Prefix; - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; -} -parameter_types! { - pub const ExistentialDeposit: u128 = 1; -} -impl pallet_balances::Config for Test { - type MaxReserves = (); - type ReserveIdentifier = [u8; 4]; - type MaxLocks = (); - type Balance = Balance; - type RuntimeEvent = RuntimeEvent; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type WeightInfo = (); -} - -parameter_types! { - // Our NORMAL_DISPATCH_RATIO is 70% of the 5MB limit - // So anything more than 3.5MB doesn't make sense here - pub const PreimageMaxSize: u32 = 3584 * 1024; -} - -impl pallet_preimage::Config for Test { - type WeightInfo = calamari_runtime::weights::pallet_preimage::SubstrateWeight; - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type ManagerOrigin = EnsureRoot; - // The sum of the below 2 amounts will get reserved every time someone submits a preimage. - // Their sum will be unreserved when the preimage is requested, i.e. when it is going to be used. - type BaseDeposit = ConstU128< - { - /* 1 */ - KMA - }, - >; - type ByteDeposit = ConstU128< - { - /* 1 */ - KMA - }, - >; -} -use sp_std::cmp::Ordering; -pub struct OriginPrivilegeCmp; -impl frame_support::traits::PrivilegeCmp for OriginPrivilegeCmp { - fn cmp_privilege(left: &OriginCaller, right: &OriginCaller) -> Option { - if left == right { - return Some(Ordering::Equal); - } - match (left, right) { - (OriginCaller::system(frame_system::RawOrigin::Root), _) => Some(Ordering::Greater), - _ => None, - } - } -} -parameter_types! { - pub MaximumSchedulerWeight: Weight = Perbill::from_percent(10) * calamari_runtime::MAXIMUM_BLOCK_WEIGHT; - pub const NoPreimagePostponement: Option = Some(10); -} -impl pallet_scheduler::Config for Test { - type RuntimeEvent = RuntimeEvent; - type RuntimeOrigin = RuntimeOrigin; - type PalletsOrigin = OriginCaller; - type RuntimeCall = RuntimeCall; - type MaximumWeight = MaximumSchedulerWeight; - type ScheduleOrigin = EnsureRoot; - type MaxScheduledPerBlock = ConstU32<50>; // 50 scheduled calls at most in the queue for a single block. - type WeightInfo = calamari_runtime::weights::pallet_scheduler::SubstrateWeight; - type OriginPrivilegeCmp = OriginPrivilegeCmp; - type Preimages = Preimage; -} - -pub struct IsRegistered; -impl ValidatorRegistration for IsRegistered { - fn is_registered(id: &u64) -> bool { - *id != 7u64 - } -} -impl ValidatorSet for IsRegistered { - type ValidatorId = u64; - type ValidatorIdOf = manta_collator_selection::IdentityCollator; - fn session_index() -> sp_staking::SessionIndex { - 1 - } - fn validators() -> Vec { - vec![] - } -} -parameter_types! { - pub const PotId: PalletId = PalletId(*b"PotStake"); -} -impl manta_collator_selection::Config for Test { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type UpdateOrigin = EnsureRoot; - type PotId = PotId; - type MaxCandidates = ConstU32<20>; - type MaxInvulnerables = ConstU32<20>; - type ValidatorId = ::AccountId; - type ValidatorIdOf = manta_collator_selection::IdentityCollator; - type AccountIdOf = manta_collator_selection::IdentityCollator; - type ValidatorRegistration = IsRegistered; - type WeightInfo = (); - type CanAuthor = (); -} - -parameter_types! { - /// Fixed percentage a collator takes off the top of due rewards - pub const DefaultCollatorCommission: Perbill = Perbill::from_percent(10); - /// Default percent of inflation set aside for parachain bond every round - pub const DefaultParachainBondReservePercent: Percent = Percent::zero(); - pub DefaultBlocksPerRound: BlockNumber = 15; - pub LeaveDelayRounds: BlockNumber = 1; // == 7 * DAYS / 6 * HOURS = 28 -} -impl pallet_parachain_staking::Config for Test { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type BlockAuthor = BlockAuthor; - type MonetaryGovernanceOrigin = EnsureRoot; - /// Minimum round length is 2 minutes (10 * 12 second block times) - type MinBlocksPerRound = ConstU32<10>; - /// Blocks per round - type DefaultBlocksPerRound = DefaultBlocksPerRound; - /// Rounds before the collator leaving the candidates request can be executed - type LeaveCandidatesDelay = LeaveDelayRounds; - /// Rounds before the candidate bond increase/decrease can be executed - type CandidateBondLessDelay = LeaveDelayRounds; - /// Rounds before the delegator exit can be executed - type LeaveDelegatorsDelay = LeaveDelayRounds; - /// Rounds before the delegator revocation can be executed - type RevokeDelegationDelay = LeaveDelayRounds; - /// Rounds before the delegator bond increase/decrease can be executed - type DelegationBondLessDelay = LeaveDelayRounds; - /// Rounds before the reward is paid - type RewardPaymentDelay = ConstU32<2>; - /// Minimum collators selected per round, default at genesis and minimum forever after - type MinSelectedCandidates = ConstU32<5>; - /// Maximum top delegations per candidate - type MaxTopDelegationsPerCandidate = ConstU32<100>; - /// Maximum bottom delegations per candidate - type MaxBottomDelegationsPerCandidate = ConstU32<50>; - /// Maximum delegations per delegator - type MaxDelegationsPerDelegator = ConstU32<25>; - type DefaultCollatorCommission = DefaultCollatorCommission; - type DefaultParachainBondReservePercent = DefaultParachainBondReservePercent; - /// Minimum stake on a collator to be considered for block production - type MinCollatorStk = - ConstU128<{ calamari_runtime::staking::MIN_BOND_TO_BE_CONSIDERED_COLLATOR }>; - /// Minimum stake the collator runner must bond to register as collator candidate - type MinCandidateStk = ConstU128<{ calamari_runtime::staking::NORMAL_COLLATOR_MINIMUM_STAKE }>; - /// WHITELIST: Minimum stake required for *a whitelisted* account to be a collator candidate - type MinWhitelistCandidateStk = - ConstU128<{ calamari_runtime::staking::EARLY_COLLATOR_MINIMUM_STAKE }>; - /// Smallest amount that can be delegated - type MinDelegation = ConstU128<{ 5_000 * KMA }>; - /// Minimum stake required to be reserved to be a delegator - type MinDelegatorStk = ConstU128<{ 5_000 * KMA }>; - type OnCollatorPayout = (); - type OnNewRound = (); - type WeightInfo = calamari_runtime::weights::pallet_parachain_staking::SubstrateWeight; // XXX: Maybe use the actual calamari weights? -} - -impl block_author::Config for Test {} - -use frame_support::PalletId; -use frame_system::EnsureRoot; -use manta_primitives::constants::LOTTERY_PALLET_ID; -parameter_types! { - pub const LotteryPotId: PalletId = LOTTERY_PALLET_ID; // ensure we don't deposit/withdraw in the drawing block - /// Time in blocks between lottery drawings - pub DrawingInterval: BlockNumber = DefaultBlocksPerRound::get(); - /// Time in blocks *before* a drawing in which modifications of the win-eligble pool are prevented - pub DrawingFreezeout: BlockNumber = 5; - /// Time in blocks until a collator is done unstaking - pub UnstakeLockTime: BlockNumber = LeaveDelayRounds::get() * DefaultBlocksPerRound::get(); -} - -use frame_support::traits::Currency; -pub type BalanceOf = <::Currency as Currency< - ::AccountId, ->>::Balance; -pub struct MockEstimateFee {} -impl frame_support::traits::EstimateCallFee, BalanceOf> - for MockEstimateFee -{ - fn estimate_call_fee( - _call: &pallet_parachain_staking::Call, - _post_info: frame_support::dispatch::PostDispatchInfo, - ) -> BalanceOf { - 7 * KMA - } -} -impl frame_support::traits::EstimateCallFee, BalanceOf> - for MockEstimateFee -{ - fn estimate_call_fee( - _call: &pallet::Call, - _post_info: frame_support::dispatch::PostDispatchInfo, - ) -> BalanceOf { - 3 * KMA - } -} -impl Config for Test { - type RuntimeCall = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type Scheduler = Scheduler; - type EstimateCallFee = MockEstimateFee; - type RandomnessSource = TestRandomness; - type ManageOrigin = frame_system::EnsureRoot; - type PalletsOrigin = OriginCaller; - type LotteryPot = LotteryPotId; - type DrawingInterval = DrawingInterval; - type DrawingFreezeout = DrawingFreezeout; - type UnstakeLockTime = UnstakeLockTime; - type WeightInfo = (); -} - -use frame_support::traits::{ValidatorRegistration, ValidatorSet}; - -pub(crate) struct ExtBuilder { - // endowed accounts with balances - balances: Vec<(AccountId, Balance)>, - // [collator, amount] - collators: Vec<(AccountId, Balance)>, - // [delegator, collator, delegation_amount] - delegations: Vec<(AccountId, AccountId, Balance)>, - // inflation config - inflation: InflationInfo, -} - -impl Default for ExtBuilder { - fn default() -> ExtBuilder { - ExtBuilder { - balances: vec![], - delegations: vec![], - collators: vec![], - inflation: InflationInfo { - expect: Range { - min: 700, - ideal: 700, - max: 700, - }, - // not used - annual: Range { - min: Perbill::from_percent(50), - ideal: Perbill::from_percent(50), - max: Perbill::from_percent(50), - }, - // unrealistically high parameterization, only for testing - round: Range { - min: Perbill::from_percent(5), - ideal: Perbill::from_percent(5), - max: Perbill::from_percent(5), - }, - }, - } - } -} - -impl ExtBuilder { - pub(crate) fn with_funded_lottery_account(mut self, balance: Balance) -> Self { - self.balances - .push((crate::Pallet::::account_id(), balance)); - self - } - - pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self { - self.balances = balances; - self - } - - pub(crate) fn with_candidates(mut self, collators: Vec<(AccountId, Balance)>) -> Self { - self.collators = collators; - self - } - - #[allow(dead_code)] - pub(crate) fn with_inflation(mut self, inflation: InflationInfo) -> Self { - self.inflation = inflation; - self - } - - pub(crate) fn build(self) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default() - .build_storage::() - .expect("Frame system builds valid default genesis config"); - - pallet_balances::GenesisConfig:: { - balances: self.balances, - } - .assimilate_storage(&mut t) - .expect("Pallet balances storage can be assimilated"); - pallet_parachain_staking::GenesisConfig:: { - candidates: self.collators, - delegations: self.delegations, - inflation_config: self.inflation, - } - .assimilate_storage(&mut t) - .expect("Parachain Staking's storage can be assimilated"); - pallet_lottery::GenesisConfig:: { - min_deposit: 5_000 * KMA, - min_withdraw: 5_000 * KMA, - gas_reserve: 10_000 * KMA, - } - .assimilate_storage(&mut t) - .expect("pallet_lottery's storage can be assimilated"); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext - } -} - -pub mod from_bench { - /// copied from frame benchmarking - use super::*; - use codec::{Decode, Encode}; - use frame_support::traits::Get; - use sp_io::hashing::blake2_256; - use sp_runtime::traits::TrailingZeroInput; - pub fn account(name: &'static str, index: u32, seed: u32) -> AccountId { - let entropy = (name, index, seed).using_encoded(blake2_256); - Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) - .expect("infinite length input; no invalid inputs for type; qed") - } - pub fn create_funded_user( - string: &'static str, - n: u32, - extra: BalanceOf, - ) -> (T::AccountId, BalanceOf) { - const SEED: u32 = 0; - let user = account(string, n, SEED); - let min_candidate_stk = - <::MinCandidateStk as Get>>::get(); - let total = min_candidate_stk + extra; - ::Currency::make_free_balance_be(&user, total); - ::Currency::issue(total); - (user, total) - } -} - -/// Rolls forward one block. Returns the new block number. -pub(crate) fn roll_one_block() -> u32 { - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - ParachainStaking::on_initialize(System::block_number()); - Scheduler::on_initialize(System::block_number()); - System::block_number() -} - -/// Rolls to the desired block. Returns the number of blocks played. -pub(crate) fn roll_to(n: u32) -> u32 { - let mut num_blocks = 0; - let mut block = System::block_number(); - while block < n { - block = roll_one_block(); - num_blocks += 1; - } - num_blocks -} - -/// Rolls block-by-block to the beginning of the specified round. -/// This will complete the block in which the round change occurs. -/// Returns the number of blocks played. -pub(crate) fn roll_to_round_begin(round: u32) -> u32 { - let block = (round - 1) * DefaultBlocksPerRound::get(); - roll_to(block) -} - -/// Rolls block-by-block to the end of the specified round. -/// The block following will be the one in which the specified round change occurs. -pub(crate) fn roll_to_round_end(round: u32) -> u32 { - let block = round * DefaultBlocksPerRound::get() - 1; - roll_to(block) -} - -pub(crate) fn last_event() -> RuntimeEvent { - System::events().pop().expect("Event expected").event -} - -/// Assert input equal to the last event emitted -#[macro_export] -macro_rules! assert_last_event { - ($event:expr) => { - match &$event { - e => assert_eq!(*e, $crate::mock::last_event()), - } - }; -} - -#[frame_support::pallet] -pub mod block_author { - use super::*; - use frame_support::{pallet_prelude::*, traits::Get}; - - #[pallet::config] - pub trait Config: frame_system::Config {} - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); - - #[pallet::storage] - #[pallet::getter(fn block_author)] - pub(super) type BlockAuthor = StorageValue<_, AccountId, ValueQuery>; - - impl Get for Pallet { - fn get() -> AccountId { - >::get() - } - } -} - -#[test] -fn roll_to_round_begin_works() { - ExtBuilder::default().build().execute_with(|| { - // these tests assume blocks-per-round of 15, as established by DefaultBlocksPerRound - assert_eq!(System::block_number(), 1); // we start on block 1 - - let num_blocks = roll_to_round_begin(1); - assert_eq!(System::block_number(), 1); // no-op, we're already on this round - assert_eq!(num_blocks, 0); - - let num_blocks = roll_to_round_begin(2); - assert_eq!(System::block_number(), 15); - assert_eq!(num_blocks, 14); - - let num_blocks = roll_to_round_begin(3); - assert_eq!(System::block_number(), 30); - assert_eq!(num_blocks, 15); - }); -} - -#[test] -fn roll_to_round_end_works() { - ExtBuilder::default().build().execute_with(|| { - // these tests assume blocks-per-round of 15, as established by DefaultBlocksPerRound - assert_eq!(System::block_number(), 1); // we start on block 1 - - let num_blocks = roll_to_round_end(1); - assert_eq!(System::block_number(), 14); - assert_eq!(num_blocks, 13); - - let num_blocks = roll_to_round_end(2); - assert_eq!(System::block_number(), 29); - assert_eq!(num_blocks, 15); - - let num_blocks = roll_to_round_end(3); - assert_eq!(System::block_number(), 44); - assert_eq!(num_blocks, 15); - }); -} diff --git a/pallets/pallet-lottery/src/rpc.rs b/pallets/pallet-lottery/src/rpc.rs deleted file mode 100644 index 8fa1bca59..000000000 --- a/pallets/pallet-lottery/src/rpc.rs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2020-2023 Manta Network. -// This file is part of Manta. -// -// Manta 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. -// -// Manta 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 Manta. If not, see . - -//! Lottery RPC Interfaces - -use crate::runtime::LotteryApi; -use core::marker::PhantomData; -use jsonrpsee::{ - core::{async_trait, RpcResult}, - proc_macros::rpc, - types::error::{CallError, ErrorObject}, -}; -use sp_api::ProvideRuntimeApi; -use sp_blockchain::HeaderBackend; -use sp_runtime::{generic::BlockId, traits::Block}; -use sp_std::sync::Arc; - -pub const LOTTERY_ERROR: i32 = 777; - -#[rpc(server)] -pub trait LotteryRpc { - #[method(name = "lottery_not_in_drawing_freezeout", blocking)] - fn not_in_drawing_freezeout(&self) -> RpcResult; - - #[method(name = "lottery_current_prize_pool", blocking)] - fn current_prize_pool(&self) -> RpcResult; - - #[method(name = "lottery_next_drawing_at", blocking)] - fn next_drawing_at(&self) -> RpcResult>; -} - -/// Lottery RPC API Implementation -pub struct Lottery { - /// Client - client: Arc, - - /// Type Parameter Marker - __: PhantomData, -} - -impl Lottery { - /// Builds a new [`Pull`] RPC API implementation. - #[inline] - pub fn new(client: codec::alloc::sync::Arc) -> Self { - Self { - client, - __: PhantomData, - } - } -} - -#[async_trait] -impl LotteryRpcServer for Lottery -where - B: Block, - C: 'static + ProvideRuntimeApi + HeaderBackend, - C::Api: LotteryApi, -{ - #[inline] - fn not_in_drawing_freezeout(&self) -> RpcResult { - let api = self.client.runtime_api(); - let at: BlockId<_> = BlockId::hash(self.client.info().best_hash); - api.not_in_drawing_freezeout(&at).map_err(|err| { - CallError::Custom(ErrorObject::owned( - LOTTERY_ERROR, - "Unable to compute drawing freezeout", - Some(format!("{err:?}")), - )) - .into() - }) - } - - #[inline] - fn current_prize_pool(&self) -> RpcResult { - let api = self.client.runtime_api(); - let at: BlockId<_> = BlockId::hash(self.client.info().best_hash); - api.current_prize_pool(&at).map_err(|err| { - CallError::Custom(ErrorObject::owned( - LOTTERY_ERROR, - "Unable to compute current prize pool", - Some(format!("{err:?}")), - )) - .into() - }) - } - - #[inline] - fn next_drawing_at(&self) -> RpcResult> { - let api = self.client.runtime_api(); - let at: BlockId<_> = BlockId::hash(self.client.info().best_hash); - api.next_drawing_at(&at).map_err(|err| { - CallError::Custom(ErrorObject::owned( - LOTTERY_ERROR, - "Unable to compute next drawing", - Some(format!("{err:?}")), - )) - .into() - }) - } -} diff --git a/pallets/pallet-lottery/src/runtime.rs b/pallets/pallet-lottery/src/runtime.rs deleted file mode 100644 index 8e685f224..000000000 --- a/pallets/pallet-lottery/src/runtime.rs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2020-2023 Manta Network. -// This file is part of Manta. -// -// Manta 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. -// -// Manta 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 Manta. If not, see . - -sp_api::decl_runtime_apis! { - pub trait LotteryApi { - fn not_in_drawing_freezeout() -> bool; - fn current_prize_pool() -> u128; - fn next_drawing_at() -> Option; - } -} diff --git a/pallets/pallet-lottery/src/staking/deposit_strategies.rs b/pallets/pallet-lottery/src/staking/deposit_strategies.rs deleted file mode 100644 index f117edd11..000000000 --- a/pallets/pallet-lottery/src/staking/deposit_strategies.rs +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright 2020-2023 Manta Network. -// This file is part of Manta. -// -// Manta 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. -// -// Manta 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 Manta. If not, see . - -use super::*; -#[cfg(not(feature = "runtime-benchmarks"))] -use frame_support::traits::Randomness; -use pallet_parachain_staking::BalanceOf; -use sp_runtime::{ - traits::{Saturating, Zero}, - Percent, -}; -use sp_std::{vec, vec::Vec}; - -#[named] -pub(super) fn reactivate_bottom_collators( - active_collators: &[T::AccountId], - new_deposit: BalanceOf, -) -> Vec<(T::AccountId, BalanceOf)> { - log::trace!(function_name!()); - - let mut deposits: Vec<(T::AccountId, BalanceOf)> = vec![]; - let mut remaining_deposit = new_deposit; - - // We only consider collators we're already staked to that are also currently active (and not being unstaked) - for collator in StakedCollators::::iter_keys().filter(|coll| active_collators.contains(coll)) - { - let staked = StakedCollators::::get(collator.clone()); - let info = pallet_parachain_staking::Pallet::::candidate_info(collator.clone()) - .expect("is active collator, therefore it has collator info. qed"); - if staked < info.lowest_top_delegation_amount { - // TODO: Small optimization: sort collators ascending by missing amount so we get the largest amount of collators active before running out of funds - let this_deposit = core::cmp::min( - remaining_deposit, - info.lowest_top_delegation_amount - staked + 1u32.into(), - ); - // Ensure we don't try to stake a smaller than allowed delegation to a collator - if remaining_deposit.saturating_sub(this_deposit) < crate::Pallet::::min_deposit() { - deposits.push((collator, remaining_deposit)); // put the full remaining balance in this collator - break; - } else { - deposits.push((collator, this_deposit)); // put just what's needed to get back into the top delegators - remaining_deposit = remaining_deposit.saturating_sub(this_deposit); - } - } - } - deposits -} - -/// second concern: We want to maximize staking APY earned, so we want to balance the staking pools with our deposits while conserving gas -pub(super) fn split_to_underallocated_collators( - active_collators: &[T::AccountId], - new_deposit: BalanceOf, -) -> Vec<(T::AccountId, BalanceOf)> { - let mut deposits: Vec<(T::AccountId, BalanceOf)> = vec![]; - let mut remaining_deposit = new_deposit; - - if active_collators.len().is_zero() || new_deposit.is_zero() { - return deposits; - } - // We only consider active collators for deposits - // TODO: Small optimization: Also consider points / pointsAwarded to not stake to collators missing blocks - let mut collators_and_counted_balances: Vec<_> = active_collators - .iter() - .cloned() - .map(|collator| { - ( - collator.clone(), - pallet_parachain_staking::Pallet::::candidate_info(collator) - .expect("is active collator, therefore it has collator info. qed") - .total_counted, - ) - }) - .collect(); - // sort ascending by counted stake - collators_and_counted_balances.sort_by(|a, b| a.1.cmp(&b.1)); - debug_assert!( - collators_and_counted_balances.len() == 1 - || pallet_parachain_staking::Pallet::::candidate_info( - collators_and_counted_balances[0].0.clone() - ) - .unwrap() - .total_counted - <= pallet_parachain_staking::Pallet::::candidate_info( - collators_and_counted_balances[1].0.clone() - ) - .unwrap() - .total_counted - ); - - let median_collator_balance = - collators_and_counted_balances[collators_and_counted_balances.len() / 2].1; - - // build collator => deviation from median map - let mut underallocated_collators: Vec<_> = - collators_and_counted_balances[..collators_and_counted_balances.len() / 2].to_vec(); - underallocated_collators = underallocated_collators - .into_iter() - .filter_map(|(collator, balance)| { - let underallocation = median_collator_balance.saturating_sub(balance); - if !underallocation.is_zero() { - Some((collator, underallocation)) - } else { - None - } - }) - .collect(); - // After this calculation, underallocated_collators is in descending order of underallocation - - // take up to 4 collators with the highest deficit ( stopping at median ) - let num_collators_to_take = core::cmp::min(4, underallocated_collators.len()); - underallocated_collators = underallocated_collators[..num_collators_to_take].to_vec(); - - debug_assert!( - underallocated_collators.is_empty() - || pallet_parachain_staking::Pallet::::candidate_info( - underallocated_collators[0].0.clone() - ) - .unwrap() - .total_counted - <= median_collator_balance - ); - debug_assert!( - underallocated_collators.len() < 2 - || pallet_parachain_staking::Pallet::::candidate_info( - underallocated_collators[0].0.clone() - ) - .unwrap() - .total_counted - <= pallet_parachain_staking::Pallet::::candidate_info( - underallocated_collators[1].0.clone() - ) - .unwrap() - .total_counted - ); - debug_assert!( - underallocated_collators.len() < 2 - || underallocated_collators[0].1 >= underallocated_collators[1].1 - ); - log::debug!( - "Total Underallocated collators: {:?}", - underallocated_collators.len() - ); - if !underallocated_collators.is_empty() { - let total_underallocation = underallocated_collators - .iter() - .cloned() - .map(|a| a.1) - .reduce(|acc, balance| acc + balance) - .expect("reduce returns None on empty iterator. we checked that `underallocated_collators` is not empty. qed"); - log::debug!( - "Underallocated tokens {:?} on selected collators: {:?}", - total_underallocation, - underallocated_collators - ); - for (account, tokens_to_reach_median) in underallocated_collators.clone() { - // If a proportional deposit is over the min deposit and can get us into the top balance, deposit it, if not just skip it - let info = pallet_parachain_staking::Pallet::::candidate_info(account.clone()) - .expect("is active collator, therefor it has collator info. qed"); - let collator_proportion = - Percent::from_rational(tokens_to_reach_median, total_underallocation); - let to_reach_mean = collator_proportion.mul_ceil(new_deposit); - let to_deposit = to_reach_mean.min(remaining_deposit); - let our_stake = StakedCollators::::get(account.clone()); - if to_deposit > crate::Pallet::::min_deposit() - && to_deposit + our_stake > info.lowest_top_delegation_amount - { - let this_deposit = core::cmp::min(to_deposit, remaining_deposit); - deposits.push((account.clone(), this_deposit)); - remaining_deposit -= this_deposit; - log::debug!( - "Selected collator {:?} for deposit of {:?} token", - account.clone(), - to_deposit - ); - }; - if remaining_deposit < crate::Pallet::::min_deposit() { - break; - } - } - } - // if we had to skip a collator above due to not getting into the top deposit or because the deposit was below minimum - // we just lump the rest into the collator with the lowest stake - if !remaining_deposit.is_zero() { - if !deposits.is_empty() { - let mut last_new_deposit = deposits.pop().expect( - "we checked that deposits is not empty, therefore pop will return Some. qed", - ); - last_new_deposit.1 += remaining_deposit; - remaining_deposit.set_zero(); - deposits.push(last_new_deposit); - } else if !underallocated_collators.is_empty() { - // i.e. no collator could be staked above but underallocated ones exist - let deposit = ( - underallocated_collators - .first() - .expect("underallocated_collators is not empty. qed") - .clone() - .0, - remaining_deposit, - ); - remaining_deposit.set_zero(); - deposits.push(deposit); - } - if !remaining_deposit.is_zero() { - log::error!("FATAL: Have {:?} tokens left over after depositing. active collators {:?}, underallocated collators {:?}, deposits {:?}", - remaining_deposit, - active_collators.len(), - underallocated_collators.len(), - deposits.len() - ); - } - } - deposits -} - -/// fallback: just assign to a random active collator ( choose a different collator for each invocation ) -pub(crate) fn stake_to_random_collator( - active_collators: &[T::AccountId], - new_deposit: BalanceOf, -) -> Option<(T::AccountId, BalanceOf)> { - use sp_runtime::traits::SaturatedConversion; - if active_collators.len().is_zero() || new_deposit.is_zero() { - return None; - } - - let block_number = >::block_number().saturated_into::(); - let extrinsic_index = >::extrinsic_index().unwrap_or_default(); - let nonce: u128 = block_number ^ extrinsic_index as u128; - let randomness_output: sp_core::U256; - #[cfg(feature = "runtime-benchmarks")] - { - use rand::{Rng, SeedableRng}; - let mut rng = rand::rngs::StdRng::seed_from_u64(nonce as u64); - randomness_output = rng.gen::().into(); - } - #[cfg(not(feature = "runtime-benchmarks"))] - { - randomness_output = sp_core::U256::from_big_endian( - T::RandomnessSource::random(&nonce.to_be_bytes()).0.as_ref(), - ); - } - // NOTE: The following line introduces modulo bias, but since this is just a fallback it is accepted - let random_index: usize = randomness_output.low_u64() as usize % active_collators.len(); - if let Some(random_collator) = active_collators.get(random_index) { - log::warn!( - "Staking {:?} randomly to {:?}", - new_deposit, - random_collator - ); - Some((random_collator.clone(), new_deposit)) - } else { - log::error!("Could not stake {:?} randomly", new_deposit); - None - } -} diff --git a/pallets/pallet-lottery/src/staking/mod.rs b/pallets/pallet-lottery/src/staking/mod.rs deleted file mode 100644 index d2a3a4371..000000000 --- a/pallets/pallet-lottery/src/staking/mod.rs +++ /dev/null @@ -1,354 +0,0 @@ -// Copyright 2020-2023 Manta Network. -// This file is part of Manta. -// -// Manta 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. -// -// Manta 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 Manta. If not, see . - -mod deposit_strategies; -mod withdraw_strategies; - -use super::*; -use frame_support::{dispatch::RawOrigin, ensure, traits::EstimateCallFee}; -use pallet_parachain_staking::BalanceOf; -use sp_runtime::{ - traits::{Saturating, Zero}, - DispatchResult, Percent, -}; -use sp_std::{vec, vec::Vec}; - -impl Pallet { - #[named] - /// distributes a given amount of tokens to zero or more collators for staking - /// if it can't distribute all tokens for some reason, it returns an empty vec - pub(crate) fn calculate_deposit_distribution( - new_deposit: BalanceOf, - ) -> Vec<(T::AccountId, BalanceOf)> { - log::trace!(function_name!()); - log::debug!( - "Calculating distribution for deposit of {:?} tokens", - new_deposit - ); - if new_deposit < Self::min_deposit() { - log::debug!( - "Requested deposit of {:?} is below limit for staking of {:?}. Skipping assignment", - new_deposit, - Self::min_deposit(), - ); - return vec![]; - } - let mut deposits: Vec<(T::AccountId, BalanceOf)> = vec![]; - let mut remaining_deposit = new_deposit; - - // Only deposit to active collators (according to ParachainStaking) that we are not currently undelegating from (re-delegating would fail) and that received some rewards in the last round - let top_collator_accounts = pallet_parachain_staking::Pallet::::selected_candidates(); - if top_collator_accounts.is_empty() { - log::error!("FATAL: ParachainStaking returned no active collators"); // NOTE: guaranteed by ParachainStaking to not happen - return vec![]; - } - let collators_we_are_unstaking_from = UnstakingCollators::::get() - .iter() - .cloned() - .map(|uc| uc.account) - .collect::>(); - - let round_info = pallet_parachain_staking::Pallet::::round(); - // NOTE: This is O(n^2) but both vecs are << 100 elements - let deposit_eligible_collators = top_collator_accounts - .iter() - .filter(|account| { - !collators_we_are_unstaking_from.contains(account) - && (round_info.current <= 1 - || !pallet_parachain_staking::Pallet::::awarded_pts( - round_info.current - 1, - account, - ) - .is_zero()) - }) - .cloned() - .collect::>(); - - // first concern: If we fell out of the active set on one or more collators, we need to get back into it - deposits.append(&mut deposit_strategies::reactivate_bottom_collators::( - deposit_eligible_collators.as_slice(), - new_deposit, - )); - // `reactivate_bottom_collators` has only distributed the funds needed for reactivation, we can have some left over - remaining_deposit -= deposits - .iter() - .map(|deposit| deposit.1) - .reduce(|sum, elem| sum + elem) - .unwrap_or_else(|| 0u32.into()); - - // If we have re-activated any collators and have leftover funds, we just distribute all surplus tokens to them evenly and call it a day - if !deposits.is_empty() { - if !remaining_deposit.is_zero() { - let deposit_per_collator = - Percent::from_rational(1, deposits.len()).mul_ceil(remaining_deposit); // this overshoots the amount if there's a remainder - for deposit in &mut deposits { - let add = remaining_deposit.saturating_sub(deposit_per_collator); // we correct the overshoot here - deposit.1 += add; - remaining_deposit -= add; - } - } - return deposits; - } - - // second concern: We want to maximize staking APY earned, so we want to balance the staking pools with our deposits while conserving gas - deposits.append( - &mut deposit_strategies::split_to_underallocated_collators::( - deposit_eligible_collators.as_slice(), - remaining_deposit, - ), - ); - remaining_deposit -= deposits - .iter() - .map(|deposit| deposit.1) - .reduce(|sum, elem| sum + elem) - .unwrap_or_else(|| 0u32.into()); - // fallback: just assign to a random active collator ( choose a different collator for each invocation ) - if !remaining_deposit.is_zero() { - log::warn!( - "Failed to distribute {:?} tokens by strategy", - remaining_deposit - ); - if let Some(deposit) = deposit_strategies::stake_to_random_collator::( - deposit_eligible_collators.as_slice(), - remaining_deposit, - ) { - deposits.push(deposit); - remaining_deposit = new_deposit - - deposits - .iter() - .map(|deposit| deposit.1) - .reduce(|sum, elem| sum + elem) - .unwrap_or_else(|| 0u32.into()); - } - } - if deposits.is_empty() { - log::error!("FATAL: Could not find any collator to stake to"); - } - log::debug!("Deposits: {:?}", deposits); - if !remaining_deposit.is_zero() { - log::error!( - "FATAL: We have {:?} unstaked balance left over after depositing, returning empty vec", - remaining_deposit - ); - deposits.clear(); - } - deposits - } - - #[named] - pub(crate) fn calculate_withdrawal_distribution( - withdrawal_amount: BalanceOf, - ) -> Vec { - log::trace!(function_name!()); - if withdrawal_amount.is_zero() { - return vec![]; - } - let mut withdrawals = vec![]; - let mut remaining_balance = withdrawal_amount; - - // Only unstake collators we're staked to **and not already unstaking from** - let staked_collators: Vec<_> = StakedCollators::::iter_keys().collect(); - let collators_we_are_unstaking_from: Vec<_> = UnstakingCollators::::get() - .iter() - .cloned() - .map(|uc| uc.account) - .collect(); - // NOTE: This is O(n^2) but both vecs are << 100 elements - let withdrawal_eligible_collators: Vec<_> = staked_collators - .iter() - .filter(|account| !collators_we_are_unstaking_from.contains(account)) - .cloned() - .collect(); - if withdrawal_eligible_collators.is_empty() { - return vec![]; - } - // first concern: If there are inactive collators we are staked with, prefer these - let (mut collators, balance_unstaked) = withdraw_strategies::unstake_inactive_collators::( - &withdrawal_eligible_collators, - remaining_balance, - ); - withdrawals.append(&mut collators); - remaining_balance = remaining_balance.saturating_sub(balance_unstaked); - if remaining_balance.is_zero() && !withdrawals.is_empty() { - return withdrawals; - } - // If we have balance to withdraw left over, we have to unstake some healthy collator. - // Unstake starting from the highest overallocated collator ( since that yields the lowest APY ) going down until request is satisfied - let (mut collators, balance_unstaked) = withdraw_strategies::unstake_least_apy_collators::( - &withdrawal_eligible_collators - .into_iter() - .filter(|collator| !withdrawals.contains(collator)) - .collect(), - remaining_balance, - ); - withdrawals.append(&mut collators); - remaining_balance = remaining_balance.saturating_sub(balance_unstaked); - - if !remaining_balance.is_zero() { - log::error!( - "FATAL: We have {:?} left that COULD NOT BE UNSTAKED", - remaining_balance - ); - return vec![]; // NOTE: Ensure we do not continue with the partial withdrawal - } - if withdrawals.is_empty() { - log::error!("COULD NOT SELECT ANY COLLATOR TO WITHDRAW FROM"); - } else { - log::debug!("Withdrawals: {:?}", withdrawals.len()); - } - withdrawals - } - - #[named] - pub(crate) fn do_stake_one_collator( - collator: T::AccountId, - amount: BalanceOf, - ) -> DispatchResult { - log::trace!(function_name!()); - // preconditions - // funds eligible for staking: - // - newly deposited funds > min_deposit - // - unstaked-but-not-needed-for-withdrawals funds > min_deposit - if amount < Self::min_deposit() { - return Err(Error::::DepositBelowMinAmount.into()); - } - if amount > Self::surplus_funds() && amount > Self::unlocked_unstaking_funds() { - // we can't handle this withdrawal from new deposits or unstaked funds - return Err(Error::::PotBalanceTooLowToStake.into()); - } - // collator exists - let candidate_delegation_count; - if let Some(info) = pallet_parachain_staking::Pallet::::candidate_info(&collator) { - candidate_delegation_count = info.delegation_count; - } else { - return Err(Error::::NoCollatorForStake.into()); - }; - let delegation_count = StakedCollators::::iter_keys().count() as u32; - - // If we're already delegated to this collator, we must call `delegate_more` - if StakedCollators::::get(&collator).is_zero() { - // Ensure the pallet has enough gas to pay for this - let fee_estimate: BalanceOf = T::EstimateCallFee::estimate_call_fee( - &pallet_parachain_staking::Call::delegate { - candidate: collator.clone(), - amount, - candidate_delegation_count: candidate_delegation_count + 1, - delegation_count: delegation_count + 1, - }, - None::.into(), - ); - ensure!( - Self::surplus_funds() > fee_estimate, - Error::::PotBalanceTooLowToPayTxFee - ); - pallet_parachain_staking::Pallet::::delegate( - RawOrigin::Signed(Self::account_id()).into(), - collator.clone(), - amount, - candidate_delegation_count + 1, - delegation_count + 1, - ) - .map_err(|e| { - log::error!( - "Could not delegate {:?} to collator {:?} with error {:?}", - amount.clone(), - collator.clone(), - e - ); - e.error - })?; - } else { - // Ensure the pallet has enough gas to pay for this - let fee_estimate: BalanceOf = T::EstimateCallFee::estimate_call_fee( - &pallet_parachain_staking::Call::delegator_bond_more { - candidate: collator.clone(), - more: amount, - }, - None::.into(), - ); - ensure!( - Self::surplus_funds() > fee_estimate, - Error::::PotBalanceTooLowToPayTxFee - ); - pallet_parachain_staking::Pallet::::delegator_bond_more( - RawOrigin::Signed(Self::account_id()).into(), - collator.clone(), - amount, - ) - .map_err(|e| { - log::error!( - "Could not bond more {:?} to collator {:?} with error {:?}", - amount.clone(), - collator.clone(), - e - ); - e.error - })?; - } - StakedCollators::::mutate(&collator, |balance| *balance += amount); - - log::debug!("Delegated {:?} tokens to {:?}", amount, collator); - Ok(()) - } - - #[named] - pub(crate) fn do_unstake_collator( - now: T::BlockNumber, - some_collator: T::AccountId, - ) -> DispatchResult { - log::trace!(function_name!()); - let delegated_amount_to_be_unstaked = StakedCollators::::get(some_collator.clone()); - if delegated_amount_to_be_unstaked.is_zero() { - log::error!("requested to unstake a collator that isn't staked"); - return Err(Error::::NoCollatorForWithdrawal.into()); - }; - log::debug!( - "Unstaking collator {:?} with balance {:?}", - some_collator, - delegated_amount_to_be_unstaked.clone() - ); - // Ensure the pallet has enough gas to pay for this - let fee_estimate: BalanceOf = T::EstimateCallFee::estimate_call_fee( - &pallet_parachain_staking::Call::schedule_revoke_delegation { - collator: some_collator.clone(), - }, - None::.into(), - ); - ensure!( - Self::surplus_funds() > fee_estimate, - Error::::PotBalanceTooLowToPayTxFee - ); - // unstake from parachain staking - // NOTE: All funds that were delegated here will no longer produce staking rewards - pallet_parachain_staking::Pallet::::schedule_revoke_delegation( - RawOrigin::Signed(Self::account_id()).into(), - some_collator.clone(), - ) - .map_err(|e| e.error)?; - - // Update bookkeeping - SurplusUnstakingBalance::::mutate(|bal| { - *bal = (*bal).saturating_add(delegated_amount_to_be_unstaked); - }); - UnstakingCollators::::mutate(|collators| { - collators.push(UnstakingCollator { - account: some_collator.clone(), - since: now, - }) - }); - Ok(()) - } -} diff --git a/pallets/pallet-lottery/src/staking/withdraw_strategies.rs b/pallets/pallet-lottery/src/staking/withdraw_strategies.rs deleted file mode 100644 index ae43001b0..000000000 --- a/pallets/pallet-lottery/src/staking/withdraw_strategies.rs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2020-2023 Manta Network. -// This file is part of Manta. -// -// Manta 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. -// -// Manta 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 Manta. If not, see . - -use super::*; -use pallet_parachain_staking::BalanceOf; -use sp_runtime::traits::{Saturating, Zero}; -use sp_std::{vec, vec::Vec}; - -pub(super) fn unstake_inactive_collators( - eligible_collators: &Vec, - withdrawal_amount: BalanceOf, -) -> (Vec, BalanceOf) { - let mut withdrawals = vec![]; - let mut unstaked = 0u32.into(); - - if eligible_collators.len().is_zero() || withdrawal_amount.is_zero() { - return (withdrawals, 0u32.into()); - } - // first concern: If there are inactive collators we are staked with, prefer these - let round_info = pallet_parachain_staking::Pallet::::round(); - let selected = pallet_parachain_staking::Pallet::::selected_candidates(); - let inactive_eligible_collators = eligible_collators.iter().filter( - |collator|{ - // no longer selected for block rewards - !selected.contains(collator) || - // did not receive any points last round unless this is the first round - (round_info.current > 1 && pallet_parachain_staking::Pallet::::awarded_pts(round_info.current-1,collator).is_zero()) - }); - // since these collators are inactive, we just unstake in any order until we have satisfied the withdrawal request - for collator in inactive_eligible_collators { - let our_stake = StakedCollators::::get(collator); - log::debug!("Unstaking {:?} from inactive {:?}", our_stake, collator); - unstaked += our_stake; - withdrawals.push(collator.clone()); - if unstaked >= withdrawal_amount { - return (withdrawals, unstaked); - } - } - log::debug!( - "Remaining after inactive: {:?}", - withdrawal_amount.saturating_sub(unstaked) - ); - (withdrawals, unstaked) -} - -pub(super) fn unstake_least_apy_collators( - eligible_collators: &Vec, - withdrawal_amount: BalanceOf, -) -> (Vec, BalanceOf) { - // If we have balance to withdraw left over, we have to unstake some healthy collator. - // Unstake starting from the highest overallocated collator ( since that yields the lowest APY ) going down until request is satisfied - let mut withdrawals = vec![]; - let mut unstaked = 0u32.into(); - - if eligible_collators.len().is_zero() || withdrawal_amount.is_zero() { - return (withdrawals, unstaked); - } - - let selected = pallet_parachain_staking::Pallet::::selected_candidates(); - let mut apy_ordered_active_collators_we_are_staked_with: Vec<_> = eligible_collators - .iter() - .filter(|collator| selected.contains(collator)) - .cloned() - .collect(); - apy_ordered_active_collators_we_are_staked_with.sort_by(|a, b| { - let ainfo = pallet_parachain_staking::Pallet::::candidate_info(a.clone()) - .expect("is a selected collator, therefore it has collator info. qed"); - let binfo = pallet_parachain_staking::Pallet::::candidate_info(b.clone()) - .expect("is a selected collator, therefore it has collator info. qed"); - binfo.total_counted.cmp(&ainfo.total_counted) - }); - log::debug!( - "Active collators: {:?}", - apy_ordered_active_collators_we_are_staked_with.len() - ); - for c in apy_ordered_active_collators_we_are_staked_with { - let our_stake = StakedCollators::::get(c.clone()); - log::debug!("Unstaking {:?} from active {:?}", our_stake, c); - withdrawals.push(c); - unstaked += our_stake; - if unstaked >= withdrawal_amount { - break; - } - } - (withdrawals, unstaked) -} diff --git a/pallets/pallet-lottery/src/tests.rs b/pallets/pallet-lottery/src/tests.rs deleted file mode 100644 index 39851a098..000000000 --- a/pallets/pallet-lottery/src/tests.rs +++ /dev/null @@ -1,744 +0,0 @@ -// Copyright 2020-2023 Manta Network. -// This file is part of Manta. -// -// Manta 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. -// -// Manta 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 Manta. If not, see . - -use crate::{ - assert_last_event, - mock::{ - roll_one_block, roll_to, roll_to_round_begin, roll_to_round_end, AccountId, Balance, - Balances, ExtBuilder, Lottery, ParachainStaking, RuntimeOrigin as Origin, System, Test, - }, - Config, Error, -}; - -use frame_support::{assert_noop, assert_ok, traits::Currency}; -use frame_system::RawOrigin; - -lazy_static::lazy_static! { - pub(crate) static ref ALICE: AccountId = 1; - pub(crate) static ref BOB: AccountId = 2; - pub(crate) static ref CHARLIE: AccountId =3; - pub(crate) static ref DAVE: AccountId =4; - pub(crate) static ref EVE: AccountId =5; -} - -const UNIT: Balance = 1_000_000_000_000; -const HIGH_BALANCE: Balance = 1_000_000_000 * UNIT; - -#[test] -fn call_manager_extrinsics_as_normal_user_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - Lottery::start_lottery(Origin::signed(1)), // Somebody who is not T::ManageOrigin - sp_runtime::DispatchError::BadOrigin - ); - assert_noop!( - Lottery::stop_lottery(Origin::signed(1)), // Somebody who is not T::ManageOrigin - sp_runtime::DispatchError::BadOrigin - ); - assert_noop!( - Lottery::draw_lottery(Origin::signed(1)), // Somebody who is not T::ManageOrigin - sp_runtime::DispatchError::BadOrigin - ); - assert_noop!( - Lottery::process_matured_withdrawals(Origin::signed(1)), // Somebody who is not T::ManageOrigin - sp_runtime::DispatchError::BadOrigin - ); - assert_noop!( - Lottery::liquidate_lottery(Origin::signed(1)), // Somebody who is not T::ManageOrigin - sp_runtime::DispatchError::BadOrigin - ); - assert_noop!( - Lottery::rebalance_stake(Origin::signed(1)), // Somebody who is not T::ManageOrigin - sp_runtime::DispatchError::BadOrigin - ); - }); -} -#[test] -fn starting_lottery_without_gas_should_not_work() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - Lottery::start_lottery(RawOrigin::Root.into()), - Error::::PotBalanceBelowGasReserve - ); - }); -} -#[test] -fn starting_funded_lottery_should_work() { - ExtBuilder::default() - .with_funded_lottery_account(HIGH_BALANCE) - .build() - .execute_with(|| { - assert_ok!(Lottery::start_lottery(RawOrigin::Root.into())); - assert_last_event!(crate::mock::RuntimeEvent::Lottery( - crate::Event::LotteryStarted - )); - assert_noop!( - Lottery::start_lottery(RawOrigin::Root.into()), - Error::::LotteryIsRunning - ); // Ensure doublestarting fails - }); -} -#[test] -fn restarting_funded_lottery_should_work() { - ExtBuilder::default() - .with_funded_lottery_account(HIGH_BALANCE) - .build() - .execute_with(|| { - assert_ok!(Lottery::start_lottery(RawOrigin::Root.into())); - assert_ok!(Lottery::stop_lottery(RawOrigin::Root.into())); - assert_last_event!(crate::mock::RuntimeEvent::Lottery( - crate::Event::LotteryStopped - )); - assert_noop!( - Lottery::stop_lottery(RawOrigin::Root.into()), - Error::::LotteryNotStarted - ); // Ensure doublestopping fails - assert_ok!(Lottery::start_lottery(RawOrigin::Root.into())); - assert_last_event!(crate::mock::RuntimeEvent::Lottery( - crate::Event::LotteryStarted - )); - }); -} - -#[test] -fn depositing_and_withdrawing_in_freezeout_should_not_work() { - let balance = 300_000_000 * UNIT; - ExtBuilder::default() - .with_balances(vec![(*ALICE, HIGH_BALANCE), (*BOB, HIGH_BALANCE)]) - .with_candidates(vec![(*BOB, balance)]) - .with_funded_lottery_account(HIGH_BALANCE) - .build() - .execute_with(|| { - assert!(HIGH_BALANCE > balance); - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance)); - assert_eq!(Lottery::sum_of_deposits(), balance); - assert_eq!(Lottery::total_pot(), balance); - assert_ok!(Lottery::start_lottery(RawOrigin::Root.into(),)); - assert!(Lottery::not_in_drawing_freezeout()); - roll_to( - Lottery::next_drawing_at().unwrap() - ::DrawingFreezeout::get(), - ); - assert!(!Lottery::not_in_drawing_freezeout()); - assert_noop!( - Lottery::deposit(Origin::signed(*ALICE), balance), - Error::::TooCloseToDrawing - ); - assert_noop!( - Lottery::request_withdraw(Origin::signed(*ALICE), balance), - Error::::TooCloseToDrawing - ); - assert_eq!(Lottery::sum_of_deposits(), balance); - assert_eq!(Lottery::total_pot(), balance); - }); -} -#[test] -fn depositing_and_withdrawing_should_work() { - let balance = 500_000_000 * UNIT; - ExtBuilder::default() - .with_balances(vec![(*ALICE, HIGH_BALANCE), (*BOB, HIGH_BALANCE)]) - .with_candidates(vec![(*BOB, balance)]) - .with_funded_lottery_account(HIGH_BALANCE) - .build() - .execute_with(|| { - assert!(HIGH_BALANCE > balance); - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance)); - assert_last_event!(crate::mock::RuntimeEvent::Lottery( - crate::Event::Deposited { - account: *ALICE, - amount: balance - } - )); - assert_eq!(Lottery::active_balance_per_user(*ALICE), balance); - assert_eq!(Lottery::sum_of_deposits(), balance); - assert_eq!(Lottery::total_pot(), balance); - assert_ok!(Lottery::request_withdraw(Origin::signed(*ALICE), balance)); - assert_last_event!(crate::mock::RuntimeEvent::Lottery( - crate::Event::ScheduledWithdraw { - account: *ALICE, - amount: balance - } - )); - assert_eq!(Lottery::sum_of_deposits(), balance); - assert_eq!(Lottery::active_balance_per_user(*ALICE), 0); - assert_eq!(Lottery::total_pot(), 0); - }); -} -#[test] -fn depositing_and_withdrawing_leaves_correct_balance_with_user() { - let balance = 500_000_000 * UNIT; - ExtBuilder::default() - .with_balances(vec![(*ALICE, HIGH_BALANCE), (*BOB, HIGH_BALANCE)]) - .with_candidates(vec![(*BOB, balance)]) - .with_funded_lottery_account(HIGH_BALANCE) - .build() - .execute_with(|| { - assert!(HIGH_BALANCE > balance); - let alice_starting_balance = Balances::free_balance(*ALICE); - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance)); - assert!(Balances::free_balance(*ALICE) <= alice_starting_balance - balance); - assert_eq!(Lottery::sum_of_deposits(), balance); - assert_eq!(Lottery::total_pot(), balance); - assert_ok!(Lottery::request_withdraw(Origin::signed(*ALICE), balance)); - assert_eq!(Lottery::sum_of_deposits(), balance); - assert_eq!(Lottery::total_pot(), 0); - let alice_balance_after_request = Balances::free_balance(*ALICE); - assert!(alice_balance_after_request <= alice_starting_balance - balance); - roll_to_round_begin(3); - assert_ok!(Lottery::process_matured_withdrawals(RawOrigin::Root.into())); - assert_eq!(Lottery::sum_of_deposits(), 0); - assert_eq!( - Balances::free_balance(*ALICE), - alice_balance_after_request + balance - ); - }); -} -#[test] -fn double_processing_withdrawals_does_not_double_pay() { - let balance = 500_000_000 * UNIT; - ExtBuilder::default() - .with_balances(vec![(*ALICE, HIGH_BALANCE), (*BOB, HIGH_BALANCE)]) - .with_candidates(vec![(*BOB, balance)]) - .with_funded_lottery_account(HIGH_BALANCE) - .build() - .execute_with(|| { - assert!(HIGH_BALANCE > balance); - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance)); - assert_ok!(Lottery::request_withdraw(Origin::signed(*ALICE), balance)); - let alice_balance_after_request = Balances::free_balance(*ALICE); - roll_to_round_begin(3); - assert_ok!(Lottery::process_matured_withdrawals(RawOrigin::Root.into())); - assert_ok!(Lottery::process_matured_withdrawals(RawOrigin::Root.into())); - assert_eq!( - Balances::free_balance(*ALICE), - alice_balance_after_request + balance - ); - }); -} -#[test] -fn staking_to_one_underallocated_collator_works() { - let balance4 = 40_000_000 * UNIT; - let balance5 = 50_000_000 * UNIT; - let balance6 = 60_000_000 * UNIT; - ExtBuilder::default() - .with_balances(vec![ - (*ALICE, HIGH_BALANCE), - (*BOB, HIGH_BALANCE), - (*CHARLIE, HIGH_BALANCE), - ]) - .with_candidates(vec![ - (*ALICE, balance4), - (*BOB, balance5), - (*CHARLIE, balance6), - ]) - .build() - .execute_with(|| { - assert!(HIGH_BALANCE > balance6 + balance5 + balance4); - assert_eq!( - ParachainStaking::candidate_info(*ALICE) - .unwrap() - .total_counted, - balance4 - ); - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance6)); - // Median = 50k, ALICE is the only underallocated, gets all tokend - assert_eq!( - ParachainStaking::candidate_info(*ALICE) - .unwrap() - .total_counted, - balance4 + balance6 - ); - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance5)); - // Median = 60k, BOB is the only underallocated, gets all token - assert_eq!( - ParachainStaking::candidate_info(*BOB) - .unwrap() - .total_counted, - balance5 + balance5 - ); - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance4)); - // Median = 100k CHARLIE is the only underallocated, gets all token - assert_eq!( - ParachainStaking::candidate_info(*CHARLIE) - .unwrap() - .total_counted, - balance6 + balance4 - ); - // Now all 3 tie at 100k, there is no underallocation, deposit is given randomly - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance6)); - assert!( - (ParachainStaking::candidate_info(*ALICE) - .unwrap() - .total_counted - == balance4 + balance6 + balance6) - || (ParachainStaking::candidate_info(*BOB) - .unwrap() - .total_counted - == balance5 + balance5 + balance6) - || (ParachainStaking::candidate_info(*CHARLIE) - .unwrap() - .total_counted - == balance6 + balance4 + balance6), - ); - }); -} - -#[test] -fn unstaking_works_with_0_collators_left() { - let balance = 50_000_000 * UNIT; - ExtBuilder::default() - .with_balances(vec![(*ALICE, HIGH_BALANCE), (*BOB, HIGH_BALANCE)]) - .with_candidates(vec![(*ALICE, balance), (*BOB, balance)]) - .with_funded_lottery_account(HIGH_BALANCE) - .build() - .execute_with(|| { - assert!(HIGH_BALANCE > balance); - assert_eq!( - ParachainStaking::candidate_info(*ALICE) - .unwrap() - .total_counted, - balance - ); - assert_eq!(crate::StakedCollators::::iter().count(), 0); - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance)); - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance)); - assert_eq!(crate::StakedCollators::::iter().count(), 2); - assert_eq!(Balances::free_balance(*ALICE), HIGH_BALANCE - 2 * balance); - assert_eq!( - ParachainStaking::candidate_info(*ALICE) - .unwrap() - .total_counted, - balance * 2 - ); - assert_eq!( - ParachainStaking::candidate_info(*BOB) - .unwrap() - .total_counted, - balance * 2 - ); - assert_ok!(Lottery::request_withdraw( - Origin::signed(*ALICE), - balance * 2 - )); - assert_eq!(crate::StakedCollators::::iter().count(), 2); - assert_eq!(Lottery::withdrawal_request_queue().len(), 1); - assert_ok!(Lottery::start_lottery(RawOrigin::Root.into())); - roll_to_round_begin(3); - // by now the withdrawal should have happened by way of lottery drawing - assert_eq!(crate::StakedCollators::::iter().count(), 0); - assert_eq!(Balances::free_balance(*ALICE), HIGH_BALANCE); - }); -} - -#[test] -fn winner_distribution_should_be_equal_with_equal_deposits() { - let balance = 500_000_000 * UNIT; - ExtBuilder::default() - .with_balances(vec![ - (*ALICE, HIGH_BALANCE), - (*BOB, HIGH_BALANCE), - ]) - .with_candidates(vec![(*BOB, balance)]) - .build() - .execute_with(|| { - assert!(HIGH_BALANCE > balance); - ::Currency::make_free_balance_be(&Lottery::account_id(), Lottery::gas_reserve()); - // Deposit 50 users with equal deposits - const WINNING_AMT: u32 = 1; - const NUMBER_OF_DRAWINGS: u32 = 10_000; - const NUMBER_OF_USERS: u32 = 50; - const USER_SEED: u32 = 696_969; - let min_delegator_bond = - <::MinDelegatorStk as frame_support::traits::Get>>::get(); - let deposit_amount: pallet_parachain_staking::BalanceOf = min_delegator_bond * 10_000u128; - for user in 0..NUMBER_OF_USERS { - System::set_block_number(user); - let (depositor, _) = - crate::mock::from_bench::create_funded_user::("depositor", USER_SEED - 1 - user, deposit_amount); - assert_ok!(Lottery::deposit( - RawOrigin::Signed(depositor).into(), - deposit_amount - )); - } - // loop 10000 times, starting from block 5000 to hopefully be outside of drawing freezeout - for x in 5_000..5_000+NUMBER_OF_DRAWINGS { - // advance block number to reseed RNG - System::set_block_number(NUMBER_OF_USERS + x); - // simulate accrued staking rewards - assert_ok!(Balances::mutate_account( - &Lottery::account_id(), - |acc| { - acc.free = acc.free.saturating_add(WINNING_AMT.into()); - } - )); - // draw lottery - assert_ok!(Lottery::draw_lottery(RawOrigin::Root.into())); - } - assert_eq!( - Lottery::total_unclaimed_winnings(), - NUMBER_OF_DRAWINGS as u128 * WINNING_AMT as u128 - ); - - // ensure every user has won > 190 times ( 200 optimum = NUMBER_OF_DRAWINGS/NUMBER_OF_USERS ) - let mut winners = vec![]; - for (user, user_winnings) in crate::UnclaimedWinningsByAccount::::iter() { - winners.push((user,user_winnings)); - assert!(user_winnings >= (WINNING_AMT as f32 * NUMBER_OF_DRAWINGS as f32 / NUMBER_OF_USERS as f32 * 0.80) as u128); - } - log::error!("{:?}",winners); - assert_eq!( - winners.len() as u32, - NUMBER_OF_USERS - ); - }); -} - -#[test] -fn depsiting_to_new_collator_multiple_times_in_the_same_block_should_work() { - let balance = 50_000_000 * UNIT; - ExtBuilder::default() - .with_balances(vec![(*ALICE, HIGH_BALANCE), (*BOB, HIGH_BALANCE)]) - .with_candidates(vec![(*BOB, balance)]) - .with_funded_lottery_account(HIGH_BALANCE) - .build() - .execute_with(|| { - assert!(HIGH_BALANCE > balance); - assert_eq!(0, Lottery::staked_collators(*BOB)); - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance)); - assert_eq!(balance, Lottery::staked_collators(*BOB)); - assert_last_event!(crate::mock::RuntimeEvent::Lottery( - crate::Event::Deposited { - account: *ALICE, - amount: balance - } - )); - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance)); - assert_eq!(2 * balance, Lottery::staked_collators(*BOB)); - assert_last_event!(crate::mock::RuntimeEvent::Lottery( - crate::Event::Deposited { - account: *ALICE, - amount: balance - } - )); - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance)); - assert_eq!(3 * balance, Lottery::staked_collators(*BOB)); - assert_last_event!(crate::mock::RuntimeEvent::Lottery( - crate::Event::Deposited { - account: *ALICE, - amount: balance - } - )); - assert_eq!(Lottery::sum_of_deposits(), 3 * balance); - }); -} - -#[test] -fn deposit_withdraw_deposit_works() { - let balance = 50_000_000 * UNIT; - ExtBuilder::default() - .with_balances(vec![(*ALICE, HIGH_BALANCE), (*BOB, HIGH_BALANCE)]) - .with_candidates(vec![(*BOB, balance)]) - .with_funded_lottery_account(HIGH_BALANCE) - .build() - .execute_with(|| { - assert!(HIGH_BALANCE > balance); - assert_eq!(0, Lottery::staked_collators(*BOB)); - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance)); - assert_eq!(balance, Lottery::staked_collators(*BOB)); - assert_last_event!(crate::mock::RuntimeEvent::Lottery( - crate::Event::Deposited { - account: *ALICE, - amount: balance - } - )); - assert_ok!(Lottery::request_withdraw(Origin::signed(*ALICE), balance)); - assert_eq!(balance, Lottery::staked_collators(*BOB)); - assert_eq!(1, Lottery::withdrawal_request_queue().len()); - // join a new collator because BOB is now ineligible to receive deposits - let (new_collator, _) = crate::mock::from_bench::create_funded_user::( - "collator", - 0xDEADBEEF, - HIGH_BALANCE, - ); - assert_ok!(ParachainStaking::join_candidates( - Origin::signed(new_collator), - balance, - 10 - )); - assert_eq!(2, ParachainStaking::candidate_pool().len()); - roll_to_round_begin(2); - assert_eq!(new_collator, ParachainStaking::selected_candidates()[1]); - // pretend the collator got some rewards - pallet_parachain_staking::AwardedPts::::insert(1, new_collator, 20); - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance)); - assert_eq!(balance, Lottery::staked_collators(new_collator)); - }); -} - -#[test] -fn withdraw_partial_deposit_works() { - let balance = 500_000_000 * UNIT; - let half_balance = 250_000_000 * UNIT; - let quarter_balance = 125_000_000 * UNIT; - ExtBuilder::default() - .with_balances(vec![(*ALICE, HIGH_BALANCE), (*BOB, HIGH_BALANCE)]) - .with_candidates(vec![(*BOB, balance)]) - .with_funded_lottery_account(balance) - .build() - .execute_with(|| { - assert!(HIGH_BALANCE > balance); - assert_eq!(0, Lottery::staked_collators(*BOB)); - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance)); - let alice_post_deposit_balance = Balances::free_balance(*ALICE); - assert_eq!(balance, Lottery::staked_collators(*BOB)); - assert_eq!(balance, Lottery::total_pot()); - assert_eq!(balance, Lottery::sum_of_deposits()); - assert_ok!(Lottery::request_withdraw( - Origin::signed(*ALICE), - half_balance - )); - roll_one_block(); - assert_ok!(Lottery::request_withdraw( - Origin::signed(*ALICE), - quarter_balance - )); - assert_eq!(balance, Lottery::staked_collators(*BOB)); - assert_eq!(quarter_balance, Lottery::surplus_unstaking_balance()); - pallet_parachain_staking::AwardedPts::::insert(2, *BOB, 20); - roll_to_round_begin(3); - // funds should be unlocked now and BOB is finished unstaking, so it's eligible for redepositing - assert_ok!(Lottery::draw_lottery(RawOrigin::Root.into())); - assert_eq!( - alice_post_deposit_balance + half_balance + quarter_balance, - Balances::free_balance(*ALICE) - ); - assert_eq!(quarter_balance, Lottery::staked_collators(*BOB)); - assert_eq!(0, Lottery::surplus_unstaking_balance()); - assert_eq!(0, Lottery::unlocked_unstaking_funds()); - assert!(Lottery::withdrawal_request_queue().is_empty()); - assert!(crate::UnstakingCollators::::get().is_empty()); - }); -} - -#[test] -fn multiround_withdraw_partial_deposit_works() { - let balance = 500_000_000 * UNIT; - let half_balance = 250_000_000 * UNIT; - let quarter_balance = 125_000_000 * UNIT; - ExtBuilder::default() - .with_balances(vec![(*ALICE, HIGH_BALANCE), (*BOB, HIGH_BALANCE)]) - .with_candidates(vec![(*BOB, balance)]) - .with_funded_lottery_account(balance) - .build() - .execute_with(|| { - assert!(HIGH_BALANCE > balance); - // one round to unstake - assert!( - ::LeaveCandidatesDelay::get() == 1u32 - ); - assert_eq!(0, Lottery::staked_collators(*BOB)); - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance)); - let alice_post_deposit_balance = Balances::free_balance(*ALICE); - assert_eq!(balance, Lottery::staked_collators(*BOB)); - assert_eq!(balance, Lottery::total_pot()); - assert_eq!(balance, Lottery::sum_of_deposits()); - roll_one_block(); // ensure this unlocks *after* round 3 start - assert_ok!(Lottery::request_withdraw( - Origin::signed(*ALICE), - half_balance - )); - assert_eq!(balance, Lottery::staked_collators(*BOB)); - assert_eq!(1, Lottery::withdrawal_request_queue().len()); - assert_eq!(half_balance, Lottery::surplus_unstaking_balance()); - - // withdrawing funds are still locked - roll_to_round_begin(2); - roll_one_block(); // ensure this unlocks *after* round 3 start - pallet_parachain_staking::AwardedPts::::insert(1, *BOB, 20); - assert_ok!(Lottery::request_withdraw( - Origin::signed(*ALICE), - quarter_balance - )); - assert_ok!(Lottery::draw_lottery(RawOrigin::Root.into())); - assert_eq!(balance, Lottery::staked_collators(*BOB)); - assert_eq!( - half_balance - quarter_balance, - Lottery::surplus_unstaking_balance() - ); - assert_eq!(0, Lottery::unlocked_unstaking_funds()); - - // collator becomes unstaked on draw_lottery, must keep quarter for withdrawal, can restake other quarter - roll_to_round_begin(3); - pallet_parachain_staking::AwardedPts::::insert(2, *BOB, 20); - assert_ok!(Lottery::draw_lottery(RawOrigin::Root.into())); - assert_eq!(quarter_balance, Lottery::staked_collators(*BOB)); - assert_eq!(quarter_balance, Lottery::unlocked_unstaking_funds()); - assert_eq!(0, Lottery::surplus_unstaking_balance()); - assert_eq!( - alice_post_deposit_balance + half_balance, - Balances::free_balance(*ALICE) - ); - assert_eq!(1, Lottery::withdrawal_request_queue().len()); - assert!(crate::UnstakingCollators::::get().is_empty()); - - roll_to_round_begin(4); - pallet_parachain_staking::AwardedPts::::insert(3, *BOB, 20); - // second withdrawal can be paid out - assert_ok!(Lottery::draw_lottery(RawOrigin::Root.into())); - assert_eq!( - alice_post_deposit_balance + half_balance + quarter_balance, - Balances::free_balance(*ALICE) - ); - assert_eq!(quarter_balance, Lottery::staked_collators(*BOB)); - assert_eq!(0, Lottery::surplus_unstaking_balance()); - assert_eq!(0, Lottery::unlocked_unstaking_funds()); - assert!(Lottery::withdrawal_request_queue().is_empty()); - }); -} - -#[test] -fn multiround_withdraw_partial_deposit_works2() { - let reserve = 10_000 * UNIT; - let balance = 500_000_000 * UNIT; - let quarter_balance = 125_000_000 * UNIT; - ExtBuilder::default() - .with_balances(vec![ - (*ALICE, HIGH_BALANCE), - (*BOB, HIGH_BALANCE), - (*CHARLIE, HIGH_BALANCE), - ]) - .with_candidates(vec![(*BOB, balance), (*CHARLIE, balance)]) - .with_funded_lottery_account(reserve) // minimally fund lottery - .build() - .execute_with(|| { - assert_eq!(reserve, Lottery::gas_reserve()); // XXX: Cant use getter in the ExtBuilder - assert_ok!(Lottery::deposit(Origin::signed(*ALICE), balance)); - assert_ok!(Lottery::request_withdraw( - Origin::signed(*ALICE), - quarter_balance - )); - roll_to_round_begin(2); - assert_ok!(Lottery::draw_lottery(RawOrigin::Root.into())); - roll_one_block(); - assert_ok!(Lottery::request_withdraw( - Origin::signed(*ALICE), - quarter_balance - )); - assert_ok!(Lottery::request_withdraw( - Origin::signed(*ALICE), - quarter_balance - )); - roll_to_round_begin(3); - pallet_parachain_staking::AwardedPts::::insert(3, *BOB, 20); - pallet_parachain_staking::AwardedPts::::insert(3, *CHARLIE, 20); - assert_ok!(Lottery::draw_lottery(RawOrigin::Root.into())); - assert_eq!(2, Lottery::withdrawal_request_queue().len()); - roll_to_round_begin(4); - pallet_parachain_staking::AwardedPts::::insert(4, *BOB, 20); - pallet_parachain_staking::AwardedPts::::insert(4, *CHARLIE, 20); - assert_ok!(Lottery::draw_lottery(RawOrigin::Root.into())); - assert_eq!(0, Lottery::unlocked_unstaking_funds()); - assert!(Lottery::withdrawal_request_queue().is_empty()); - }); -} - -#[test] -fn many_deposit_withdrawals_work() { - let balance = 50_000_000 * UNIT; - let mut round_count = 2; - ExtBuilder::default() - .with_balances(vec![ - (*ALICE, HIGH_BALANCE), - (*BOB, HIGH_BALANCE), - (*CHARLIE, HIGH_BALANCE), - (*DAVE, HIGH_BALANCE), - (*EVE, HIGH_BALANCE), - ]) - .with_candidates(vec![ - (*ALICE, HIGH_BALANCE), - (*BOB, HIGH_BALANCE), - (*CHARLIE, HIGH_BALANCE), - (*DAVE, HIGH_BALANCE), - (*EVE, HIGH_BALANCE), - ]) - .with_funded_lottery_account(HIGH_BALANCE) - .build() - .execute_with(|| { - assert!(HIGH_BALANCE > balance); - let all_collators = &[*ALICE, *BOB, *CHARLIE, *DAVE, *EVE]; - reward_collators_for_round(round_count - 1, all_collators); - roll_to_round_end(1); - assert_ok!(Lottery::start_lottery(RawOrigin::Root.into())); - for user in 0..500 { - const USER_SEED: u32 = 696_969; - let (depositor, _) = crate::mock::from_bench::create_funded_user::( - "depositor", - USER_SEED - user, - HIGH_BALANCE, - ); - assert_ok!(Lottery::deposit(Origin::signed(depositor), balance)); - assert_ok!(Lottery::deposit(Origin::signed(depositor), balance)); - assert_ok!(Lottery::request_withdraw( - Origin::signed(depositor), - balance - )); - assert_ok!(Lottery::request_withdraw( - Origin::signed(depositor), - balance - )); - assert_ok!(Lottery::deposit(Origin::signed(depositor), balance)); - assert_ok!(Lottery::deposit(Origin::signed(depositor), balance)); - assert_ok!(Lottery::deposit(Origin::signed(depositor), balance)); - assert_ok!(Lottery::deposit(Origin::signed(depositor), balance)); - assert_ok!(Lottery::deposit(Origin::signed(depositor), balance)); - assert_ok!(Lottery::deposit(Origin::signed(depositor), balance)); - assert_ok!(Lottery::request_withdraw( - Origin::signed(depositor), - balance - )); - assert_ok!(Lottery::request_withdraw( - Origin::signed(depositor), - balance - )); - assert_ok!(Lottery::deposit(Origin::signed(depositor), balance)); - assert_ok!(Lottery::deposit(Origin::signed(depositor), balance)); - - assert_eq!(Lottery::active_balance_per_user(depositor), 6 * balance); - // we only have 5 collators available - // if all 5 are unstaking, further deposits fail - // forward the chain until they are unstaked - reward_collators_for_round(round_count, all_collators); - round_count += 1; - roll_to_round_begin((round_count * 2) - 1); - reward_collators_for_round(round_count * 2 - 2, all_collators); - roll_to_round_begin(round_count * 2); - reward_collators_for_round(round_count * 2 - 1, all_collators); - // drawing happens (twice), all unstaking collators have finished unstaking - // ensure lottery doesnt run out of gas (it's not getting staking rewards in test) - assert_ok!( - ::Currency::deposit_into_existing( - &crate::Pallet::::account_id(), - crate::Pallet::::gas_reserve(), - ) - ); - } - }); -} - -fn reward_collators_for_round(round: u32, collators: &[AccountId]) { - for c in collators { - pallet_parachain_staking::AwardedPts::::insert(round, c, 20); - } -} diff --git a/pallets/pallet-lottery/src/weights.rs b/pallets/pallet-lottery/src/weights.rs deleted file mode 100644 index 12f31cd8a..000000000 --- a/pallets/pallet-lottery/src/weights.rs +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright 2020-2023 Manta Network. -// This file is part of Manta. -// -// Manta 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. -// -// Manta 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 Manta. If not, see . - -//! Autogenerated weights for pallet_lottery -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-22, STEPS: `50`, REPEAT: 40, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("manta-dev"), DB CACHE: 1024 - -// Executed Command: -// ./target/production/manta -// benchmark -// pallet -// --chain=manta-dev -// --steps=50 -// --repeat=40 -// --pallet=pallet_lottery -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./scripts/benchmarking/frame-weights-output/pallet_lottery.rs -// --template=.github/resources/frame-weight-template.hbs - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(clippy::unnecessary_cast)] - -use frame_support::{traits::Get, weights::Weight}; -use sp_std::marker::PhantomData; -use manta_primitives::constants::RocksDbWeight; - -/// Weight functions needed for pallet_lottery. -pub trait WeightInfo { - fn deposit(x: u32, y: u32, ) -> Weight; - fn request_withdraw(x: u32, y: u32, ) -> Weight; - fn claim_my_winnings(y: u32, ) -> Weight; - fn start_lottery() -> Weight; - fn stop_lottery() -> Weight; - fn draw_lottery(x: u32, y: u32, ) -> Weight; - fn process_matured_withdrawals() -> Weight; - fn set_min_deposit() -> Weight; - fn set_min_withdraw() -> Weight; - fn set_gas_reserve() -> Weight; -} - -/// Weights for pallet_lottery using the Substrate node and recommended hardware. -pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { - // Storage: Lottery MinDeposit (r:1 w:0) - // Storage: Scheduler Lookup (r:1 w:0) - // Storage: System Account (r:2 w:2) - // Storage: ParachainStaking SelectedCandidates (r:1 w:0) - // Storage: Lottery StakedCollators (r:2 w:1) - // Storage: ParachainStaking CandidatePool (r:1 w:1) - // Storage: ParachainStaking TotalSelected (r:1 w:0) - // Storage: ParachainStaking CandidateInfo (r:7 w:1) - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) - // Storage: ParachainStaking DelegatorState (r:1 w:1) - // Storage: Lottery TotalUnclaimedWinnings (r:1 w:0) - // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) - // Storage: ParachainStaking TopDelegations (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: ParachainStaking Total (r:1 w:1) - // Storage: Lottery ActiveBalancePerUser (r:1 w:1) - // Storage: Lottery TotalPot (r:1 w:1) - // Storage: Lottery TotalUsers (r:1 w:1) - // Storage: Lottery SumOfDeposits (r:1 w:1) - // Storage: ParachainStaking DelegationScheduledRequests (r:1 w:0) - /// The range of component `x` is `[0, 1000]`. - /// The range of component `y` is `[0, 63]`. - fn deposit(x: u32, y: u32, ) -> Weight { - // Minimum execution time: 183_213 nanoseconds. - Weight::from_ref_time(191_155_541) - // Standard Error: 525 - .saturating_add(Weight::from_ref_time(158_552).saturating_mul(x.into())) - // Standard Error: 8_276 - .saturating_add(Weight::from_ref_time(295_360).saturating_mul(y.into())) - .saturating_add(T::DbWeight::get().reads(25)) - .saturating_add(T::DbWeight::get().writes(13)) - } - // Storage: Lottery MinWithdraw (r:1 w:0) - // Storage: Scheduler Lookup (r:1 w:0) - // Storage: Lottery ActiveBalancePerUser (r:1 w:1) - // Storage: Lottery WithdrawalRequestQueue (r:1 w:1) - // Storage: Lottery TotalUsers (r:1 w:1) - // Storage: Lottery TotalPot (r:1 w:1) - // Storage: Lottery SurplusUnstakingBalance (r:1 w:1) - // Storage: ParachainStaking SelectedCandidates (r:1 w:0) - // Storage: Lottery StakedCollators (r:2 w:1) - // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) - // Storage: System Account (r:1 w:0) - // Storage: ParachainStaking DelegatorState (r:1 w:1) - // Storage: Lottery TotalUnclaimedWinnings (r:1 w:0) - // Storage: ParachainStaking DelegationScheduledRequests (r:1 w:1) - // Storage: Lottery UnstakingCollators (r:1 w:1) - // Storage: ParachainStaking AwardedPts (r:1 w:0) - /// The range of component `x` is `[0, 1000]`. - /// The range of component `y` is `[0, 63]`. - fn request_withdraw(x: u32, y: u32, ) -> Weight { - // Minimum execution time: 110_186 nanoseconds. - Weight::from_ref_time(114_775_116) - // Standard Error: 1_466 - .saturating_add(Weight::from_ref_time(106_892).saturating_mul(x.into())) - // Standard Error: 23_106 - .saturating_add(Weight::from_ref_time(106_427).saturating_mul(y.into())) - .saturating_add(T::DbWeight::get().reads(16)) - .saturating_add(T::DbWeight::get().writes(9)) - } - // Storage: Lottery UnclaimedWinningsByAccount (r:1 w:1) - // Storage: System Account (r:2 w:2) - // Storage: Lottery SumOfDeposits (r:1 w:0) - // Storage: Lottery TotalUnclaimedWinnings (r:1 w:1) - /// The range of component `y` is `[0, 63]`. - fn claim_my_winnings(y: u32, ) -> Weight { - // Minimum execution time: 47_723 nanoseconds. - Weight::from_ref_time(50_397_562) - // Standard Error: 1_406 - .saturating_add(Weight::from_ref_time(151_698).saturating_mul(y.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Scheduler Lookup (r:1 w:1) - // Storage: System Account (r:1 w:0) - // Storage: ParachainStaking DelegatorState (r:1 w:0) - // Storage: Lottery TotalUnclaimedWinnings (r:1 w:0) - // Storage: Lottery GasReserve (r:1 w:0) - // Storage: Scheduler Agenda (r:1 w:1) - fn start_lottery() -> Weight { - // Minimum execution time: 40_198 nanoseconds. - Weight::from_ref_time(43_685_000) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Scheduler Lookup (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) - fn stop_lottery() -> Weight { - // Minimum execution time: 28_772 nanoseconds. - Weight::from_ref_time(29_405_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: System Account (r:1 w:0) - // Storage: Lottery WithdrawalRequestQueue (r:1 w:0) - // Storage: ParachainStaking DelegatorState (r:1 w:0) - // Storage: Lottery TotalUnclaimedWinnings (r:1 w:1) - // Storage: Lottery GasReserve (r:1 w:0) - // Storage: Lottery SumOfDeposits (r:1 w:0) - // Storage: Lottery TotalPot (r:1 w:0) - // Storage: Lottery ActiveBalancePerUser (r:2 w:0) - // Storage: Lottery UnclaimedWinningsByAccount (r:1 w:1) - // Storage: Lottery UnstakingCollators (r:1 w:0) - /// The range of component `x` is `[0, 1000]`. - /// The range of component `y` is `[0, 63]`. - fn draw_lottery(x: u32, y: u32, ) -> Weight { - // Minimum execution time: 78_612 nanoseconds. - Weight::from_ref_time(79_396_000) - // Standard Error: 46_210 - .saturating_add(Weight::from_ref_time(908_503).saturating_mul(x.into())) - // Standard Error: 735_204 - .saturating_add(Weight::from_ref_time(17_320_055).saturating_mul(y.into())) - .saturating_add(T::DbWeight::get().reads(11)) - .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(y.into()))) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Lottery UnstakingCollators (r:1 w:0) - // Storage: Lottery WithdrawalRequestQueue (r:1 w:0) - // Storage: System Account (r:1 w:0) - // Storage: ParachainStaking DelegatorState (r:1 w:0) - // Storage: Lottery TotalUnclaimedWinnings (r:1 w:0) - // Storage: Lottery GasReserve (r:1 w:0) - fn process_matured_withdrawals() -> Weight { - // Minimum execution time: 52_626 nanoseconds. - Weight::from_ref_time(53_534_000) - .saturating_add(T::DbWeight::get().reads(6)) - } - fn set_min_deposit() -> Weight { - // Minimum execution time: 52_626 nanoseconds. - Weight::from_ref_time(53_534_000) - .saturating_add(T::DbWeight::get().reads(6)) - } - fn set_min_withdraw() -> Weight { - // Minimum execution time: 52_626 nanoseconds. - Weight::from_ref_time(53_534_000) - .saturating_add(T::DbWeight::get().reads(6)) - } - fn set_gas_reserve() -> Weight { - // Minimum execution time: 52_626 nanoseconds. - Weight::from_ref_time(53_534_000) - .saturating_add(T::DbWeight::get().reads(6)) - } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - // Storage: Lottery MinDeposit (r:1 w:0) - // Storage: Scheduler Lookup (r:1 w:0) - // Storage: System Account (r:2 w:2) - // Storage: ParachainStaking SelectedCandidates (r:1 w:0) - // Storage: Lottery StakedCollators (r:2 w:1) - // Storage: ParachainStaking CandidatePool (r:1 w:1) - // Storage: ParachainStaking TotalSelected (r:1 w:0) - // Storage: ParachainStaking CandidateInfo (r:7 w:1) - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) - // Storage: ParachainStaking DelegatorState (r:1 w:1) - // Storage: Lottery TotalUnclaimedWinnings (r:1 w:0) - // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) - // Storage: ParachainStaking TopDelegations (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: ParachainStaking Total (r:1 w:1) - // Storage: Lottery ActiveBalancePerUser (r:1 w:1) - // Storage: Lottery TotalPot (r:1 w:1) - // Storage: Lottery TotalUsers (r:1 w:1) - // Storage: Lottery SumOfDeposits (r:1 w:1) - // Storage: ParachainStaking DelegationScheduledRequests (r:1 w:0) - /// The range of component `x` is `[0, 1000]`. - /// The range of component `y` is `[0, 63]`. - fn deposit(x: u32, y: u32, ) -> Weight { - // Minimum execution time: 183_213 nanoseconds. - Weight::from_ref_time(191_155_541) - // Standard Error: 525 - .saturating_add(Weight::from_ref_time(158_552).saturating_mul(x.into())) - // Standard Error: 8_276 - .saturating_add(Weight::from_ref_time(295_360).saturating_mul(y.into())) - .saturating_add(RocksDbWeight::get().reads(25)) - .saturating_add(RocksDbWeight::get().writes(13)) - } - // Storage: Lottery MinWithdraw (r:1 w:0) - // Storage: Scheduler Lookup (r:1 w:0) - // Storage: Lottery ActiveBalancePerUser (r:1 w:1) - // Storage: Lottery WithdrawalRequestQueue (r:1 w:1) - // Storage: Lottery TotalUsers (r:1 w:1) - // Storage: Lottery TotalPot (r:1 w:1) - // Storage: Lottery SurplusUnstakingBalance (r:1 w:1) - // Storage: ParachainStaking SelectedCandidates (r:1 w:0) - // Storage: Lottery StakedCollators (r:2 w:1) - // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) - // Storage: System Account (r:1 w:0) - // Storage: ParachainStaking DelegatorState (r:1 w:1) - // Storage: Lottery TotalUnclaimedWinnings (r:1 w:0) - // Storage: ParachainStaking DelegationScheduledRequests (r:1 w:1) - // Storage: Lottery UnstakingCollators (r:1 w:1) - // Storage: ParachainStaking AwardedPts (r:1 w:0) - /// The range of component `x` is `[0, 1000]`. - /// The range of component `y` is `[0, 63]`. - fn request_withdraw(x: u32, y: u32, ) -> Weight { - // Minimum execution time: 110_186 nanoseconds. - Weight::from_ref_time(114_775_116) - // Standard Error: 1_466 - .saturating_add(Weight::from_ref_time(106_892).saturating_mul(x.into())) - // Standard Error: 23_106 - .saturating_add(Weight::from_ref_time(106_427).saturating_mul(y.into())) - .saturating_add(RocksDbWeight::get().reads(16)) - .saturating_add(RocksDbWeight::get().writes(9)) - } - // Storage: Lottery UnclaimedWinningsByAccount (r:1 w:1) - // Storage: System Account (r:2 w:2) - // Storage: Lottery SumOfDeposits (r:1 w:0) - // Storage: Lottery TotalUnclaimedWinnings (r:1 w:1) - /// The range of component `y` is `[0, 63]`. - fn claim_my_winnings(y: u32, ) -> Weight { - // Minimum execution time: 47_723 nanoseconds. - Weight::from_ref_time(50_397_562) - // Standard Error: 1_406 - .saturating_add(Weight::from_ref_time(151_698).saturating_mul(y.into())) - .saturating_add(RocksDbWeight::get().reads(5)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Scheduler Lookup (r:1 w:1) - // Storage: System Account (r:1 w:0) - // Storage: ParachainStaking DelegatorState (r:1 w:0) - // Storage: Lottery TotalUnclaimedWinnings (r:1 w:0) - // Storage: Lottery GasReserve (r:1 w:0) - // Storage: Scheduler Agenda (r:1 w:1) - fn start_lottery() -> Weight { - // Minimum execution time: 40_198 nanoseconds. - Weight::from_ref_time(43_685_000) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Scheduler Lookup (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) - fn stop_lottery() -> Weight { - // Minimum execution time: 28_772 nanoseconds. - Weight::from_ref_time(29_405_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: System Account (r:1 w:0) - // Storage: Lottery WithdrawalRequestQueue (r:1 w:0) - // Storage: ParachainStaking DelegatorState (r:1 w:0) - // Storage: Lottery TotalUnclaimedWinnings (r:1 w:1) - // Storage: Lottery GasReserve (r:1 w:0) - // Storage: Lottery SumOfDeposits (r:1 w:0) - // Storage: Lottery TotalPot (r:1 w:0) - // Storage: Lottery ActiveBalancePerUser (r:2 w:0) - // Storage: Lottery UnclaimedWinningsByAccount (r:1 w:1) - // Storage: Lottery UnstakingCollators (r:1 w:0) - /// The range of component `x` is `[0, 1000]`. - /// The range of component `y` is `[0, 63]`. - fn draw_lottery(x: u32, y: u32, ) -> Weight { - // Minimum execution time: 78_612 nanoseconds. - Weight::from_ref_time(79_396_000) - // Standard Error: 46_210 - .saturating_add(Weight::from_ref_time(908_503).saturating_mul(x.into())) - // Standard Error: 735_204 - .saturating_add(Weight::from_ref_time(17_320_055).saturating_mul(y.into())) - .saturating_add(RocksDbWeight::get().reads(11)) - .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(y.into()))) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Lottery UnstakingCollators (r:1 w:0) - // Storage: Lottery WithdrawalRequestQueue (r:1 w:0) - // Storage: System Account (r:1 w:0) - // Storage: ParachainStaking DelegatorState (r:1 w:0) - // Storage: Lottery TotalUnclaimedWinnings (r:1 w:0) - // Storage: Lottery GasReserve (r:1 w:0) - fn process_matured_withdrawals() -> Weight { - // Minimum execution time: 52_626 nanoseconds. - Weight::from_ref_time(53_534_000) - .saturating_add(RocksDbWeight::get().reads(6)) - } - fn set_min_deposit() -> Weight { - // Minimum execution time: 52_626 nanoseconds. - Weight::from_ref_time(53_534_000) - .saturating_add(RocksDbWeight::get().reads(6)) - } - fn set_min_withdraw() -> Weight { - // Minimum execution time: 52_626 nanoseconds. - Weight::from_ref_time(53_534_000) - .saturating_add(RocksDbWeight::get().reads(6)) - } - fn set_gas_reserve() -> Weight { - // Minimum execution time: 52_626 nanoseconds. - Weight::from_ref_time(53_534_000) - .saturating_add(RocksDbWeight::get().reads(6)) - } -} diff --git a/pallets/randomness/Cargo.toml b/pallets/randomness/Cargo.toml deleted file mode 100644 index 000f52e6d..000000000 --- a/pallets/randomness/Cargo.toml +++ /dev/null @@ -1,57 +0,0 @@ -[package] -authors = ["Manta Network"] -description = "Provides on-chain randomness" -edition = "2021" -homepage = 'https://manta.network' -license = 'GPL-3.0' -name = 'pallet-randomness' -repository = 'https://github.com/Manta-Network/Manta/' -version = '4.4.0' - -[dependencies] -frame-benchmarking = { git = 'https://github.com/paritytech/substrate.git', branch = "polkadot-v0.9.37", default-features = false, optional = true } -frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.37", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.37", default-features = false } -hex = { version = "0.4.3", default-features = false } -log = { version = "0.4", default-features = false } -manta-primitives = { path = "../../primitives/manta", default-features = false } -nimbus-primitives = { git = "https://github.com/manta-network/nimbus.git", tag = "v4.0.8", default-features = false } -parity-scale-codec = { version = "3.4.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", default-features = false, optional = true } -session-key-primitives = { path = '../../primitives/session-keys', default-features = false } -sp-core = { git = 'https://github.com/paritytech/substrate.git', branch = "polkadot-v0.9.37", default-features = false } -sp-io = { git = 'https://github.com/paritytech/substrate.git', branch = "polkadot-v0.9.37", default-features = false } -sp-runtime = { git = 'https://github.com/paritytech/substrate.git', branch = "polkadot-v0.9.37", default-features = false } -sp-std = { git = 'https://github.com/paritytech/substrate.git', branch = "polkadot-v0.9.37", default-features = false } - -[dev-dependencies] -derive_more = "0.99" -pallet-balances = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.37", features = ["std"] } - -[features] -default = ["std"] -runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", -] -std = [ - "frame-benchmarking/std", - "frame-support/std", - "frame-system/std", - "hex/std", - "nimbus-primitives/std", - "parity-scale-codec/std", - "scale-info/std", - "serde", - "session-key-primitives/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", - "sp-std/std", -] -try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", -] diff --git a/pallets/randomness/README.md b/pallets/randomness/README.md deleted file mode 100644 index 07c1900e1..000000000 --- a/pallets/randomness/README.md +++ /dev/null @@ -1,115 +0,0 @@ -# Randomness Solutions Tradeoff Analysis - -This pallet provides access to 1 source of randomness: - -1. The **BABE epoch randomness** is produced by the relay chain per relay chain epoch. It is based on **all the VRF produced** by the relay chain validators **during** a complete **epoch**.(~600 blocks on Kusama, ~2400 blocks on Polkadot). At the beginning of a new Epoch, those VRFs are **mixed together** and **hashed** in order to produce a **pseudo-random word**. - -## CAP Theorem - -The CAP Theorem says we can have at most 2 of the following properties: Consistency, Availability, and Partition Tolerance. - -### Consistency - -At any block, the random word is consistent between the nodes. - -In practice it means that there **is only 1 possible random word generated** and so the node cannot choose between multiple ones. - -### Example - -Having multiple actors **sending a VRF proof** of the randomly generated word, and **revealing** their random words after all the proofs have been published. (the random word would be the hash of all the secrets) - -By doing so, once the VRF proofs have all been published, there can only be 1 generated word. -_(This example however doesn't have the "Availability" property as actors can "fail" to reveal their secret preventing the random word to be available)_ - -### Availability - -Every request to generate a random word receives a response. - -In practice, it means a node **cannot withhold** any information **preventing** the random word to receive **a response**. - -#### Example - -Using the mandatory VRF of the current collator as a pseudo-random word makes it "available" as it is always present when the block is produced. - -If the collator skips the block production, the next collator producing the block will be able to include its VRF to the block, allowing to provide the pseudo-random word. -_(This example however breaks the "consistency" because for the given block, there were the possibility of 2 different pseudo-random)_ - -### Partition Tolerance - -The network continues to operate, even if an arbitrary number of nodes are failing. - -In practice, it means that the randomness process cannot rely on a designated node but must, like the blockchain consensus, continue to work with a subset of the collators. - -#### Example - -Ex: Using the current block collator to produce the randomness output is partition tolerant. -If the current block collator fails to produce the block, the consensus will pick another collator allowing to produce the pseudo-random word. - -## Breaking down in 2 categories - -Because we can't get rid of the partition tolerance, we can only provide solutions that are compromising the Consistency or the Availability. - -### Category 1: Availability Over Consistency - -The solutions in category 1 provide a pseudo-random process that is guaranteed to provide a pseudo-random word but cannot ensure it hasn't been tampered before being revealed. - -This is the case for the [Babe Epoch Randomness] which ensure each epoch provides a pseudo-random word but also allows the last validator of an epoch to known and pick 2 different pseudo-random words by skipping the block production (At the cost of a relay chain block reward) - -### Category 2: Consistency over Availability - -The solutions in this category provide a pseudo-random that cannot be tampered and that is unique but cannot provide the guarantee it will be always be possible to retrieve it. - -This is the case of the [Mixed Delayed Secret] (not yet described), which will require collators to provide a VRF proof of locally generated secret, and to reveal it later once all the VRF proofs have been published. Such a solution will guarantee that, once the VRF proofs are published, it is impossible to provide a different pseudo-random word. It also guarantees that if at least 1 collator is a good actor, it will be impossible to know the pseudo-random word until all the secrets are revealed. However such solution cannot guarantee that a node will always be able to provide its secret (it can be lost, it can be attacked, or the node can be malicious and refuses to publish it). - -## Babe Epoch Randomness - -The Babe epoch randomness is based on **all the VRF produced** by the validators **during** a complete **epoch**.(~600 blocks on Kusama, ~2400 blocks on Polkadot) -At the beginning of a new Epoch, those VRFs are **mixed together** and **hashed** in order to produce a **pseudo-random word**. -To ensure each pseudo-random word generated during an epoch is different, the Smart Contract must provide a unique salt each time. - -### Properties - -- This randomness is totally **independent of the parachain**, preventing a malicious actor on the parachain to influence the randomness value. -- This randomness is **constant during a full epoch range** (~250 blocks on Kusama, ~2300 blocks on Polkadot) making it **resilient enough against censorship**. If a collator prevents fulfillment at a given block, another collator can fulfill it at the next block with the same random value. -- This randomness **requires** at last 1 epoch after the current epoch (**~1h30** on Kusama, **~6h** on Polkadot) to ensure the pseudo-random word cannot be predicted at the time of the request. - -### Risks - -The **danger** in this process comes from the knowledge that the **last validator** (Validator Y in the schema) has when producing the last block of an Epoch. The process being deterministic and all the material to generate the pseudo random word being known, the validator can decide to **skip producing the block** in order to not include its VRF, which would result in a different pseudo-random word. - -Because epoch are time-based, if the block is skipped, there won't be any additional block produced for that epoch. So the last validator of the block knows both possible output: - -1. When **producing the block** including its VRF => pseudo-random word **AAAA** -2. When **skipping the block** and using already known previous VRFs => pseudo-random word **BBBB** - -The only **incentive** to prevent the validator from skipping the block is the **block rewards**. So the randomness value is only **economically safe if the value at stake is lower than a block reward**. - -```sequence -note over Validator: Validator A -note over Relay: Epoch 1: Block #2399 -Relay->Para: (Relay Block #2399) -note over Para: Block #111\nRequest Randomness (@Epoch 3) -note left of Relay: No knowledge of epoch 2 randomness\nexists yet -Validator->Relay: (Relay Block #2400) -note over Relay: Epoch 2: Block #2400\n(random epoch 1: 0xAAAAAA...) -note over Relay: .\n.\n. -note over Para: .\n.\n. -note over Validator: Validator X -Validator->Relay: Produces #4798\n(influences Epoch 2 Randomness\nbut doesn't know the result) -note over Validator: Validator Y -Validator->Relay: Produces #4799\n(knows/influences Epoch 2 Randomness)\ncan choose 0xBBBBBB... or 0xCCCCCC... -note over Relay: Epoch 3: Block #4800\n(random epoch 2: 0xBBBBBB...or 0xCCCCCC...) -Relay->Para: (Relay Block #4800) -note over Para: Block #222\nFulFill Randomness using\n0xBBBBBB...or 0xCCCCCC... -``` - -_In this schema, we can see that validator Y can decide the epoch 2 randomness by producing or skipping its block._ - -### Multiple slot leaders - -Additionally, the Babe consensus can sometime allow multiple validator to produce a block at the same slot. If that is the last slot of an Epoch,the selected validators coordinate in order to decide which one is producing the block, offering the choice of even more pseudo-random words. - -### Asynchronous Backing - -This solution is **safe** even after the asynchronous backing is supported as the pseudo-random is not dependant on which relay block the parachain block is referencing. -A collator being able to choose the relay block on top of which it builds the parachain block will not influence the pseudo-random word. diff --git a/pallets/randomness/src/benchmarks.rs b/pallets/randomness/src/benchmarks.rs deleted file mode 100644 index be3bb0dd9..000000000 --- a/pallets/randomness/src/benchmarks.rs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2020-2023 Manta Network. -// This file is part of Manta. -// -// Manta 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. -// -// Manta 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 Manta. If not, see . - -#![cfg(feature = "runtime-benchmarks")] - -//! Benchmarking -use crate::{ - Call, Config, InherentIncluded, Pallet, RandomnessResult, RandomnessResults, RelayEpoch, - RequestType, -}; -use frame_benchmarking::{benchmarks, impl_benchmark_test_suite}; -use frame_system::RawOrigin; - -benchmarks! { - // Benchmark for inherent included in every block - set_babe_randomness_results { - // set the current relay epoch as 9, `get_epoch_index` configured to return 10 - const BENCHMARKING_OLD_EPOCH: u64 = 9u64; - RelayEpoch::::put(BENCHMARKING_OLD_EPOCH); - let benchmarking_babe_output = T::Hash::default(); - let benchmarking_new_epoch = BENCHMARKING_OLD_EPOCH.saturating_add(1u64); - RandomnessResults::::insert( - RequestType::BabeEpoch(benchmarking_new_epoch), - RandomnessResult::new() - ); - }: _(RawOrigin::None) - verify { - // verify randomness result - assert_eq!( - RandomnessResults::::get( - RequestType::BabeEpoch(benchmarking_new_epoch) - ).unwrap().randomness, - Some(benchmarking_babe_output) - ); - assert!(InherentIncluded::::get().is_some()); - assert_eq!( - RelayEpoch::::get(), - benchmarking_new_epoch - ); - } -} - -#[cfg(test)] -mod tests { - use crate::mock::Test; - use sp_io::TestExternalities; - - pub fn new_test_ext() -> TestExternalities { - let t = frame_system::GenesisConfig::default() - .build_storage::() - .unwrap(); - TestExternalities::new(t) - } -} - -impl_benchmark_test_suite!( - Pallet, - crate::benchmarks::tests::new_test_ext(), - crate::mock::Test -); diff --git a/pallets/randomness/src/lib.rs b/pallets/randomness/src/lib.rs deleted file mode 100644 index d9640bfa8..000000000 --- a/pallets/randomness/src/lib.rs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2020-2023 Manta Network. -// This file is part of Manta. -// -// Manta 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. -// -// Manta 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 Manta. If not, see . - -//! # Randomness Pallet -//! -//! This pallet provides access to 1 sources of randomness: -//! 1. relay chain BABE one epoch ago randomness, produced by the relay chain per relay chain epoch -//! These options are represented as `type::RequestType`. -//! -//! There are no extrinsics for this pallet. Instead, public functions on `Pallet` expose -//! user actions for the precompile i.e. `request_randomness`. -//! -//! ## Babe Epoch Randomness -//! Babe epoch randomness is retrieved once every relay chain epoch. -//! -//! The `set_babe_randomness_results` mandatory inherent reads the Babe epoch randomness from the -//! relay chain state proof and fills any pending `RandomnessResults` for this epoch randomness. -//! -//! `Config::BabeDataGetter` is responsible for reading the epoch index and epoch randomness -//! from the relay chain state proof. The moonbeam `GetBabeData` implementation is in the runtime. - -#![cfg_attr(not(feature = "std"), no_std)] - -use frame_support::pallet; -pub use pallet::*; -use sp_std::vec::Vec; -pub use weights::WeightInfo; - -#[cfg(any(test, feature = "runtime-benchmarks"))] -mod benchmarks; -pub mod types; -pub use types::*; -pub mod weights; - -#[cfg(test)] -mod mock; -#[cfg(test)] -mod tests; - -/// Read babe randomness info from the relay chain state proof -pub trait GetBabeData { - fn get_epoch_index() -> EpochIndex; - fn get_epoch_randomness() -> Randomness; -} - -#[pallet] -pub mod pallet { - use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - use session_key_primitives::inherent::{InherentError, INHERENT_IDENTIFIER}; - use sp_runtime::traits::Hash; - use sp_std::convert::TryInto; - #[pallet::pallet] - #[pallet::without_storage_info] - pub struct Pallet(PhantomData); - - /// Configuration trait of this pallet. - #[pallet::config] - pub trait Config: frame_system::Config { - /// Get the BABE data from the runtime - type BabeDataGetter: GetBabeData>; - type WeightInfo: WeightInfo; - } - - #[pallet::error] - pub enum Error { - CannotRequestRandomnessAfterMaxDelay, - } - - /// Relay epoch - #[pallet::storage] - #[pallet::getter(fn relay_epoch)] - pub(crate) type RelayEpoch = StorageValue<_, u64, ValueQuery>; - - /// Ensures the mandatory inherent was included in the block - #[pallet::storage] - #[pallet::getter(fn inherent_included)] - pub(crate) type InherentIncluded = StorageValue<_, ()>; - - /// Snapshot of randomness to fulfill all requests that are for the same raw randomness - /// Removed once $value.request_count == 0 - #[pallet::storage] - #[pallet::getter(fn randomness_results)] - pub type RandomnessResults = - StorageMap<_, Twox64Concat, RequestType, RandomnessResult>; - - #[pallet::call] - impl Pallet { - /// Populates `RandomnessResults` due this epoch with BABE epoch randomness - #[pallet::call_index(0)] - #[pallet::weight(( - ::WeightInfo::set_babe_randomness_results(), - DispatchClass::Mandatory - ))] - pub fn set_babe_randomness_results(origin: OriginFor) -> DispatchResultWithPostInfo { - ensure_none(origin)?; - let last_relay_epoch_index = >::get(); - let relay_epoch_index = T::BabeDataGetter::get_epoch_index(); - if relay_epoch_index > last_relay_epoch_index { - // NOTE: Whether n = 1 or 2 depends on the trait implementation of BabeDataGetter - let babe_n_epochs_ago_this_block = RequestType::BabeEpoch(relay_epoch_index); - if let Some(randomness) = T::BabeDataGetter::get_epoch_randomness() { - let result = RandomnessResult { - request_count: 1, - randomness: Some(randomness), - }; - >::insert(babe_n_epochs_ago_this_block, result); - } else { - log::warn!( - "Failed to fill BABE epoch randomness results \ - REQUIRE HOTFIX TO FILL EPOCH RANDOMNESS RESULTS FOR EPOCH {:?}", - relay_epoch_index - ); - } - } - >::put(relay_epoch_index); - >::put(()); - Ok(Pays::No.into()) - } - } - - #[pallet::inherent] - impl ProvideInherent for Pallet { - type Call = Call; - type Error = InherentError; - const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER; - - fn is_inherent_required(_: &InherentData) -> Result, Self::Error> { - // Return Ok(Some(_)) unconditionally because this inherent is required in every block - // If it is not found, throw a VrfInherentRequired error. - Ok(Some(InherentError::Other( - sp_runtime::RuntimeString::Borrowed( - "Inherent required to set babe randomness results", - ), - ))) - } - - // The empty-payload inherent extrinsic. - fn create_inherent(_data: &InherentData) -> Option { - Some(Call::set_babe_randomness_results {}) - } - - fn is_inherent(call: &Self::Call) -> bool { - matches!(call, Call::set_babe_randomness_results { .. }) - } - } - - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_finalize(_now: BlockNumberFor) { - // Ensure the mandatory inherent was included in the block or the block is invalid - assert!( - >::take().is_some(), - "Mandatory randomness inherent not included; InherentIncluded storage item is empty" - ); - } - } - - // Randomness trait - impl frame_support::traits::Randomness> for Pallet { - /// Uses the BABE randomness to generate a random seed. - fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor) { - let relay_epoch_index = >::get(); - let randomness_output = - RandomnessResults::::get(RequestType::BabeEpoch(relay_epoch_index)) - .unwrap_or_else(|| { - log::error!( - "FATAL Could not find the included Babe randomness for {:?}. Using None", - relay_epoch_index - ); - RandomnessResult::::new() - }) - .randomness - .unwrap_or_else(|| { - log::error!("FATAL included BABE randomness is `None`. Using default hash"); - T::Hash::default() - }); - let mut digest = Vec::new(); - digest.extend_from_slice(randomness_output.as_ref()); - digest.extend_from_slice(subject); - let randomness = T::Hashing::hash(digest.as_slice()); - // TODO: Randomness Established at start of Epoch! This is nontrivial to implement - // because we need to map the start-of-epoch relayblock to its matching parablock - // in its current form block_number is meaningless and should not be relied upon - let randomness_established_at = 0u32.into(); - (randomness, randomness_established_at) - } - } -} diff --git a/pallets/randomness/src/mock.rs b/pallets/randomness/src/mock.rs deleted file mode 100644 index b15ef304f..000000000 --- a/pallets/randomness/src/mock.rs +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2020-2023 Manta Network. -// This file is part of Manta. -// -// Manta 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. -// -// Manta 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 Manta. If not, see . - -//! A minimal runtime including the pallet-randomness pallet -use super::*; -use crate as pallet_randomness; -use frame_support::{construct_runtime, parameter_types, traits::Everything, weights::Weight}; -use sp_core::{H160, H256}; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - Perbill, -}; -use sp_std::convert::{TryFrom, TryInto}; - -pub type AccountId = H160; -pub type Balance = u128; -pub type BlockNumber = u32; - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -// Configure a mock runtime to test the pallet. -construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Randomness: pallet_randomness::{Pallet, Call, Storage, Inherent}, - } -); - -parameter_types! { - pub const BlockHashCount: u32 = 250; - pub const MaximumBlockWeight: Weight = Weight::from_ref_time(1024); - pub const MaximumBlockLength: u32 = 2 * 1024; - pub const AvailableBlockRatio: Perbill = Perbill::one(); - pub const SS58Prefix: u8 = 42; -} -impl frame_system::Config for Test { - type BaseCallFilter = Everything; - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = BlockNumber; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Header = sp_runtime::generic::Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type BlockWeights = (); - type BlockLength = (); - type SS58Prefix = SS58Prefix; - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; -} - -parameter_types! { - pub const ExistentialDeposit: u128 = 0; -} -impl pallet_balances::Config for Test { - type MaxReserves = (); - type ReserveIdentifier = [u8; 4]; - type MaxLocks = (); - type Balance = Balance; - type RuntimeEvent = RuntimeEvent; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type WeightInfo = (); -} - -pub struct BabeDataGetter; -impl crate::GetBabeData> for BabeDataGetter { - fn get_epoch_index() -> u64 { - 10u64 - } - fn get_epoch_randomness() -> Option { - Some(H256::default()) - } -} - -parameter_types! { - pub const Deposit: u128 = 10; - pub const MaxRandomWords: u8 = 1; - pub const MinBlockDelay: u32 = 2; - pub const MaxBlockDelay: u32 = 20; -} -impl Config for Test { - type BabeDataGetter = BabeDataGetter; - type WeightInfo = (); -} - -/// Externality builder for pallet randomness mock runtime -#[derive(Default)] -pub(crate) struct ExtBuilder { - /// Balance amounts per AccountId - balances: Vec<(AccountId, Balance)>, -} - -impl ExtBuilder { - #[allow(dead_code)] - pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self { - self.balances = balances; - self - } - - #[allow(dead_code)] - pub(crate) fn build(self) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default() - .build_storage::() - .expect("Frame system builds valid default genesis config"); - - pallet_balances::GenesisConfig:: { - balances: self.balances, - } - .assimilate_storage(&mut t) - .expect("Pallet balances storage can be assimilated"); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext - } -} diff --git a/pallets/randomness/src/tests.rs b/pallets/randomness/src/tests.rs deleted file mode 100644 index b81b0c755..000000000 --- a/pallets/randomness/src/tests.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2020-2023 Manta Network. -// This file is part of Manta. -// -// Manta 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. -// -// Manta 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 Manta. If not, see . - -use crate::mock::*; - -#[test] -fn set_babe_randomness_results_is_mandatory() { - use frame_support::dispatch::{DispatchClass, GetDispatchInfo}; - - let info = crate::Call::::set_babe_randomness_results {}.get_dispatch_info(); - assert_eq!(info.class, DispatchClass::Mandatory); -} diff --git a/pallets/randomness/src/types.rs b/pallets/randomness/src/types.rs deleted file mode 100644 index 524e41fd8..000000000 --- a/pallets/randomness/src/types.rs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2020-2023 Manta Network. -// This file is part of Manta. -// -// Manta 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. -// -// Manta 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 Manta. If not, see . - -use frame_support::pallet_prelude::*; - -#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -#[scale_info(skip_type_params(T))] -/// Shared request info, a subset of `RequestInfo` -pub enum RequestType { - /// Babe one epoch ago - BabeEpoch(u64), -} - -#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -#[scale_info(skip_type_params(T))] -/// Type of request -/// Represents a request for the most recent randomness at or after the inner first field -/// Expiration is second inner field -pub enum RequestInfo { - /// Babe one epoch ago - BabeEpoch(u64, u64), -} - -#[derive(PartialEq, Eq, Clone, Default, Encode, Decode, RuntimeDebug, TypeInfo)] -/// Raw randomness snapshot, the unique value for a `RequestType` in `RandomnessResults` map -pub struct RandomnessResult { - /// Randomness once available - pub randomness: Option, - /// Number of randomness requests for the type - pub request_count: u64, -} - -impl RandomnessResult { - pub fn new() -> RandomnessResult { - RandomnessResult { - randomness: None, - request_count: 1u64, - } - } -} diff --git a/pallets/randomness/src/weights.rs b/pallets/randomness/src/weights.rs deleted file mode 100644 index efc2f3a1f..000000000 --- a/pallets/randomness/src/weights.rs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2020-2023 Manta Network. -// This file is part of Manta. -// -// Manta 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. -// -// Manta 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 Manta. If not, see . - -//! Autogenerated weights for pallet_randomness -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-22, STEPS: `50`, REPEAT: 40, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("manta-dev"), DB CACHE: 1024 - -// Executed Command: -// ./target/production/manta -// benchmark -// pallet -// --chain=manta-dev -// --steps=50 -// --repeat=40 -// --pallet=pallet_randomness -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./scripts/benchmarking/frame-weights-output/pallet_randomness.rs -// --template=.github/resources/frame-weight-template.hbs - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(clippy::unnecessary_cast)] - -use frame_support::{traits::Get, weights::Weight}; -use sp_std::marker::PhantomData; -use manta_primitives::constants::RocksDbWeight; - -/// Weight functions needed for pallet_randomness. -pub trait WeightInfo { - fn set_babe_randomness_results() -> Weight; -} - -/// Weights for pallet_randomness using the Substrate node and recommended hardware. -pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { - // Storage: Randomness RelayEpoch (r:1 w:1) - // Storage: ParachainSystem ValidationData (r:1 w:0) - // Storage: ParachainSystem RelayStateProof (r:1 w:0) - // Storage: Randomness RandomnessResults (r:0 w:1) - // Storage: Randomness InherentIncluded (r:0 w:1) - fn set_babe_randomness_results() -> Weight { - // Minimum execution time: 14_410 nanoseconds. - Weight::from_ref_time(14_737_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - // Storage: Randomness RelayEpoch (r:1 w:1) - // Storage: ParachainSystem ValidationData (r:1 w:0) - // Storage: ParachainSystem RelayStateProof (r:1 w:0) - // Storage: Randomness RandomnessResults (r:0 w:1) - // Storage: Randomness InherentIncluded (r:0 w:1) - fn set_babe_randomness_results() -> Weight { - // Minimum execution time: 14_410 nanoseconds. - Weight::from_ref_time(14_737_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) - } -} diff --git a/runtime/calamari/Cargo.toml b/runtime/calamari/Cargo.toml index d6cf61286..858422f87 100644 --- a/runtime/calamari/Cargo.toml +++ b/runtime/calamari/Cargo.toml @@ -96,12 +96,10 @@ manta-support = { package = "pallet-manta-support", path = "../../pallets/manta- pallet-asset-manager = { path = '../../pallets/asset-manager', default-features = false } pallet-farming = { path = '../../pallets/farming', default-features = false } pallet-farming-rpc-runtime-api = { path = '../../pallets/farming/rpc/runtime-api', default-features = false } -pallet-lottery = { path = '../../pallets/pallet-lottery', default-features = false } pallet-manta-pay = { path = '../../pallets/manta-pay', default-features = false, features = ["runtime"] } pallet-manta-sbt = { path = '../../pallets/manta-sbt', default-features = false, features = ["runtime"] } pallet-name-service = { path = '../../pallets/name-service', default-features = false } pallet-parachain-staking = { path = '../../pallets/parachain-staking', default-features = false } -pallet-randomness = { path = '../../pallets/randomness', default-features = false } pallet-tx-pause = { path = '../../pallets/tx-pause', default-features = false } runtime-common = { path = '../common', default-features = false } session-key-primitives = { path = '../../primitives/session-keys', default-features = false } @@ -110,6 +108,9 @@ session-key-primitives = { path = '../../primitives/session-keys', default-featu orml-traits = { git = 'https://github.com/manta-network/open-runtime-module-library.git', default-features = false, branch = "polkadot-v0.9.37" } orml-xtokens = { git = 'https://github.com/manta-network/open-runtime-module-library.git', default-features = false, branch = "polkadot-v0.9.37" } +pallet-lottery = { git = 'https://github.com/jumboshrimpslab/lottery-polkadot.git', default-features = false } +pallet-randomness = { git = 'https://github.com/jumboshrimpslab/lottery-polkadot.git', default-features = false } + zenlink-protocol = { git = 'https://github.com/manta-network/Zenlink', branch = "polkadot-v0.9.37", default-features = false } zenlink-protocol-runtime-api = { git = 'https://github.com/manta-network/Zenlink', branch = "polkadot-v0.9.37", default-features = false } diff --git a/runtime/manta/Cargo.toml b/runtime/manta/Cargo.toml index 0b88d3b3f..29d0f48ef 100644 --- a/runtime/manta/Cargo.toml +++ b/runtime/manta/Cargo.toml @@ -88,6 +88,9 @@ xcm-executor = { git = 'https://github.com/paritytech/polkadot.git', default-fea orml-traits = { git = 'https://github.com/manta-network/open-runtime-module-library.git', default-features = false, branch = "polkadot-v0.9.37" } orml-xtokens = { git = 'https://github.com/manta-network/open-runtime-module-library.git', default-features = false, branch = "polkadot-v0.9.37" } +pallet-lottery = { git = 'https://github.com/jumboshrimpslab/lottery-polkadot.git', default-features = false } +pallet-randomness = { git = 'https://github.com/jumboshrimpslab/lottery-polkadot.git', default-features = false } + zenlink-protocol = { git = 'https://github.com/manta-network/Zenlink', branch = "polkadot-v0.9.37", default-features = false } zenlink-protocol-runtime-api = { git = 'https://github.com/manta-network/Zenlink', branch = "polkadot-v0.9.37", default-features = false } @@ -98,12 +101,10 @@ manta-support = { package = "pallet-manta-support", path = "../../pallets/manta- pallet-asset-manager = { path = '../../pallets/asset-manager', default-features = false } pallet-farming = { path = '../../pallets/farming', default-features = false } pallet-farming-rpc-runtime-api = { path = '../../pallets/farming/rpc/runtime-api', default-features = false } -pallet-lottery = { path = '../../pallets/pallet-lottery', default-features = false } pallet-manta-pay = { path = '../../pallets/manta-pay', default-features = false, features = ["runtime"] } pallet-manta-sbt = { path = '../../pallets/manta-sbt', default-features = false, features = ["runtime"] } pallet-name-service = { path = '../../pallets/name-service', default-features = false } pallet-parachain-staking = { path = '../../pallets/parachain-staking', default-features = false } -pallet-randomness = { path = '../../pallets/randomness', default-features = false } pallet-tx-pause = { path = '../../pallets/tx-pause', default-features = false } runtime-common = { path = '../common', default-features = false } session-key-primitives = { path = '../../primitives/session-keys', default-features = false }