From 5086f00bc3a2d0f8b6e6b988fe1be13a498473c3 Mon Sep 17 00:00:00 2001 From: Amar Singh Date: Thu, 22 Jul 2021 19:32:39 -0400 Subject: [PATCH] Delay nominator revocations and exits (#610) * init * save * save * save * log::trace -> log::warn, drop errors, and fix compile errors * commit migrationss design will revert for simpler impl * minimal migration impl instead * delay leave nominators with all unit tests passing * delayed revoke_nomination with all tests passing * unit tests * split BondDuration into 4 constants and update runtimes * try green * green * rm revoke nomination TS test because it expects revocation to be immediate and it is now delayed and there is no leave candidates TS test to show how to delay * add types to moonbeam types bundle and update test instead of commenting it out * fix * fix * try fix TS test * try again * every byte counts * x * y * z * update weights * ready for review * rm unused errors * more accurate weight returned in on runtime upgrade * fix weight, one read to check if migration has run before * Update pallets/parachain-staking/src/lib.rs --- Cargo.lock | 54 +- moonbeam-types-bundle/index.ts | 19 +- pallets/parachain-staking/Cargo.toml | 15 +- pallets/parachain-staking/README.md | 6 + pallets/parachain-staking/src/benchmarks.rs | 13 +- pallets/parachain-staking/src/lib.rs | 532 ++++++++++++++---- pallets/parachain-staking/src/mock.rs | 12 +- pallets/parachain-staking/src/tests.rs | 559 ++++++++++++++----- pallets/parachain-staking/src/weights.rs | 148 ++--- runtime/moonbase/src/lib.rs | 15 +- runtime/moonbase/tests/integration_test.rs | 72 +-- runtime/moonbeam/src/lib.rs | 15 +- runtime/moonbeam/tests/integration_test.rs | 72 +-- runtime/moonriver/src/lib.rs | 15 +- runtime/moonriver/tests/integration_test.rs | 72 +-- runtime/moonshadow/src/lib.rs | 15 +- runtime/moonshadow/tests/integration_test.rs | 72 +-- tests/tests/test-stake.ts | 52 +- 18 files changed, 1118 insertions(+), 640 deletions(-) create mode 100644 pallets/parachain-staking/README.md diff --git a/Cargo.lock b/Cargo.lock index 94c785d58e..13fe0a4dcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -844,7 +844,9 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" dependencies = [ + "lazy_static", "memchr", + "regex-automata", ] [[package]] @@ -1086,6 +1088,19 @@ dependencies = [ "cache-padded", ] +[[package]] +name = "console" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "terminal_size", + "winapi 0.3.9", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -1913,6 +1928,12 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "enum-as-inner" version = "0.3.3" @@ -6752,7 +6773,7 @@ dependencies = [ [[package]] name = "parachain-staking" -version = "2.1.0" +version = "2.1.1" dependencies = [ "frame-benchmarking", "frame-support", @@ -6762,6 +6783,7 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "serde", + "similar-asserts", "sp-core", "sp-io", "sp-runtime", @@ -10578,6 +10600,26 @@ dependencies = [ "paste", ] +[[package]] +name = "similar" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec" +dependencies = [ + "bstr", + "unicode-segmentation", +] + +[[package]] +name = "similar-asserts" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e6ad120c7cf671a6bae051f6d0240fbefc5b7ca2bb4a43fee351f7fbaecb559" +dependencies = [ + "console", + "similar", +] + [[package]] name = "slab" version = "0.4.3" @@ -11784,6 +11826,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "textwrap" version = "0.11.0" diff --git a/moonbeam-types-bundle/index.ts b/moonbeam-types-bundle/index.ts index cf1d80658f..8d7d2da8b3 100644 --- a/moonbeam-types-bundle/index.ts +++ b/moonbeam-types-bundle/index.ts @@ -612,6 +612,17 @@ export const moonbeamDefinitions = { nominations: "Vec", total: "Balance", }, + NominatorStatus: { + _enum: ["Active", { Leaving: "RoundIndex" }], + }, + Nominator2: { + nominations: "Vec", + revocations: "Vec", + total: "Balance", + scheduled_revocations_count: "u32", + scheduled_revocations_total: "Balance", + status: "NominatorStatus", + }, Bond: { owner: "AccountId", amount: "Balance", @@ -680,7 +691,7 @@ export const moonbeamDefinitions = { state: "CollatorStatus", }, NominatorAdded: { - _enum: ["AddedToBottom", { AddedToTop: "Balance" }], + _enum: [{ AddedToTop: "Balance" }, "AddedToBottom"], }, CollatorSnapshot: { bond: "Balance", @@ -716,6 +727,12 @@ export const moonbeamDefinitions = { s: "H256", v: "U8", }, + ExitQ: { + candidates: "Vec", + nominators_leaving: "Vec", + candidate_schedule: "Vec<(AccountId, RoundIndex)>", + nominator_schedule: "Vec<(AccountId, Option, RoundIndex)>", + }, }, }, ], diff --git a/pallets/parachain-staking/Cargo.toml b/pallets/parachain-staking/Cargo.toml index 0e3554f883..b2a84caf68 100644 --- a/pallets/parachain-staking/Cargo.toml +++ b/pallets/parachain-staking/Cargo.toml @@ -1,33 +1,34 @@ [package] name = "parachain-staking" -version = "2.1.0" +version = "2.1.1" authors = ["PureStake"] edition = "2018" description = "parachain staking pallet for collator selection and reward distribution" [dependencies] -nimbus-primitives = { git = "https://github.com/purestake/cumulus", branch = "joshy-np098", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false, optional = true } frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false } -pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false } log = "0.4" +nimbus-primitives = { git = "https://github.com/purestake/cumulus", branch = "joshy-np098", default-features = false } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false } parity-scale-codec = { version = "2.0.0", default-features = false, features = ["derive"] } serde = { version = "1.0.101", optional = true } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false } substrate-fixed = { default-features = false, git = "https://github.com/encointer/substrate-fixed" } -frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false, optional = true } [dev-dependencies] -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false } +similar-asserts = "1.1.0" sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false } [features] default = ["std"] std = [ + "frame-benchmarking/std", "frame-support/std", "frame-system/std", - "frame-benchmarking/std", "nimbus-primitives/std", "pallet-balances/std", "parity-scale-codec/std", diff --git a/pallets/parachain-staking/README.md b/pallets/parachain-staking/README.md new file mode 100644 index 0000000000..3f343edddf --- /dev/null +++ b/pallets/parachain-staking/README.md @@ -0,0 +1,6 @@ +# DPoS Pallet for Parachain Staking + +## Formatting Rules + +- dependencies in alphabetical order in the `Cargo.toml` and at the top of each file +- prefer explicit imports to glob import syntax i.e. prefer `use::crate::{Ex1, Ex2, ..};` to `use super::*;` diff --git a/pallets/parachain-staking/src/benchmarks.rs b/pallets/parachain-staking/src/benchmarks.rs index a8ca061510..1a29b9159a 100644 --- a/pallets/parachain-staking/src/benchmarks.rs +++ b/pallets/parachain-staking/src/benchmarks.rs @@ -344,7 +344,7 @@ benchmarks! { } }: _(RawOrigin::Signed(caller.clone()), nomination_count) verify { - assert!(!Pallet::::is_nominator(&caller)); + assert!(Pallet::::nominator_state2(&caller).unwrap().is_leaving()); } revoke_nomination { @@ -363,9 +363,12 @@ benchmarks! { 0u32, 0u32 )?; - }: _(RawOrigin::Signed(caller.clone()), collator) + }: _(RawOrigin::Signed(caller.clone()), collator.clone()) verify { - assert!(!Pallet::::is_nominator(&caller)); + assert_eq!( + Pallet::::nominator_state2(&caller).unwrap().revocations.0[0], + collator + ); } nominator_bond_more { @@ -520,7 +523,7 @@ benchmarks! { // PREPARE RUN_TO_BLOCK LOOP let before_running_round_index = Pallet::::round().current; let round_length: T::BlockNumber = Pallet::::round().length.into(); - let reward_delay = <::BondDuration as Get>::get() + 2u32; + let reward_delay = <::RewardPaymentDelay as Get>::get() + 2u32; let mut now = >::block_number(); let mut counter = 0usize; let end = Pallet::::round().first + (round_length * reward_delay.into()); @@ -581,7 +584,7 @@ benchmarks! { #[cfg(test)] mod tests { - use super::*; + use crate::benchmarks::*; use crate::mock::Test; use frame_support::assert_ok; use sp_io::TestExternalities; diff --git a/pallets/parachain-staking/src/lib.rs b/pallets/parachain-staking/src/lib.rs index d1fa0800b6..665bc91f24 100644 --- a/pallets/parachain-staking/src/lib.rs +++ b/pallets/parachain-staking/src/lib.rs @@ -24,18 +24,18 @@ //! There is a new round every `>::get().length` blocks. //! //! At the start of every round, -//! * issuance is distributed to collators for `BondDuration` rounds ago -//! in proportion to the points they received in that round (for authoring blocks) -//! * queued collator exits are executed +//! * issuance is distributed to collators (and their nominators) for block authoring +//! `T::RewardPaymentDelay` rounds ago +//! * queued collator and nominator exits are executed //! * a new set of collators is chosen from the candidates //! //! To join the set of candidates, call `join_candidates` with `bond >= MinCollatorCandidateStk`. //! //! To leave the set of candidates, call `leave_candidates`. If the call succeeds, //! the collator is removed from the pool of candidates so they cannot be selected for future -//! collator sets, but they are not unstaked until `BondDuration` rounds later. The exit request is -//! stored in the `ExitQueue` and processed `BondDuration` rounds later to unstake the collator -//! and all of its nominations. +//! collator sets, but they are not unstaked until `T::LeaveCandidatesDelay` rounds later. +//! The exit request is stored in the `ExitQueue` and processed `T::LeaveCandidatesDelay` rounds +//! later to unstake the collator and all of its nominations. //! //! To join the set of nominators, call `nominate` and pass in an account that is //! already a collator candidate and `bond >= MinNominatorStk`. Each nominator can nominate up to @@ -65,8 +65,7 @@ pub use pallet::*; #[pallet] pub mod pallet { - use super::*; - use crate::set::OrderedSet; + use crate::{set::OrderedSet, InflationInfo, Range, WeightInfo}; use frame_support::pallet_prelude::*; use frame_support::traits::{Currency, Get, Imbalance, ReservableCurrency}; use frame_system::pallet_prelude::*; @@ -422,8 +421,8 @@ pub mod pallet { pub fn go_online(&mut self) { self.state = CollatorStatus::Active; } - pub fn leave_candidates(&mut self, round: RoundIndex) { - self.state = CollatorStatus::Leaving(round); + pub fn leave(&mut self, when: RoundIndex) { + self.state = CollatorStatus::Leaving(when); } } @@ -437,30 +436,86 @@ pub mod pallet { } } + #[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug)] + pub enum NominatorStatus { + /// Active with no scheduled exit + Active, + /// Schedule exit to revoke all ongoing nominations + Leaving(RoundIndex), + } + + #[derive(Encode, Decode, RuntimeDebug)] + /// Nominator state + pub struct Nominator2 { + /// All current nominations + pub nominations: OrderedSet>, + /// Nominations scheduled to be revoked + pub revocations: OrderedSet, + /// Total balance locked for this nominator + pub total: Balance, + /// Total number of revocations scheduled to be executed + pub scheduled_revocations_count: u32, + /// Total amount to be unbonded once revocations are executed + pub scheduled_revocations_total: Balance, + /// Status for this nominator + pub status: NominatorStatus, + } + #[derive(Encode, Decode, RuntimeDebug)] + /// DEPRECATED nominator state pub struct Nominator { pub nominations: OrderedSet>, pub total: Balance, } + impl From> + for Nominator2 + { + fn from(other: Nominator) -> Nominator2 { + Nominator2 { + nominations: other.nominations, + revocations: OrderedSet::new(), + total: other.total, + scheduled_revocations_count: 0u32, + scheduled_revocations_total: Zero::zero(), + status: NominatorStatus::Active, + } + } + } + impl< AccountId: Ord + Clone, Balance: Copy + sp_std::ops::AddAssign + sp_std::ops::Add + sp_std::ops::SubAssign - + PartialOrd, - > Nominator + + PartialOrd + + Zero, + > Nominator2 { pub fn new(collator: AccountId, amount: Balance) -> Self { - Nominator { + Nominator2 { nominations: OrderedSet::from(vec![Bond { owner: collator, amount, }]), + revocations: OrderedSet::new(), total: amount, + scheduled_revocations_count: 0u32, + scheduled_revocations_total: Zero::zero(), + status: NominatorStatus::Active, } } + pub fn is_active(&self) -> bool { + matches!(self.status, NominatorStatus::Active) + } + pub fn is_leaving(&self) -> bool { + matches!(self.status, NominatorStatus::Leaving(_)) + } + /// Set nominator status to exit + pub fn leave(&mut self, when: RoundIndex) { + self.status = NominatorStatus::Leaving(when) + } pub fn add_nomination(&mut self, bond: Bond) -> bool { let amt = bond.amount; if self.nominations.insert(bond) { @@ -595,6 +650,59 @@ pub mod pallet { } } + #[derive(Encode, Decode, RuntimeDebug, Default)] + /// Store and process all delayed exits by collators and nominators + pub struct ExitQ { + /// Candidate exit set + pub candidates: OrderedSet, + /// Nominator exit set (does not include nominators that made `revoke` requests) + pub nominators_leaving: OrderedSet, + /// [Candidate, Round to Exit] + pub candidate_schedule: Vec<(AccountId, RoundIndex)>, + /// [Nominator, Some(ValidatorId) || None => All Nominations, Round To Exit] + pub nominator_schedule: Vec<(AccountId, Option, RoundIndex)>, + } + + impl ExitQ { + /// Schedule to leave the set of candidates and return all ongoing nominations + pub fn schedule_candidate_exit( + &mut self, + candidate: A, + exit_round: RoundIndex, + ) -> DispatchResult { + ensure!( + self.candidates.insert(candidate.clone()), + Error::::CandidateAlreadyLeaving + ); + self.candidate_schedule.push((candidate, exit_round)); + Ok(()) + } + /// Schedule to leave the set of nominators and revoke all ongoing nominations + pub fn schedule_nominator_exit( + &mut self, + nominator: A, + exit_round: RoundIndex, + ) -> DispatchResult { + ensure!( + self.nominators_leaving.insert(nominator.clone()), + Error::::NominatorAlreadyLeaving + ); + self.nominator_schedule.push((nominator, None, exit_round)); + Ok(()) + } + /// Schedule to revoke a single nomination + pub fn schedule_nomination_revocation( + &mut self, + nominator: A, + collator: A, + exit_round: RoundIndex, + ) -> DispatchResult { + self.nominator_schedule + .push((nominator, Some(collator), exit_round)); + Ok(()) + } + } + type RoundIndex = u32; type RewardPoint = u32; pub type BalanceOf = @@ -614,7 +722,13 @@ pub mod pallet { /// Default number of blocks per round at genesis type DefaultBlocksPerRound: Get; /// Number of rounds that collators remain bonded before exit request is executed - type BondDuration: Get; + type LeaveCandidatesDelay: Get; + /// Number of rounds that nominators remain bonded before exit request is executed + type LeaveNominatorsDelay: Get; + /// Number of rounds that nominations remain bonded before revocation request is executed + type RevokeNominationDelay: Get; + /// Number of rounds after which block authors are rewarded + type RewardPaymentDelay: Get; /// Minimum number of selected candidates every round type MinSelectedCandidates: Get; /// Maximum nominators counted per collator @@ -642,6 +756,7 @@ pub mod pallet { // Nominator Does Not Exist NominatorDNE, CandidateDNE, + NominationDNE, NominatorExists, CandidateExists, ValBondBelowMin, @@ -649,11 +764,13 @@ pub mod pallet { NominationBelowMin, AlreadyOffline, AlreadyActive, - AlreadyLeaving, - CannotActivateIfLeaving, + NominatorAlreadyLeaving, + NominationAlreadyRevoked, + CandidateAlreadyLeaving, + CannotActBecauseLeaving, + CannotActBecauseRevoking, ExceedMaxCollatorsPerNom, AlreadyNominatedCollator, - NominationDNE, InvalidSchedule, CannotSetBelowMin, NoWritingSameValue, @@ -687,6 +804,10 @@ pub mod pallet { NominationIncreased(T::AccountId, T::AccountId, BalanceOf, bool, BalanceOf), // Nominator, Collator, Old Nomination, Counted in Top, New Nomination NominationDecreased(T::AccountId, T::AccountId, BalanceOf, bool, BalanceOf), + /// Round, Nominator, Scheduled Exit + NominatorExitScheduled(RoundIndex, T::AccountId, RoundIndex), + /// Round, Nominator, Collator, Scheduled Exit + NominationRevocationScheduled(RoundIndex, T::AccountId, T::AccountId, RoundIndex), /// Nominator, Amount Unstaked NominatorLeft(T::AccountId, BalanceOf), /// Nominator, Amount Locked, Collator, Nominator Position with New Total Backing if in Top @@ -724,19 +845,62 @@ pub mod pallet { Perbill, Perbill, ), + /// Migrated NominatorState -> NominatorState2, ExitQueue -> ExitQueue2 + DelayNominationExitsMigrationExecuted, + } + + /// Storage migration for delaying nomination exits and revocations + fn delay_nomination_exits_migration_execution() -> (u64, u64) { + if !>::get() { + // migrate from Nominator -> Nominator2 + let (mut reads, mut writes) = (0u64, 0u64); + for (acc, nominator_state) in NominatorState::::drain() { + let state: Nominator2> = nominator_state.into(); + >::insert(acc, state); + reads += 1u64; + writes += 1u64; + } + // migrate from ExitQueue -> ExitQueue2 + let just_collators_exit_queue = >::take(); + let mut candidates: Vec = Vec::new(); + for (acc, _) in just_collators_exit_queue.clone().into_iter() { + candidates.push(acc); + } + reads += 1u64; + writes += 1u64; + >::put(ExitQ { + candidates: candidates.into(), + nominators_leaving: OrderedSet::new(), + candidate_schedule: just_collators_exit_queue, + nominator_schedule: Vec::new(), + }); + >::put(true); + Pallet::::deposit_event(Event::DelayNominationExitsMigrationExecuted); + (reads, writes) + } else { + (1u64, 0u64) + } } #[pallet::hooks] impl Hooks> for Pallet { + fn on_runtime_upgrade() -> Weight { + let (reads, writes) = delay_nomination_exits_migration_execution::(); + ::DbWeight::get().reads(reads) + + ::DbWeight::get().writes(writes) + + 5_000_000_000 // 1% of the max block weight, to account for computation + } fn on_initialize(n: T::BlockNumber) -> Weight { let mut round = >::get(); if round.should_update(n) { // mutate round round.update(n); - // pay all stakers for T::BondDuration rounds ago + // pay all stakers for T::RewardPaymentDelay rounds ago Self::pay_stakers(round.current); // execute all delayed collator exits - Self::execute_delayed_collator_exits(round.current); + Self::execute_collator_exits(round.current); + // execute all delayed nominator exits + Self::execute_nominator_exits(round.current); // select top collator candidates for next round let (collator_count, nomination_count, total_staked) = Self::select_top_candidates(round.current); @@ -757,6 +921,11 @@ pub mod pallet { } } + #[pallet::storage] + #[pallet::getter(fn delay_nomination_exits_migration)] + /// True if executed, false by default + type DelayNominationExitsMigration = StorageValue<_, bool, ValueQuery>; + #[pallet::storage] #[pallet::getter(fn collator_commission)] /// Commission percent taken off of rewards for all collators @@ -780,6 +949,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn nominator_state)] + /// DEPRECATED AFTER `DelayNominationExitsMigration` migration is executed /// Get nominator state associated with an account if account is nominating else None type NominatorState = StorageMap< _, @@ -789,6 +959,17 @@ pub mod pallet { OptionQuery, >; + #[pallet::storage] + #[pallet::getter(fn nominator_state2)] + /// Get nominator state associated with an account if account is nominating else None + type NominatorState2 = StorageMap< + _, + Twox64Concat, + T::AccountId, + Nominator2>, + OptionQuery, + >; + #[pallet::storage] #[pallet::getter(fn collator_state2)] /// Get collator state associated with an account if account is collating else None @@ -818,9 +999,14 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn exit_queue)] - /// A queue of collators awaiting exit `BondDuration` delay after request - type ExitQueue = - StorageValue<_, OrderedSet>, ValueQuery>; + /// DEPRECATED + /// A queue of collators awaiting exit + type ExitQueue = StorageValue<_, Vec<(T::AccountId, RoundIndex)>, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn exit_queue2)] + /// A queue of collators and nominators awaiting exit + type ExitQueue2 = StorageValue<_, ExitQ, ValueQuery>; #[pallet::storage] #[pallet::getter(fn at_stake)] @@ -898,11 +1084,7 @@ pub mod pallet { balance, candidate_count, ) { - log::trace!( - target: "staking", - "Join candidates failed in genesis with error {:?}", - error - ); + log::warn!("Join candidates failed in genesis with error {:?}", error); } else { candidate_count += 1u32; } @@ -932,11 +1114,7 @@ pub mod pallet { cn_count, nn_count, ) { - log::trace!( - target: "staking", - "Join nominators failed in genesis with error {:?}", - error - ); + log::warn!("Join nominators failed in genesis with error {:?}", error); } else { if let Some(x) = col_nominator_count.get_mut(&target) { *x += 1u32; @@ -1160,7 +1338,7 @@ pub mod pallet { } /// Request to leave the set of candidates. If successful, the account is immediately /// removed from the candidate pool to prevent selection as a collator, but unbonding is - /// executed with a delay of `BondDuration` rounds. + /// executed with a delay of `T::LeaveCandidates` rounds. #[pallet::weight(::WeightInfo::leave_candidates(*candidate_count))] pub fn leave_candidates( origin: OriginFor, @@ -1168,18 +1346,12 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let collator = ensure_signed(origin)?; let mut state = >::get(&collator).ok_or(Error::::CandidateDNE)?; - ensure!(!state.is_leaving(), Error::::AlreadyLeaving); - let mut exits = >::get(); + ensure!(!state.is_leaving(), Error::::CandidateAlreadyLeaving); + let mut exits = >::get(); let now = >::get().current; - let when = now + T::BondDuration::get(); - ensure!( - exits.insert(Bond { - owner: collator.clone(), - amount: when - }), - Error::::AlreadyLeaving - ); - state.leave_candidates(when); + let when = now + T::LeaveCandidatesDelay::get(); + exits.schedule_candidate_exit::(collator.clone(), when)?; + state.leave(when); let mut candidates = >::get(); ensure!( candidate_count >= candidates.0.len() as u32, @@ -1188,7 +1360,7 @@ pub mod pallet { if candidates.remove(&Bond::from_owner(collator.clone())) { >::put(candidates); } - >::put(exits); + >::put(exits); >::insert(&collator, state); Self::deposit_event(Event::CollatorScheduledExit(now, collator, when)); Ok(().into()) @@ -1217,7 +1389,7 @@ pub mod pallet { let collator = ensure_signed(origin)?; let mut state = >::get(&collator).ok_or(Error::::CandidateDNE)?; ensure!(!state.is_active(), Error::::AlreadyActive); - ensure!(!state.is_leaving(), Error::::CannotActivateIfLeaving); + ensure!(!state.is_leaving(), Error::::CannotActBecauseLeaving); state.go_online(); let mut candidates = >::get(); ensure!( @@ -1243,7 +1415,7 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let collator = ensure_signed(origin)?; let mut state = >::get(&collator).ok_or(Error::::CandidateDNE)?; - ensure!(!state.is_leaving(), Error::::CannotActivateIfLeaving); + ensure!(!state.is_leaving(), Error::::CannotActBecauseLeaving); T::Currency::reserve(&collator, more)?; let before = state.bond; state.bond_more(more); @@ -1265,7 +1437,7 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let collator = ensure_signed(origin)?; let mut state = >::get(&collator).ok_or(Error::::CandidateDNE)?; - ensure!(!state.is_leaving(), Error::::CannotActivateIfLeaving); + ensure!(!state.is_leaving(), Error::::CannotActBecauseLeaving); let before = state.bond; let after = state.bond_less(less).ok_or(Error::::ValBondBelowMin)?; ensure!( @@ -1298,28 +1470,30 @@ pub mod pallet { nomination_count: u32, ) -> DispatchResultWithPostInfo { let acc = ensure_signed(origin)?; - let nominator = if let Some(mut nom) = >::get(&acc) { + let nominator = if let Some(mut state) = >::get(&acc) { + ensure!(state.is_active(), Error::::CannotActBecauseLeaving); // nomination after first ensure!( amount >= T::MinNomination::get(), Error::::NominationBelowMin ); ensure!( - nomination_count >= nom.nominations.0.len() as u32, + nomination_count >= state.nominations.0.len() as u32, Error::::TooLowNominationCountToNominate ); ensure!( - (nom.nominations.0.len() as u32) < T::MaxCollatorsPerNominator::get(), + (state.nominations.0.len() as u32) < T::MaxCollatorsPerNominator::get(), Error::::ExceedMaxCollatorsPerNom ); + // ensure that nominator is not in the exit_queue ensure!( - nom.add_nomination(Bond { + state.add_nomination(Bond { owner: collator.clone(), amount }), Error::::AlreadyNominatedCollator ); - nom + state } else { // first nomination ensure!( @@ -1327,7 +1501,7 @@ pub mod pallet { Error::::NomBondBelowMin ); ensure!(!Self::is_candidate(&acc), Error::::CandidateExists); - Nominator::new(collator.clone(), amount) + Nominator2::new(collator.clone(), amount) }; let mut state = >::get(&collator).ok_or(Error::::CandidateDNE)?; ensure!( @@ -1344,36 +1518,99 @@ pub mod pallet { let new_total_locked = >::get() + amount; >::put(new_total_locked); >::insert(&collator, state); - >::insert(&acc, nominator); + >::insert(&acc, nominator); Self::deposit_event(Event::Nomination(acc, amount, collator, nominator_position)); Ok(().into()) } - /// Leave the set of nominators and, by implication, revoke all ongoing nominations + /// Request to leave the set of nominators. If successful, the nominator is scheduled + /// to exit #[pallet::weight(::WeightInfo::leave_nominators(*nomination_count))] pub fn leave_nominators( origin: OriginFor, nomination_count: u32, ) -> DispatchResultWithPostInfo { let acc = ensure_signed(origin)?; - let nominator = >::get(&acc).ok_or(Error::::NominatorDNE)?; + let mut state = >::get(&acc).ok_or(Error::::NominatorDNE)?; + ensure!(!state.is_leaving(), Error::::NominatorAlreadyLeaving); ensure!( - nomination_count >= (nominator.nominations.0.len() as u32), + nomination_count >= (state.nominations.0.len() as u32), Error::::TooLowNominationCountToLeaveNominators ); - for bond in nominator.nominations.0 { - Self::nominator_leaves_collator(acc.clone(), bond.owner.clone())?; - } - >::remove(&acc); - Self::deposit_event(Event::NominatorLeft(acc, nominator.total)); + let mut exits = >::get(); + let now = >::get().current; + let when = now + T::LeaveNominatorsDelay::get(); + exits.schedule_nominator_exit::(acc.clone(), when)?; + state.leave(when); + state.scheduled_revocations_total = state.total; + state.scheduled_revocations_count = state.nominations.0.len() as u32; + >::put(exits); + >::insert(&acc, state); + Self::deposit_event(Event::NominatorExitScheduled(now, acc, when)); Ok(().into()) } - /// Revoke an existing nomination + /// Request to revoke an existing nomination. If successful, the nomination is scheduled + /// to exit #[pallet::weight(::WeightInfo::revoke_nomination())] pub fn revoke_nomination( origin: OriginFor, collator: T::AccountId, ) -> DispatchResultWithPostInfo { - Self::nominator_revokes_collator(ensure_signed(origin)?, collator) + let nominator = ensure_signed(origin)?; + let mut state = + >::get(&nominator).ok_or(Error::::NominatorDNE)?; + ensure!(state.is_active(), Error::::CannotActBecauseLeaving); + ensure!( + state.revocations.insert(collator.clone()), + Error::::NominationAlreadyRevoked + ); + let mut nomination_amount: Option> = None; + for Bond { owner, amount } in state.nominations.0.iter() { + if owner == &collator { + nomination_amount = Some(*amount); + break; + } + } + // Ensure that the collator exists in the nominations + let amount = nomination_amount.ok_or(Error::::NominationDNE)?; + let remaining = state.total - state.scheduled_revocations_total - amount; + let leaving = + if state.nominations.0.len() as u32 - state.scheduled_revocations_count < 2 { + true + } else { + ensure!( + remaining >= T::MinNominatorStk::get(), + Error::::NomBondBelowMin + ); + false + }; + let mut exits = >::get(); + let now = >::get().current; + let when = now + T::RevokeNominationDelay::get(); + if leaving { + // schedule to leave the set of nominators if this is the only nomination + exits.schedule_nominator_exit::(nominator.clone(), when)?; + state.leave(when); + state.scheduled_revocations_total = state.total; + state.scheduled_revocations_count = state.nominations.0.len() as u32; + >::put(exits); + >::insert(&nominator, state); + Self::deposit_event(Event::NominatorExitScheduled(now, nominator, when)); + } else { + // schedule to revoke this nomination + exits.schedule_nomination_revocation::( + nominator.clone(), + collator.clone(), + when, + )?; + state.scheduled_revocations_total += amount; + state.scheduled_revocations_count += 1u32; + >::put(exits); + >::insert(&nominator, state); + Self::deposit_event(Event::NominationRevocationScheduled( + now, nominator, collator, when, + )); + } + Ok(().into()) } /// Bond more for nominators with respect to a specific collator candidate #[pallet::weight(::WeightInfo::nominator_bond_more())] @@ -1383,12 +1620,17 @@ pub mod pallet { more: BalanceOf, ) -> DispatchResultWithPostInfo { let nominator = ensure_signed(origin)?; - let mut nominations = - >::get(&nominator).ok_or(Error::::NominatorDNE)?; + let mut state = + >::get(&nominator).ok_or(Error::::NominatorDNE)?; + ensure!(state.is_active(), Error::::CannotActBecauseLeaving); + ensure!( + !state.revocations.contains(&candidate), + Error::::CannotActBecauseRevoking + ); let mut collator = >::get(&candidate).ok_or(Error::::CandidateDNE)?; ensure!( - nominations.inc_nomination(candidate.clone(), more), + state.inc_nomination(candidate.clone(), more), Error::::NominationDNE ); T::Currency::reserve(&nominator, more)?; @@ -1399,7 +1641,7 @@ pub mod pallet { Self::update_active(candidate.clone(), after); } >::insert(&candidate, collator); - >::insert(&nominator, nominations); + >::insert(&nominator, state); let new_total_staked = >::get().saturating_add(more); >::put(new_total_staked); Self::deposit_event(Event::NominationIncreased( @@ -1415,11 +1657,16 @@ pub mod pallet { less: BalanceOf, ) -> DispatchResultWithPostInfo { let nominator = ensure_signed(origin)?; - let mut nominations = - >::get(&nominator).ok_or(Error::::NominatorDNE)?; + let mut state = + >::get(&nominator).ok_or(Error::::NominatorDNE)?; + ensure!(state.is_active(), Error::::CannotActBecauseLeaving); + ensure!( + !state.revocations.contains(&candidate), + Error::::CannotActBecauseRevoking + ); let mut collator = >::get(&candidate).ok_or(Error::::CandidateDNE)?; - let remaining = nominations + let remaining = state .dec_nomination(candidate.clone(), less) .ok_or(Error::::NominationDNE)? .ok_or(Error::::NomBondBelowMin)?; @@ -1428,7 +1675,7 @@ pub mod pallet { Error::::NominationBelowMin ); ensure!( - nominations.total >= T::MinNominatorStk::get(), + state.total >= T::MinNominatorStk::get(), Error::::NomBondBelowMin ); T::Currency::unreserve(&nominator, less); @@ -1439,7 +1686,7 @@ pub mod pallet { Self::update_active(candidate.clone(), after); } >::insert(&candidate, collator); - >::insert(&nominator, nominations); + >::insert(&nominator, state); let new_total_staked = >::get().saturating_sub(less); >::put(new_total_staked); Self::deposit_event(Event::NominationDecreased( @@ -1451,7 +1698,7 @@ pub mod pallet { impl Pallet { pub fn is_nominator(acc: &T::AccountId) -> bool { - >::get(acc).is_some() + >::get(acc).is_some() } pub fn is_candidate(acc: &T::AccountId) -> bool { >::get(acc).is_some() @@ -1482,31 +1729,6 @@ pub mod pallet { round_issuance.ideal } } - fn nominator_revokes_collator( - acc: T::AccountId, - collator: T::AccountId, - ) -> DispatchResultWithPostInfo { - let mut nominator = >::get(&acc).ok_or(Error::::NominatorDNE)?; - let old_total = nominator.total; - let remaining = nominator - .rm_nomination(collator.clone()) - .ok_or(Error::::NominationDNE)?; - // edge case; if no nominations remaining, leave set of nominators - if nominator.nominations.0.len().is_zero() { - // leave the set of nominators because no nominations left - Self::nominator_leaves_collator(acc.clone(), collator)?; - >::remove(&acc); - Self::deposit_event(Event::NominatorLeft(acc, old_total)); - return Ok(().into()); - } - ensure!( - remaining >= T::MinNominatorStk::get(), - Error::::NomBondBelowMin - ); - Self::nominator_leaves_collator(acc.clone(), collator)?; - >::insert(&acc, nominator); - Ok(().into()) - } fn nominator_leaves_collator( nominator: T::AccountId, collator: T::AccountId, @@ -1531,7 +1753,7 @@ pub mod pallet { } fn pay_stakers(next: RoundIndex) { // payout is next - duration rounds ago => next - duration > 0 else return early - let duration = T::BondDuration::get(); + let duration = T::RewardPaymentDelay::get(); if next <= duration { return; } @@ -1585,29 +1807,37 @@ pub mod pallet { } } } - fn execute_delayed_collator_exits(next: RoundIndex) { - let remain_exits = >::get() - .0 + /// Executes all collator exits scheduled for when <= now + fn execute_collator_exits(now: RoundIndex) { + let mut exit_queue = >::get(); + let remaining_exits = exit_queue + .candidate_schedule + .clone() .into_iter() - .filter_map(|x| { - if x.amount > next { - Some(x) + .filter_map(|(who, when)| { + if when > now { + Some((who, when)) } else { - if let Some(state) = >::get(&x.owner) { + if !exit_queue.candidates.remove(&who) { + log::warn!( + "Candidates set removal failed, CollatorState had inconsistency!", + ); + } + if let Some(state) = >::get(&who) { // return stake to nominator let return_stake = |bond: Bond>| { T::Currency::unreserve(&bond.owner, bond.amount); // remove nomination from nominator state - let mut nominator = NominatorState::::get(&bond.owner).expect( + let mut nominator = NominatorState2::::get(&bond.owner).expect( "Collator state and nominator state are consistent. Collator state has a record of this nomination. Therefore, Nominator state also has a record. qed.", ); - if let Some(remaining) = nominator.rm_nomination(x.owner.clone()) { + if let Some(remaining) = nominator.rm_nomination(who.clone()) { if remaining.is_zero() { - >::remove(&bond.owner); + >::remove(&bond.owner); } else { - >::insert(&bond.owner, nominator); + >::insert(&bond.owner, nominator); } } }; @@ -1621,12 +1851,12 @@ pub mod pallet { } // return stake to collator T::Currency::unreserve(&state.id, state.bond); - >::remove(&x.owner); + >::remove(&who); let new_total_staked = >::get().saturating_sub(state.total_backing); >::put(new_total_staked); Self::deposit_event(Event::CollatorLeft( - x.owner, + who, state.total_backing, new_total_staked, )); @@ -1634,8 +1864,78 @@ pub mod pallet { None } }) - .collect::>>(); - >::put(OrderedSet::from(remain_exits)); + .collect::>(); + exit_queue.candidate_schedule = remaining_exits; + >::put(exit_queue); + } + /// Executes all nominator exits for when <= now + fn execute_nominator_exits(now: RoundIndex) { + let mut exit_queue = >::get(); + let remaining_exits = exit_queue + .nominator_schedule + .clone() + .into_iter() + .filter_map(|(nominator, maybe_collator, when)| { + if when > now { + Some((nominator, maybe_collator, when)) + } else { + if let Some(collator) = maybe_collator { + // single revocation needs to be executed + if let Some(mut state) = >::get(&nominator) { + let pre_total = state.total; + if let Some(remaining) = state.rm_nomination(collator.clone()) { + let amount = pre_total - remaining; + state.scheduled_revocations_total -= amount; + state.scheduled_revocations_count -= 1u32; + state.revocations.remove(&collator); + let _ = Self::nominator_leaves_collator( + nominator.clone(), + collator, + ); + >::insert(&nominator, state); + } + } else { + log::warn!( + "Nominator State for Nominator {:?} Not Found During Revocation + of Support for Collator {:?}", + nominator, + collator, + ); + } + } else { + if !exit_queue.nominators_leaving.remove(&nominator) { + log::warn!( + "Nominators set removal failed, + NominatorState had inconsistency!", + ); + } + if let Some(state) = >::get(&nominator) { + for bond in state.nominations.0 { + if let Err(error) = Self::nominator_leaves_collator( + nominator.clone(), + bond.owner.clone(), + ) { + log::warn!( + "Nominator leaves collator failed with error: {:?}", + error + ); + } + } + >::remove(&nominator); + Self::deposit_event(Event::NominatorLeft(nominator, state.total)); + } else { + log::warn!( + "Nominator State Not Found During Exit for Nominator {:?}", + nominator + ); + } + } + None + } + }) + .collect::, RoundIndex)>>(); + exit_queue.nominator_schedule = remaining_exits; + >::put(exit_queue); } /// Best as in most cumulatively supported in terms of stake /// Returns [collator_count, nomination_count, total staked] diff --git a/pallets/parachain-staking/src/mock.rs b/pallets/parachain-staking/src/mock.rs index dae7c0f48e..ca4d8ac712 100644 --- a/pallets/parachain-staking/src/mock.rs +++ b/pallets/parachain-staking/src/mock.rs @@ -15,8 +15,8 @@ // along with Moonbeam. If not, see . //! Test utilities -use super::*; use crate as stake; +use crate::{pallet, AwardedPts, Config, InflationInfo, Points, Range}; use frame_support::{ construct_runtime, parameter_types, traits::{GenesisBuild, OnFinalize, OnInitialize}, @@ -99,7 +99,10 @@ impl pallet_balances::Config for Test { parameter_types! { pub const MinBlocksPerRound: u32 = 3; pub const DefaultBlocksPerRound: u32 = 5; - pub const BondDuration: u32 = 2; + pub const LeaveCandidatesDelay: u32 = 2; + pub const LeaveNominatorsDelay: u32 = 2; + pub const RevokeNominationDelay: u32 = 2; + pub const RewardPaymentDelay: u32 = 2; pub const MinSelectedCandidates: u32 = 5; pub const MaxNominatorsPerCollator: u32 = 4; pub const MaxCollatorsPerNominator: u32 = 4; @@ -115,7 +118,10 @@ impl Config for Test { type MonetaryGovernanceOrigin = frame_system::EnsureRoot; type MinBlocksPerRound = MinBlocksPerRound; type DefaultBlocksPerRound = DefaultBlocksPerRound; - type BondDuration = BondDuration; + type LeaveCandidatesDelay = LeaveCandidatesDelay; + type LeaveNominatorsDelay = LeaveNominatorsDelay; + type RevokeNominationDelay = RevokeNominationDelay; + type RewardPaymentDelay = RewardPaymentDelay; type MinSelectedCandidates = MinSelectedCandidates; type MaxNominatorsPerCollator = MaxNominatorsPerCollator; type MaxCollatorsPerNominator = MaxCollatorsPerNominator; diff --git a/pallets/parachain-staking/src/tests.rs b/pallets/parachain-staking/src/tests.rs index 7ad4c48b69..97b8842329 100644 --- a/pallets/parachain-staking/src/tests.rs +++ b/pallets/parachain-staking/src/tests.rs @@ -29,6 +29,18 @@ use crate::{Bond, CollatorStatus, Error, Event, NominatorAdded, Range}; use frame_support::{assert_noop, assert_ok}; use sp_runtime::{traits::Zero, DispatchError, Perbill, Percent}; +/// Prints the diff iff assert_eq fails, should only be used for debugging purposes +#[macro_export] +macro_rules! asserts_eq { + ($left:expr, $right:expr) => { + match (&$left, &$right) { + (left_val, right_val) => { + similar_asserts::assert_eq!(*left_val, *right_val); + } + } + }; +} + // ~~ ROOT ~~ #[test] @@ -789,7 +801,7 @@ fn cannot_leave_candidates_if_already_leaving_candidates() { assert_ok!(Stake::leave_candidates(Origin::signed(1), 1u32)); assert_noop!( Stake::leave_candidates(Origin::signed(1), 1u32), - Error::::AlreadyLeaving + Error::::CandidateAlreadyLeaving ); }); } @@ -1097,7 +1109,7 @@ fn cannot_candidate_bond_more_if_leaving_candidates() { assert_ok!(Stake::leave_candidates(Origin::signed(1), 1)); assert_noop!( Stake::candidate_bond_more(Origin::signed(1), 30), - Error::::CannotActivateIfLeaving + Error::::CannotActBecauseLeaving ); }); } @@ -1239,7 +1251,7 @@ fn cannot_candidate_bond_less_if_leaving_candidates() { assert_ok!(Stake::leave_candidates(Origin::signed(1), 1)); assert_noop!( Stake::candidate_bond_less(Origin::signed(1), 10), - Error::::CannotActivateIfLeaving + Error::::CannotActBecauseLeaving ); }); } @@ -1305,9 +1317,9 @@ fn nominate_updates_nominator_state() { .with_candidates(vec![(1, 30)]) .build() .execute_with(|| { - assert!(Stake::nominator_state(2).is_none()); + assert!(Stake::nominator_state2(2).is_none()); assert_ok!(Stake::nominate(Origin::signed(2), 1, 10, 0, 0)); - let nominator_state = Stake::nominator_state(2).expect("just nominated => exists"); + let nominator_state = Stake::nominator_state2(2).expect("just nominated => exists"); assert_eq!(nominator_state.total, 10); assert_eq!( nominator_state.nominations.0[0], @@ -1355,6 +1367,35 @@ fn can_nominate_immediately_after_other_join_candidates() { }); } +#[test] +fn can_nominate_if_revoking() { + ExtBuilder::default() + .with_balances(vec![(1, 20), (2, 30), (3, 20), (4, 20)]) + .with_candidates(vec![(1, 20), (3, 20), (4, 20)]) + .with_nominations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::revoke_nomination(Origin::signed(2), 1)); + assert_ok!(Stake::nominate(Origin::signed(2), 4, 10, 0, 2)); + }); +} + +#[test] +fn cannot_nominate_if_leaving() { + ExtBuilder::default() + .with_balances(vec![(1, 20), (2, 20)]) + .with_candidates(vec![(1, 20)]) + .with_nominations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::leave_nominators(Origin::signed(2), 1)); + assert_noop!( + Stake::nominate(Origin::signed(2), 1, 10, 0, 0), + Error::::CannotActBecauseLeaving + ); + }); +} + #[test] fn cannot_nominate_if_candidate() { ExtBuilder::default() @@ -1484,8 +1525,14 @@ fn leave_nominators_event_emits_correctly() { .with_nominations(vec![(2, 1, 10)]) .build() .execute_with(|| { + roll_to(1); assert_ok!(Stake::leave_nominators(Origin::signed(2), 1)); - assert_eq!(last_event(), MetaEvent::Stake(Event::NominatorLeft(2, 10,)),); + assert_eq!( + last_event(), + MetaEvent::Stake(Event::NominatorExitScheduled(1, 2, 3)) + ); + roll_to(10); + assert!(events().contains(&Event::NominatorLeft(2, 10))); }); } @@ -1497,9 +1544,11 @@ fn leave_nominators_unreserves_balance() { .with_nominations(vec![(2, 1, 10)]) .build() .execute_with(|| { + roll_to(1); assert_eq!(Balances::reserved_balance(&2), 10); assert_eq!(Balances::free_balance(&2), 0); assert_ok!(Stake::leave_nominators(Origin::signed(2), 1)); + roll_to(10); assert_eq!(Balances::reserved_balance(&2), 0); assert_eq!(Balances::free_balance(&2), 10); }); @@ -1513,8 +1562,10 @@ fn leave_nominators_decreases_total_staked() { .with_nominations(vec![(2, 1, 10)]) .build() .execute_with(|| { + roll_to(1); assert_eq!(Stake::total(), 40); assert_ok!(Stake::leave_nominators(Origin::signed(2), 1)); + roll_to(10); assert_eq!(Stake::total(), 30); }); } @@ -1527,9 +1578,11 @@ fn leave_nominators_removes_nominator_state() { .with_nominations(vec![(2, 1, 10)]) .build() .execute_with(|| { - assert!(Stake::nominator_state(2).is_some()); + roll_to(1); + assert!(Stake::nominator_state2(2).is_some()); assert_ok!(Stake::leave_nominators(Origin::signed(2), 1)); - assert!(Stake::nominator_state(2).is_none()); + roll_to(10); + assert!(Stake::nominator_state2(2).is_none()); }); } @@ -1541,6 +1594,7 @@ fn leave_nominators_removes_nominations_from_collator_state() { .with_nominations(vec![(1, 2, 10), (1, 3, 10), (1, 4, 10), (1, 5, 10)]) .build() .execute_with(|| { + roll_to(1); for i in 2..6 { let candidate_state = Stake::collator_state2(i).expect("initialized in ext builder"); @@ -1555,10 +1609,11 @@ fn leave_nominators_removes_nominations_from_collator_state() { assert_eq!(candidate_state.total_backing, 30); } assert_eq!( - Stake::nominator_state(1).unwrap().nominations.0.len(), + Stake::nominator_state2(1).unwrap().nominations.0.len(), 4usize ); assert_ok!(Stake::leave_nominators(Origin::signed(1), 10)); + roll_to(10); for i in 2..6 { let candidate_state = Stake::collator_state2(i).expect("initialized in ext builder"); @@ -1569,6 +1624,39 @@ fn leave_nominators_removes_nominations_from_collator_state() { }); } +#[test] +fn cannot_leave_nominators_if_leaving_through_revoking_last_nomination() { + ExtBuilder::default() + .with_balances(vec![(1, 30), (2, 20), (3, 20)]) + .with_candidates(vec![(1, 30), (3, 20)]) + .with_nominations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::revoke_nomination(Origin::signed(2), 1)); + assert_ok!(Stake::revoke_nomination(Origin::signed(2), 3)); + assert_noop!( + Stake::leave_nominators(Origin::signed(2), 2), + Error::::NominatorAlreadyLeaving + ); + }); +} + +#[test] +fn cannot_leave_nominators_if_already_leaving() { + ExtBuilder::default() + .with_balances(vec![(1, 30), (2, 10)]) + .with_candidates(vec![(1, 30)]) + .with_nominations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::leave_nominators(Origin::signed(2), 1)); + assert_noop!( + Stake::leave_nominators(Origin::signed(2), 1), + Error::::NominatorAlreadyLeaving + ); + }); +} + #[test] fn cannot_leave_nominators_if_not_nominator() { ExtBuilder::default() @@ -1617,7 +1705,7 @@ fn sufficient_leave_nominators_weight_hint_succeeds() { // REVOKE_NOMINATION #[test] -fn revoke_nomination_event_emits_correctly() { +fn revoke_nomination_event_emits_exit_scheduled_if_no_nominations_left() { // last nomination is revocation ExtBuilder::default() .with_balances(vec![(1, 30), (2, 10)]) @@ -1625,24 +1713,34 @@ fn revoke_nomination_event_emits_correctly() { .with_nominations(vec![(2, 1, 10)]) .build() .execute_with(|| { + roll_to(1); assert_ok!(Stake::revoke_nomination(Origin::signed(2), 1)); assert_eq!( - events(), - vec![ - Event::NominatorLeftCollator(2, 1, 10, 30), - Event::NominatorLeft(2, 10,), - ] + last_event(), + MetaEvent::Stake(Event::NominatorExitScheduled(1, 2, 3)) ); + roll_to(10); + assert!(events().contains(&Event::NominatorLeftCollator(2, 1, 10, 30))); + assert!(events().contains(&Event::NominatorLeft(2, 10))); }); - // more nominations remaining after revocation +} + +#[test] +fn revoke_nomination_event_emits_correctly() { ExtBuilder::default() .with_balances(vec![(1, 30), (2, 20), (3, 30)]) .with_candidates(vec![(1, 30), (3, 30)]) .with_nominations(vec![(2, 1, 10), (2, 3, 10)]) .build() .execute_with(|| { + roll_to(1); assert_ok!(Stake::revoke_nomination(Origin::signed(2), 1)); - assert_eq!(events(), vec![Event::NominatorLeftCollator(2, 1, 10, 30)]); + assert_eq!( + last_event(), + MetaEvent::Stake(Event::NominationRevocationScheduled(1, 2, 1, 3)) + ); + roll_to(10); + assert!(events().contains(&Event::NominatorLeftCollator(2, 1, 10, 30))); }); } @@ -1654,14 +1752,56 @@ fn revoke_nomination_unreserves_balance() { .with_nominations(vec![(2, 1, 10)]) .build() .execute_with(|| { + roll_to(1); assert_eq!(Balances::reserved_balance(&2), 10); assert_eq!(Balances::free_balance(&2), 0); assert_ok!(Stake::revoke_nomination(Origin::signed(2), 1)); + roll_to(10); assert_eq!(Balances::reserved_balance(&2), 0); assert_eq!(Balances::free_balance(&2), 10); }); } +#[test] +fn revoke_nomination_adds_revocation_to_nominator_state() { + ExtBuilder::default() + .with_balances(vec![(1, 30), (2, 20), (3, 20)]) + .with_candidates(vec![(1, 30), (3, 20)]) + .with_nominations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert!(Stake::nominator_state2(2) + .expect("exists") + .revocations + .0 + .is_empty()); + assert_ok!(Stake::revoke_nomination(Origin::signed(2), 1)); + assert_eq!( + Stake::nominator_state2(2).expect("exists").revocations.0[0], + 1 + ); + }); +} + +#[test] +fn revoke_nomination_removes_revocation_from_nominator_state_upon_execution() { + ExtBuilder::default() + .with_balances(vec![(1, 30), (2, 20), (3, 20)]) + .with_candidates(vec![(1, 30), (3, 20)]) + .with_nominations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + roll_to(1); + assert_ok!(Stake::revoke_nomination(Origin::signed(2), 1)); + roll_to(10); + assert!(Stake::nominator_state2(2) + .expect("exists") + .revocations + .0 + .is_empty()); + }); +} + #[test] fn revoke_nomination_decreases_total_staked() { ExtBuilder::default() @@ -1670,8 +1810,10 @@ fn revoke_nomination_decreases_total_staked() { .with_nominations(vec![(2, 1, 10)]) .build() .execute_with(|| { + roll_to(1); assert_eq!(Stake::total(), 40); assert_ok!(Stake::revoke_nomination(Origin::signed(2), 1)); + roll_to(10); assert_eq!(Stake::total(), 30); }); } @@ -1684,9 +1826,11 @@ fn revoke_nomination_for_last_nomination_removes_nominator_state() { .with_nominations(vec![(2, 1, 10)]) .build() .execute_with(|| { - assert!(Stake::nominator_state(2).is_some()); + roll_to(1); + assert!(Stake::nominator_state2(2).is_some()); assert_ok!(Stake::revoke_nomination(Origin::signed(2), 1)); - assert!(Stake::nominator_state(2).is_none()); + roll_to(10); + assert!(Stake::nominator_state2(2).is_none()); }); } @@ -1698,6 +1842,7 @@ fn revoke_nomination_removes_nomination_from_candidate_state() { .with_nominations(vec![(2, 1, 10)]) .build() .execute_with(|| { + roll_to(1); assert_eq!( Stake::collator_state2(1) .expect("exists") @@ -1707,6 +1852,7 @@ fn revoke_nomination_removes_nomination_from_candidate_state() { 1usize ); assert_ok!(Stake::revoke_nomination(Origin::signed(2), 1)); + roll_to(10); assert!(Stake::collator_state2(1) .expect("exists") .nominators @@ -1715,6 +1861,35 @@ fn revoke_nomination_removes_nomination_from_candidate_state() { }); } +#[test] +fn can_revoke_nomination_if_revoking_another_nomination() { + ExtBuilder::default() + .with_balances(vec![(1, 30), (2, 20), (3, 20)]) + .with_candidates(vec![(1, 30), (3, 20)]) + .with_nominations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::revoke_nomination(Origin::signed(2), 1)); + assert_ok!(Stake::revoke_nomination(Origin::signed(2), 3)); + }); +} + +#[test] +fn cannot_revoke_if_leaving() { + ExtBuilder::default() + .with_balances(vec![(1, 30), (2, 20), (3, 20)]) + .with_candidates(vec![(1, 30)]) + .with_nominations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::leave_nominators(Origin::signed(2), 2)); + assert_noop!( + Stake::revoke_nomination(Origin::signed(2), 3), + Error::::CannotActBecauseLeaving + ); + }); +} + #[test] fn cannot_revoke_nomination_if_not_nominator() { ExtBuilder::default().build().execute_with(|| { @@ -1811,9 +1986,9 @@ fn nominator_bond_more_updates_nominator_state() { .with_nominations(vec![(2, 1, 10)]) .build() .execute_with(|| { - assert_eq!(Stake::nominator_state(2).expect("exists").total, 10); + assert_eq!(Stake::nominator_state2(2).expect("exists").total, 10); assert_ok!(Stake::nominator_bond_more(Origin::signed(2), 1, 5)); - assert_eq!(Stake::nominator_state(2).expect("exists").total, 15); + assert_eq!(Stake::nominator_state2(2).expect("exists").total, 15); }); } @@ -1857,6 +2032,38 @@ fn nominator_bond_more_increases_total() { }); } +#[test] +fn cannot_nominator_bond_more_if_leaving() { + ExtBuilder::default() + .with_balances(vec![(1, 30), (2, 15)]) + .with_candidates(vec![(1, 30)]) + .with_nominations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::leave_nominators(Origin::signed(2), 1)); + assert_noop!( + Stake::nominator_bond_more(Origin::signed(2), 1, 5), + Error::::CannotActBecauseLeaving + ); + }); +} + +#[test] +fn cannot_nominator_bond_more_if_revoking() { + ExtBuilder::default() + .with_balances(vec![(1, 30), (2, 25), (3, 20)]) + .with_candidates(vec![(1, 30), (3, 20)]) + .with_nominations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::revoke_nomination(Origin::signed(2), 1)); + assert_noop!( + Stake::nominator_bond_more(Origin::signed(2), 1, 5), + Error::::CannotActBecauseRevoking + ); + }); +} + #[test] fn cannot_nominator_bond_more_if_not_nominator() { ExtBuilder::default().build().execute_with(|| { @@ -1972,9 +2179,9 @@ fn nominator_bond_less_updates_nominator_state() { .with_nominations(vec![(2, 1, 10)]) .build() .execute_with(|| { - assert_eq!(Stake::nominator_state(2).expect("exists").total, 10); + assert_eq!(Stake::nominator_state2(2).expect("exists").total, 10); assert_ok!(Stake::nominator_bond_less(Origin::signed(2), 1, 5)); - assert_eq!(Stake::nominator_state(2).expect("exists").total, 5); + assert_eq!(Stake::nominator_state2(2).expect("exists").total, 5); }); } @@ -2018,6 +2225,38 @@ fn nominator_bond_less_decreases_total() { }); } +#[test] +fn cannot_nominator_bond_less_if_leaving() { + ExtBuilder::default() + .with_balances(vec![(1, 30), (2, 15)]) + .with_candidates(vec![(1, 30)]) + .with_nominations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::leave_nominators(Origin::signed(2), 1)); + assert_noop!( + Stake::nominator_bond_less(Origin::signed(2), 1, 1), + Error::::CannotActBecauseLeaving + ); + }); +} + +#[test] +fn cannot_nominator_bond_less_if_revoking() { + ExtBuilder::default() + .with_balances(vec![(1, 30), (2, 25), (3, 20)]) + .with_candidates(vec![(1, 30), (3, 20)]) + .with_nominations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::revoke_nomination(Origin::signed(2), 1)); + assert_noop!( + Stake::nominator_bond_less(Origin::signed(2), 1, 1), + Error::::CannotActBecauseRevoking + ); + }); +} + #[test] fn cannot_nominator_bond_less_if_not_nominator() { ExtBuilder::default().build().execute_with(|| { @@ -2105,6 +2344,48 @@ fn cannot_nominator_bond_less_below_min_nomination() { // ~~ PROPERTY-BASED TESTS ~~ +#[test] +fn nominator_schedule_revocation_total() { + ExtBuilder::default() + .with_balances(vec![(1, 20), (2, 40), (3, 20), (4, 20), (5, 20)]) + .with_candidates(vec![(1, 20), (3, 20), (4, 20), (5, 20)]) + .with_nominations(vec![(2, 1, 10), (2, 3, 10), (2, 4, 10)]) + .build() + .execute_with(|| { + roll_to(1); + assert_ok!(Stake::revoke_nomination(Origin::signed(2), 1)); + assert_eq!( + Stake::nominator_state2(2) + .expect("exists") + .scheduled_revocations_total, + 10 + ); + roll_to(10); + assert_eq!( + Stake::nominator_state2(2) + .expect("exists") + .scheduled_revocations_total, + 0 + ); + assert_ok!(Stake::nominate(Origin::signed(2), 5, 10, 0, 2)); + assert_ok!(Stake::revoke_nomination(Origin::signed(2), 3)); + assert_ok!(Stake::revoke_nomination(Origin::signed(2), 4)); + assert_eq!( + Stake::nominator_state2(2) + .expect("exists") + .scheduled_revocations_total, + 20 + ); + roll_to(20); + assert_eq!( + Stake::nominator_state2(2) + .expect("exists") + .scheduled_revocations_total, + 0 + ); + }); +} + #[test] fn parachain_bond_inflation_reserve_matches_config() { ExtBuilder::default() @@ -2177,68 +2458,63 @@ fn parachain_bond_inflation_reserve_matches_config() { // ~ set block author as 1 for all blocks this round set_author(3, 1, 100); set_author(4, 1, 100); + set_author(5, 1, 100); // 1. ensure nominators are paid for 2 rounds after they leave assert_noop!( Stake::leave_nominators(Origin::signed(66), 10), Error::::NominatorDNE ); assert_ok!(Stake::leave_nominators(Origin::signed(6), 10)); - roll_to(21); - // keep paying 6 (note: inflation is in terms of total issuance so that's why 1 is 21) + // fast forward to block in which nominator 6 exit executes + roll_to(25); let mut new2 = vec![ - Event::NominatorLeftCollator(6, 1, 10, 40), - Event::NominatorLeft(6, 10), + Event::NominatorExitScheduled(4, 6, 6), Event::ReservedForParachainBond(11, 16), Event::Rewarded(1, 19), Event::Rewarded(7, 6), Event::Rewarded(10, 6), Event::Rewarded(6, 6), + Event::CollatorChosen(5, 1, 50), Event::CollatorChosen(5, 2, 40), - Event::CollatorChosen(5, 1, 40), Event::CollatorChosen(5, 4, 20), Event::CollatorChosen(5, 3, 20), Event::CollatorChosen(5, 5, 10), - Event::NewRound(20, 5, 5, 130), + Event::NewRound(20, 5, 5, 140), + Event::ReservedForParachainBond(11, 16), + Event::Rewarded(1, 20), + Event::Rewarded(7, 6), + Event::Rewarded(10, 6), + Event::Rewarded(6, 6), + Event::NominatorLeftCollator(6, 1, 10, 40), + Event::NominatorLeft(6, 10), + Event::CollatorChosen(6, 2, 40), + Event::CollatorChosen(6, 1, 40), + Event::CollatorChosen(6, 4, 20), + Event::CollatorChosen(6, 3, 20), + Event::CollatorChosen(6, 5, 10), + Event::NewRound(25, 6, 5, 130), ]; expected.append(&mut new2); assert_eq!(events(), expected); - assert_eq!(Balances::free_balance(&11), 32); + assert_eq!(Balances::free_balance(&11), 48); assert_ok!(Stake::set_parachain_bond_reserve_percent( Origin::root(), Percent::from_percent(50) )); // 6 won't be paid for this round because they left already - set_author(5, 1, 100); - roll_to(26); + set_author(6, 1, 100); + roll_to(30); // keep paying 6 let mut new3 = vec![ Event::ParachainBondReservePercentSet( Percent::from_percent(30), Percent::from_percent(50), ), - Event::ReservedForParachainBond(11, 27), - Event::Rewarded(1, 15), - Event::Rewarded(7, 4), - Event::Rewarded(10, 4), - Event::Rewarded(6, 4), - Event::CollatorChosen(6, 2, 40), - Event::CollatorChosen(6, 1, 40), - Event::CollatorChosen(6, 4, 20), - Event::CollatorChosen(6, 3, 20), - Event::CollatorChosen(6, 5, 10), - Event::NewRound(25, 6, 5, 130), - ]; - expected.append(&mut new3); - assert_eq!(events(), expected); - assert_eq!(Balances::free_balance(&11), 59); - set_author(6, 1, 100); - roll_to(31); - // no more paying 6 - let mut new4 = vec![ Event::ReservedForParachainBond(11, 29), - Event::Rewarded(1, 17), - Event::Rewarded(7, 6), - Event::Rewarded(10, 6), + Event::Rewarded(1, 15), + Event::Rewarded(7, 5), + Event::Rewarded(10, 5), + Event::Rewarded(6, 5), Event::CollatorChosen(7, 2, 40), Event::CollatorChosen(7, 1, 40), Event::CollatorChosen(7, 4, 20), @@ -2246,33 +2522,33 @@ fn parachain_bond_inflation_reserve_matches_config() { Event::CollatorChosen(7, 5, 10), Event::NewRound(30, 7, 5, 130), ]; - expected.append(&mut new4); + expected.append(&mut new3); assert_eq!(events(), expected); - assert_eq!(Balances::free_balance(&11), 88); + assert_eq!(Balances::free_balance(&11), 77); set_author(7, 1, 100); - assert_ok!(Stake::nominate(Origin::signed(8), 1, 10, 10, 10)); - roll_to(36); - // new nomination is not rewarded yet - let mut new5 = vec![ - Event::Nomination(8, 10, 1, NominatorAdded::AddedToTop { new_total: 50 }), + roll_to(35); + // no more paying 6 + let mut new4 = vec![ Event::ReservedForParachainBond(11, 30), Event::Rewarded(1, 18), Event::Rewarded(7, 6), Event::Rewarded(10, 6), - Event::CollatorChosen(8, 1, 50), Event::CollatorChosen(8, 2, 40), + Event::CollatorChosen(8, 1, 40), Event::CollatorChosen(8, 4, 20), Event::CollatorChosen(8, 3, 20), Event::CollatorChosen(8, 5, 10), - Event::NewRound(35, 8, 5, 140), + Event::NewRound(35, 8, 5, 130), ]; - expected.append(&mut new5); + expected.append(&mut new4); assert_eq!(events(), expected); - assert_eq!(Balances::free_balance(&11), 118); + assert_eq!(Balances::free_balance(&11), 107); set_author(8, 1, 100); - roll_to(41); - // new nomination is still not rewarded yet - let mut new6 = vec![ + assert_ok!(Stake::nominate(Origin::signed(8), 1, 10, 10, 10)); + roll_to(40); + // new nomination is not rewarded yet + let mut new5 = vec![ + Event::Nomination(8, 10, 1, NominatorAdded::AddedToTop { new_total: 50 }), Event::ReservedForParachainBond(11, 32), Event::Rewarded(1, 19), Event::Rewarded(7, 6), @@ -2284,17 +2560,17 @@ fn parachain_bond_inflation_reserve_matches_config() { Event::CollatorChosen(9, 5, 10), Event::NewRound(40, 9, 5, 140), ]; - expected.append(&mut new6); + expected.append(&mut new5); assert_eq!(events(), expected); - assert_eq!(Balances::free_balance(&11), 150); - roll_to(46); - // new nomination is rewarded, 2 rounds after joining (`BondDuration` is 2) - let mut new7 = vec![ + assert_eq!(Balances::free_balance(&11), 139); + set_author(9, 1, 100); + roll_to(45); + // new nomination is still not rewarded yet + let mut new6 = vec![ Event::ReservedForParachainBond(11, 33), - Event::Rewarded(1, 18), - Event::Rewarded(7, 5), - Event::Rewarded(8, 5), - Event::Rewarded(10, 5), + Event::Rewarded(1, 20), + Event::Rewarded(7, 7), + Event::Rewarded(10, 7), Event::CollatorChosen(10, 1, 50), Event::CollatorChosen(10, 2, 40), Event::CollatorChosen(10, 4, 20), @@ -2302,9 +2578,27 @@ fn parachain_bond_inflation_reserve_matches_config() { Event::CollatorChosen(10, 5, 10), Event::NewRound(45, 10, 5, 140), ]; + expected.append(&mut new6); + assert_eq!(events(), expected); + assert_eq!(Balances::free_balance(&11), 172); + roll_to(50); + // new nomination is rewarded, 2 rounds after joining (`RewardPaymentDelay` is 2) + let mut new7 = vec![ + Event::ReservedForParachainBond(11, 35), + Event::Rewarded(1, 18), + Event::Rewarded(7, 6), + Event::Rewarded(8, 6), + Event::Rewarded(10, 6), + Event::CollatorChosen(11, 1, 50), + Event::CollatorChosen(11, 2, 40), + Event::CollatorChosen(11, 4, 20), + Event::CollatorChosen(11, 3, 20), + Event::CollatorChosen(11, 5, 10), + Event::NewRound(50, 11, 5, 140), + ]; expected.append(&mut new7); assert_eq!(events(), expected); - assert_eq!(Balances::free_balance(&11), 183); + assert_eq!(Balances::free_balance(&11), 207); }); } @@ -2779,14 +3073,14 @@ fn multiple_nominations() { expected.append(&mut new3); assert_eq!(events(), expected); // verify that nominations are removed after collator leaves, not before - assert_eq!(Stake::nominator_state(7).unwrap().total, 90); + assert_eq!(Stake::nominator_state2(7).unwrap().total, 90); assert_eq!( - Stake::nominator_state(7).unwrap().nominations.0.len(), + Stake::nominator_state2(7).unwrap().nominations.0.len(), 2usize ); - assert_eq!(Stake::nominator_state(6).unwrap().total, 40); + assert_eq!(Stake::nominator_state2(6).unwrap().total, 40); assert_eq!( - Stake::nominator_state(6).unwrap().nominations.0.len(), + Stake::nominator_state2(6).unwrap().nominations.0.len(), 4usize ); assert_eq!(Balances::reserved_balance(&6), 40); @@ -2794,14 +3088,14 @@ fn multiple_nominations() { assert_eq!(Balances::free_balance(&6), 60); assert_eq!(Balances::free_balance(&7), 10); roll_to(40); - assert_eq!(Stake::nominator_state(7).unwrap().total, 10); - assert_eq!(Stake::nominator_state(6).unwrap().total, 30); + assert_eq!(Stake::nominator_state2(7).unwrap().total, 10); + assert_eq!(Stake::nominator_state2(6).unwrap().total, 30); assert_eq!( - Stake::nominator_state(7).unwrap().nominations.0.len(), + Stake::nominator_state2(7).unwrap().nominations.0.len(), 1usize ); assert_eq!( - Stake::nominator_state(6).unwrap().nominations.0.len(), + Stake::nominator_state2(6).unwrap().nominations.0.len(), 3usize ); assert_eq!(Balances::reserved_balance(&6), 30); @@ -2874,39 +3168,34 @@ fn payouts_follow_nomination_changes() { // ~ set block author as 1 for all blocks this round set_author(3, 1, 100); set_author(4, 1, 100); + set_author(5, 1, 100); // 1. ensure nominators are paid for 2 rounds after they leave assert_noop!( Stake::leave_nominators(Origin::signed(66), 10), Error::::NominatorDNE ); assert_ok!(Stake::leave_nominators(Origin::signed(6), 10)); - roll_to(21); + // fast forward to block in which nominator 6 exit executes + roll_to(25); // keep paying 6 (note: inflation is in terms of total issuance so that's why 1 is 21) let mut new2 = vec![ - Event::NominatorLeftCollator(6, 1, 10, 40), - Event::NominatorLeft(6, 10), + Event::NominatorExitScheduled(4, 6, 6), Event::Rewarded(1, 27), Event::Rewarded(7, 8), Event::Rewarded(10, 8), Event::Rewarded(6, 8), + Event::CollatorChosen(5, 1, 50), Event::CollatorChosen(5, 2, 40), - Event::CollatorChosen(5, 1, 40), Event::CollatorChosen(5, 4, 20), Event::CollatorChosen(5, 3, 20), Event::CollatorChosen(5, 5, 10), - Event::NewRound(20, 5, 5, 130), - ]; - expected.append(&mut new2); - assert_eq!(events(), expected); - // 6 won't be paid for this round because they left already - set_author(5, 1, 100); - roll_to(26); - // keep paying 6 - let mut new3 = vec![ + Event::NewRound(20, 5, 5, 140), Event::Rewarded(1, 29), Event::Rewarded(7, 9), Event::Rewarded(10, 9), Event::Rewarded(6, 9), + Event::NominatorLeftCollator(6, 1, 10, 40), + Event::NominatorLeft(6, 10), Event::CollatorChosen(6, 2, 40), Event::CollatorChosen(6, 1, 40), Event::CollatorChosen(6, 4, 20), @@ -2914,15 +3203,17 @@ fn payouts_follow_nomination_changes() { Event::CollatorChosen(6, 5, 10), Event::NewRound(25, 6, 5, 130), ]; - expected.append(&mut new3); + expected.append(&mut new2); assert_eq!(events(), expected); + // 6 won't be paid for this round because they left already set_author(6, 1, 100); - roll_to(31); - // no more paying 6 - let mut new4 = vec![ - Event::Rewarded(1, 35), - Event::Rewarded(7, 11), - Event::Rewarded(10, 11), + roll_to(30); + // keep paying 6 + let mut new3 = vec![ + Event::Rewarded(1, 30), + Event::Rewarded(7, 9), + Event::Rewarded(10, 9), + Event::Rewarded(6, 9), Event::CollatorChosen(7, 2, 40), Event::CollatorChosen(7, 1, 40), Event::CollatorChosen(7, 4, 20), @@ -2930,30 +3221,30 @@ fn payouts_follow_nomination_changes() { Event::CollatorChosen(7, 5, 10), Event::NewRound(30, 7, 5, 130), ]; - expected.append(&mut new4); + expected.append(&mut new3); assert_eq!(events(), expected); set_author(7, 1, 100); - assert_ok!(Stake::nominate(Origin::signed(8), 1, 10, 10, 10)); - roll_to(36); - // new nomination is not rewarded yet - let mut new5 = vec![ - Event::Nomination(8, 10, 1, NominatorAdded::AddedToTop { new_total: 50 }), + roll_to(35); + // no more paying 6 + let mut new4 = vec![ Event::Rewarded(1, 36), Event::Rewarded(7, 12), Event::Rewarded(10, 12), - Event::CollatorChosen(8, 1, 50), Event::CollatorChosen(8, 2, 40), + Event::CollatorChosen(8, 1, 40), Event::CollatorChosen(8, 4, 20), Event::CollatorChosen(8, 3, 20), Event::CollatorChosen(8, 5, 10), - Event::NewRound(35, 8, 5, 140), + Event::NewRound(35, 8, 5, 130), ]; - expected.append(&mut new5); + expected.append(&mut new4); assert_eq!(events(), expected); set_author(8, 1, 100); - roll_to(41); - // new nomination is still not rewarded yet - let mut new6 = vec![ + assert_ok!(Stake::nominate(Origin::signed(8), 1, 10, 10, 10)); + roll_to(40); + // new nomination is not rewarded yet + let mut new5 = vec![ + Event::Nomination(8, 10, 1, NominatorAdded::AddedToTop { new_total: 50 }), Event::Rewarded(1, 38), Event::Rewarded(7, 13), Event::Rewarded(10, 13), @@ -2964,15 +3255,15 @@ fn payouts_follow_nomination_changes() { Event::CollatorChosen(9, 5, 10), Event::NewRound(40, 9, 5, 140), ]; - expected.append(&mut new6); + expected.append(&mut new5); assert_eq!(events(), expected); - roll_to(46); - // new nomination is rewarded for first time, 2 rounds after joining (`BondDuration` = 2) - let mut new7 = vec![ - Event::Rewarded(1, 35), - Event::Rewarded(7, 11), - Event::Rewarded(8, 11), - Event::Rewarded(10, 11), + set_author(9, 1, 100); + roll_to(45); + // new nomination is still not rewarded yet + let mut new6 = vec![ + Event::Rewarded(1, 40), + Event::Rewarded(7, 13), + Event::Rewarded(10, 13), Event::CollatorChosen(10, 1, 50), Event::CollatorChosen(10, 2, 40), Event::CollatorChosen(10, 4, 20), @@ -2980,6 +3271,22 @@ fn payouts_follow_nomination_changes() { Event::CollatorChosen(10, 5, 10), Event::NewRound(45, 10, 5, 140), ]; + expected.append(&mut new6); + assert_eq!(events(), expected); + roll_to(50); + // new nomination is rewarded for first time, 2 rounds after joining (`RewardPaymentDelay` = 2) + let mut new7 = vec![ + Event::Rewarded(1, 36), + Event::Rewarded(7, 11), + Event::Rewarded(8, 11), + Event::Rewarded(10, 11), + Event::CollatorChosen(11, 1, 50), + Event::CollatorChosen(11, 2, 40), + Event::CollatorChosen(11, 4, 20), + Event::CollatorChosen(11, 3, 20), + Event::CollatorChosen(11, 5, 10), + Event::NewRound(50, 11, 5, 140), + ]; expected.append(&mut new7); assert_eq!(events(), expected); }); diff --git a/pallets/parachain-staking/src/weights.rs b/pallets/parachain-staking/src/weights.rs index e0690daef0..8673905b3b 100644 --- a/pallets/parachain-staking/src/weights.rs +++ b/pallets/parachain-staking/src/weights.rs @@ -1,20 +1,25 @@ // Copyright 2019-2021 PureStake Inc. // This file is part of Moonbeam. + // Moonbeam 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. + // Moonbeam 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 Moonbeam. If not, see . + //! Autogenerated weights for parachain_staking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-06-23, STEPS: `[32, ]`, REPEAT: 64, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-07-19, STEPS: `[32, ]`, REPEAT: 64, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + // Executed Command: // ./target/release/moonbeam // benchmark @@ -34,13 +39,16 @@ // --template=./benchmarking/frame-weight-template.hbs // --output // /tmp/ + #![allow(unused_parens)] #![allow(unused_imports)] + use frame_support::{ traits::Get, weights::{constants::RocksDbWeight, Weight}, }; use sp_std::marker::PhantomData; + /// Weight functions needed for parachain_staking. pub trait WeightInfo { fn set_staking_expectations() -> Weight; @@ -64,236 +72,234 @@ pub trait WeightInfo { fn active_on_initialize(x: u32, y: u32) -> Weight; fn passive_on_initialize() -> Weight; } + /// Weights for parachain_staking using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn set_staking_expectations() -> Weight { - (21_480_000 as Weight) + (20_719_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } fn set_inflation() -> Weight { - (63_436_000 as Weight) + (63_011_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } fn set_parachain_bond_account() -> Weight { - (21_223_000 as Weight) + (20_434_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } fn set_parachain_bond_reserve_percent() -> Weight { - (20_038_000 as Weight) + (19_239_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } fn set_total_selected() -> Weight { - (19_078_000 as Weight) + (18_402_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } fn set_collator_commission() -> Weight { - (18_969_000 as Weight) + (18_178_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } fn set_blocks_per_round() -> Weight { - (65_945_000 as Weight) + (65_939_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn join_candidates(x: u32) -> Weight { - (85_903_000 as Weight) - // Standard Error: 0 - .saturating_add((355_000 as Weight).saturating_mul(x as Weight)) + (84_807_000 as Weight) + // Standard Error: 1_000 + .saturating_add((333_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(9 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } fn leave_candidates(x: u32) -> Weight { - (63_925_000 as Weight) + (64_426_000 as Weight) // Standard Error: 1_000 - .saturating_add((354_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((332_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } fn go_offline() -> Weight { - (36_988_000 as Weight) + (36_577_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn go_online() -> Weight { - (36_379_000 as Weight) + (36_134_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn candidate_bond_more() -> Weight { - (61_176_000 as Weight) + (59_735_000 as Weight) .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } fn candidate_bond_less() -> Weight { - (60_970_000 as Weight) + (59_421_000 as Weight) .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } fn nominate(x: u32, y: u32) -> Weight { - (72_584_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_103_000 as Weight).saturating_mul(x as Weight)) - // Standard Error: 7_000 - .saturating_add((1_030_000 as Weight).saturating_mul(y as Weight)) + (71_656_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_049_000 as Weight).saturating_mul(x as Weight)) + // Standard Error: 5_000 + .saturating_add((947_000 as Weight).saturating_mul(y as Weight)) .saturating_add(T::DbWeight::get().reads(9 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } fn leave_nominators(x: u32) -> Weight { - (0 as Weight) - // Standard Error: 89_000 - .saturating_add((51_424_000 as Weight).saturating_mul(x as Weight)) + (36_354_000 as Weight) + // Standard Error: 2_000 + .saturating_add((694_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(7 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(x as Weight))) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(x as Weight))) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn revoke_nomination() -> Weight { - (78_979_000 as Weight) - .saturating_add(T::DbWeight::get().reads(9 as Weight)) - .saturating_add(T::DbWeight::get().writes(7 as Weight)) + (37_580_000 as Weight) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn nominator_bond_more() -> Weight { - (72_203_000 as Weight) + (70_867_000 as Weight) .saturating_add(T::DbWeight::get().reads(9 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } fn nominator_bond_less() -> Weight { - (72_469_000 as Weight) + (70_857_000 as Weight) .saturating_add(T::DbWeight::get().reads(9 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } fn active_on_initialize(x: u32, y: u32) -> Weight { - (10_360_000 as Weight) + (10_177_000 as Weight) // Standard Error: 0 - .saturating_add((26_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((24_000 as Weight).saturating_mul(x as Weight)) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(y as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) } fn passive_on_initialize() -> Weight { - (4_902_000 as Weight).saturating_add(T::DbWeight::get().reads(1 as Weight)) + (4_913_000 as Weight).saturating_add(T::DbWeight::get().reads(1 as Weight)) } } + // For backwards compatibility and tests impl WeightInfo for () { fn set_staking_expectations() -> Weight { - (21_480_000 as Weight) + (20_719_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } fn set_inflation() -> Weight { - (63_436_000 as Weight) + (63_011_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } fn set_parachain_bond_account() -> Weight { - (21_223_000 as Weight) + (20_434_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } fn set_parachain_bond_reserve_percent() -> Weight { - (20_038_000 as Weight) + (19_239_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } fn set_total_selected() -> Weight { - (19_078_000 as Weight) + (18_402_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } fn set_collator_commission() -> Weight { - (18_969_000 as Weight) + (18_178_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } fn set_blocks_per_round() -> Weight { - (65_945_000 as Weight) + (65_939_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn join_candidates(x: u32) -> Weight { - (85_903_000 as Weight) - // Standard Error: 0 - .saturating_add((355_000 as Weight).saturating_mul(x as Weight)) + (84_807_000 as Weight) + // Standard Error: 1_000 + .saturating_add((333_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(9 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } fn leave_candidates(x: u32) -> Weight { - (63_925_000 as Weight) + (64_426_000 as Weight) // Standard Error: 1_000 - .saturating_add((354_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((332_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(8 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } fn go_offline() -> Weight { - (36_988_000 as Weight) + (36_577_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn go_online() -> Weight { - (36_379_000 as Weight) + (36_134_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn candidate_bond_more() -> Weight { - (61_176_000 as Weight) + (59_735_000 as Weight) .saturating_add(RocksDbWeight::get().reads(8 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } fn candidate_bond_less() -> Weight { - (60_970_000 as Weight) + (59_421_000 as Weight) .saturating_add(RocksDbWeight::get().reads(8 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } fn nominate(x: u32, y: u32) -> Weight { - (72_584_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_103_000 as Weight).saturating_mul(x as Weight)) - // Standard Error: 7_000 - .saturating_add((1_030_000 as Weight).saturating_mul(y as Weight)) + (71_656_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_049_000 as Weight).saturating_mul(x as Weight)) + // Standard Error: 5_000 + .saturating_add((947_000 as Weight).saturating_mul(y as Weight)) .saturating_add(RocksDbWeight::get().reads(9 as Weight)) .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } fn leave_nominators(x: u32) -> Weight { - (0 as Weight) - // Standard Error: 89_000 - .saturating_add((51_424_000 as Weight).saturating_mul(x as Weight)) + (36_354_000 as Weight) + // Standard Error: 2_000 + .saturating_add((694_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) - .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(x as Weight))) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(x as Weight))) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn revoke_nomination() -> Weight { - (78_979_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(9 as Weight)) - .saturating_add(RocksDbWeight::get().writes(7 as Weight)) + (37_580_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(7 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn nominator_bond_more() -> Weight { - (72_203_000 as Weight) + (70_867_000 as Weight) .saturating_add(RocksDbWeight::get().reads(9 as Weight)) .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } fn nominator_bond_less() -> Weight { - (72_469_000 as Weight) + (70_857_000 as Weight) .saturating_add(RocksDbWeight::get().reads(9 as Weight)) .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } fn active_on_initialize(x: u32, y: u32) -> Weight { - (10_360_000 as Weight) + (10_177_000 as Weight) // Standard Error: 0 - .saturating_add((26_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((24_000 as Weight).saturating_mul(x as Weight)) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(y as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) } fn passive_on_initialize() -> Weight { - (4_902_000 as Weight).saturating_add(RocksDbWeight::get().reads(1 as Weight)) + (4_913_000 as Weight).saturating_add(RocksDbWeight::get().reads(1 as Weight)) } } diff --git a/runtime/moonbase/src/lib.rs b/runtime/moonbase/src/lib.rs index d990d5c39f..bd0f87f26d 100644 --- a/runtime/moonbase/src/lib.rs +++ b/runtime/moonbase/src/lib.rs @@ -569,8 +569,14 @@ parameter_types! { pub const MinBlocksPerRound: u32 = 10; /// Default BlocksPerRound is every hour (300 * 12 second block times) pub const DefaultBlocksPerRound: u32 = 300; - /// Reward payments and collator exit requests are delayed by 2 hours (2 * 300 * block_time) - pub const BondDuration: u32 = 2; + /// Collator candidate exits are delayed by 2 hours (2 * 300 * block_time) + pub const LeaveCandidatesDelay: u32 = 2; + /// Nominator exits are delayed by 2 hours (2 * 300 * block_time) + pub const LeaveNominatorsDelay: u32 = 2; + /// Nomination revocations are delayed by 2 hours (2 * 300 * block_time) + pub const RevokeNominationDelay: u32 = 2; + /// Reward payments are delayed by 2 hours (2 * 300 * block_time) + pub const RewardPaymentDelay: u32 = 2; /// Minimum 8 collators selected per round, default at genesis and minimum forever after pub const MinSelectedCandidates: u32 = 8; /// Maximum 10 nominators per collator @@ -594,7 +600,10 @@ impl parachain_staking::Config for Runtime { type MonetaryGovernanceOrigin = EnsureRoot; type MinBlocksPerRound = MinBlocksPerRound; type DefaultBlocksPerRound = DefaultBlocksPerRound; - type BondDuration = BondDuration; + type LeaveCandidatesDelay = LeaveCandidatesDelay; + type LeaveNominatorsDelay = LeaveNominatorsDelay; + type RevokeNominationDelay = RevokeNominationDelay; + type RewardPaymentDelay = RewardPaymentDelay; type MinSelectedCandidates = MinSelectedCandidates; type MaxNominatorsPerCollator = MaxNominatorsPerCollator; type MaxCollatorsPerNominator = MaxCollatorsPerNominator; diff --git a/runtime/moonbase/tests/integration_test.rs b/runtime/moonbase/tests/integration_test.rs index 1b987c32fe..979ef72307 100644 --- a/runtime/moonbase/tests/integration_test.rs +++ b/runtime/moonbase/tests/integration_test.rs @@ -1300,7 +1300,7 @@ fn leave_nominators_via_precompile() { let mut call_data = Vec::::from([0u8; 36]); call_data[0..4].copy_from_slice(&Keccak256::digest(b"leave_nominators(uint256)")[0..4]); nomination_count.to_big_endian(&mut call_data[4..]); - + run_to_block(1); assert_ok!(Call::EVM(pallet_evm::Call::::call( AccountId::from(CHARLIE), staking_precompile_address, @@ -1311,48 +1311,9 @@ fn leave_nominators_via_precompile() { None, // Use the next nonce )) .dispatch(::Origin::root())); - + run_to_block(600); // Charlie is no longer a nominator assert!(!ParachainStaking::is_nominator(&AccountId::from(CHARLIE))); - - // Check for the right events. - let expected_events = vec![ - Event::Balances(pallet_balances::Event::Unreserved( - AccountId::from(CHARLIE), - 500 * UNIT, - )), - Event::ParachainStaking(parachain_staking::Event::NominatorLeftCollator( - AccountId::from(CHARLIE), - AccountId::from(ALICE), - 500 * UNIT, - 1_000 * UNIT, - )), - Event::Balances(pallet_balances::Event::Unreserved( - AccountId::from(CHARLIE), - 500 * UNIT, - )), - Event::ParachainStaking(parachain_staking::Event::NominatorLeftCollator( - AccountId::from(CHARLIE), - AccountId::from(BOB), - 500 * UNIT, - 1_000 * UNIT, - )), - Event::ParachainStaking(parachain_staking::Event::NominatorLeft( - AccountId::from(CHARLIE), - 1_000 * UNIT, - )), - Event::EVM(pallet_evm::Event::::Executed( - staking_precompile_address, - )), - ]; - - assert_eq!( - System::events() - .into_iter() - .map(|e| e.event) - .collect::>(), - expected_events - ); }); } @@ -1387,7 +1348,7 @@ fn revoke_nomination_via_precompile() { call_data[0..4] .copy_from_slice(&Keccak256::digest(b"revoke_nomination(address)")[0..4]); call_data[16..36].copy_from_slice(&ALICE); - + run_to_block(1); assert_ok!(Call::EVM(pallet_evm::Call::::call( AccountId::from(CHARLIE), staking_precompile_address, @@ -1398,34 +1359,9 @@ fn revoke_nomination_via_precompile() { None, // Use the next nonce )) .dispatch(::Origin::root())); - + run_to_block(600); // Charlie is still a nominator because only nomination to Alice was revoked assert!(ParachainStaking::is_nominator(&AccountId::from(CHARLIE))); - - // Check for the right events. - let expected_events = vec![ - Event::Balances(pallet_balances::Event::Unreserved( - AccountId::from(CHARLIE), - 500 * UNIT, - )), - Event::ParachainStaking(parachain_staking::Event::NominatorLeftCollator( - AccountId::from(CHARLIE), - AccountId::from(ALICE), - 500 * UNIT, - 1_000 * UNIT, - )), - Event::EVM(pallet_evm::Event::::Executed( - staking_precompile_address, - )), - ]; - - assert_eq!( - System::events() - .into_iter() - .map(|e| e.event) - .collect::>(), - expected_events - ); }); } diff --git a/runtime/moonbeam/src/lib.rs b/runtime/moonbeam/src/lib.rs index 75baa3149a..7abc91221d 100644 --- a/runtime/moonbeam/src/lib.rs +++ b/runtime/moonbeam/src/lib.rs @@ -573,8 +573,14 @@ parameter_types! { pub const MinBlocksPerRound: u32 = 10; /// Default BlocksPerRound is every hour (300 * 12 second block times) pub const DefaultBlocksPerRound: u32 = 300; - /// Reward payments and collator exit requests are delayed by 2 hours (2 * 300 * block_time) - pub const BondDuration: u32 = 2; + /// Collator candidate exits are delayed by 2 hours (2 * 300 * block_time) + pub const LeaveCandidatesDelay: u32 = 2; + /// Nominator exits are delayed by 2 hours (2 * 300 * block_time) + pub const LeaveNominatorsDelay: u32 = 2; + /// Nomination revocations are delayed by 2 hours (2 * 300 * block_time) + pub const RevokeNominationDelay: u32 = 2; + /// Reward payments are delayed by 2 hours (2 * 300 * block_time) + pub const RewardPaymentDelay: u32 = 2; /// Minimum 8 collators selected per round, default at genesis and minimum forever after pub const MinSelectedCandidates: u32 = 8; /// Maximum 10 nominators per collator @@ -598,7 +604,10 @@ impl parachain_staking::Config for Runtime { type MonetaryGovernanceOrigin = EnsureRoot; type MinBlocksPerRound = MinBlocksPerRound; type DefaultBlocksPerRound = DefaultBlocksPerRound; - type BondDuration = BondDuration; + type LeaveCandidatesDelay = LeaveCandidatesDelay; + type LeaveNominatorsDelay = LeaveNominatorsDelay; + type RevokeNominationDelay = RevokeNominationDelay; + type RewardPaymentDelay = RewardPaymentDelay; type MinSelectedCandidates = MinSelectedCandidates; type MaxNominatorsPerCollator = MaxNominatorsPerCollator; type MaxCollatorsPerNominator = MaxCollatorsPerNominator; diff --git a/runtime/moonbeam/tests/integration_test.rs b/runtime/moonbeam/tests/integration_test.rs index c584b81754..6586f640c2 100644 --- a/runtime/moonbeam/tests/integration_test.rs +++ b/runtime/moonbeam/tests/integration_test.rs @@ -1318,7 +1318,7 @@ fn leave_nominators_via_precompile() { let mut call_data = Vec::::from([0u8; 36]); call_data[0..4].copy_from_slice(&Keccak256::digest(b"leave_nominators(uint256)")[0..4]); nomination_count.to_big_endian(&mut call_data[4..]); - + run_to_block(1); assert_ok!(Call::EVM(pallet_evm::Call::::call( AccountId::from(CHARLIE), staking_precompile_address, @@ -1329,48 +1329,9 @@ fn leave_nominators_via_precompile() { None, // Use the next nonce )) .dispatch(::Origin::root())); - + run_to_block(600); // Charlie is no longer a nominator assert!(!ParachainStaking::is_nominator(&AccountId::from(CHARLIE))); - - // Check for the right events. - let expected_events = vec![ - Event::Balances(pallet_balances::Event::Unreserved( - AccountId::from(CHARLIE), - 500 * GLMR, - )), - Event::ParachainStaking(parachain_staking::Event::NominatorLeftCollator( - AccountId::from(CHARLIE), - AccountId::from(ALICE), - 500 * GLMR, - 1_000 * GLMR, - )), - Event::Balances(pallet_balances::Event::Unreserved( - AccountId::from(CHARLIE), - 500 * GLMR, - )), - Event::ParachainStaking(parachain_staking::Event::NominatorLeftCollator( - AccountId::from(CHARLIE), - AccountId::from(BOB), - 500 * GLMR, - 1_000 * GLMR, - )), - Event::ParachainStaking(parachain_staking::Event::NominatorLeft( - AccountId::from(CHARLIE), - 1_000 * GLMR, - )), - Event::EVM(pallet_evm::Event::::Executed( - staking_precompile_address, - )), - ]; - - assert_eq!( - System::events() - .into_iter() - .map(|e| e.event) - .collect::>(), - expected_events - ); }); } @@ -1405,7 +1366,7 @@ fn revoke_nomination_via_precompile() { call_data[0..4] .copy_from_slice(&Keccak256::digest(b"revoke_nomination(address)")[0..4]); call_data[16..36].copy_from_slice(&ALICE); - + run_to_block(1); assert_ok!(Call::EVM(pallet_evm::Call::::call( AccountId::from(CHARLIE), staking_precompile_address, @@ -1416,34 +1377,9 @@ fn revoke_nomination_via_precompile() { None, // Use the next nonce )) .dispatch(::Origin::root())); - + run_to_block(600); // Charlie is still a nominator because only nomination to Alice was revoked assert!(ParachainStaking::is_nominator(&AccountId::from(CHARLIE))); - - // Check for the right events. - let expected_events = vec![ - Event::Balances(pallet_balances::Event::Unreserved( - AccountId::from(CHARLIE), - 500 * GLMR, - )), - Event::ParachainStaking(parachain_staking::Event::NominatorLeftCollator( - AccountId::from(CHARLIE), - AccountId::from(ALICE), - 500 * GLMR, - 1_000 * GLMR, - )), - Event::EVM(pallet_evm::Event::::Executed( - staking_precompile_address, - )), - ]; - - assert_eq!( - System::events() - .into_iter() - .map(|e| e.event) - .collect::>(), - expected_events - ); }); } diff --git a/runtime/moonriver/src/lib.rs b/runtime/moonriver/src/lib.rs index f07b760a89..a83dcbd26c 100644 --- a/runtime/moonriver/src/lib.rs +++ b/runtime/moonriver/src/lib.rs @@ -586,8 +586,14 @@ parameter_types! { pub const MinBlocksPerRound: u32 = 10; /// Default BlocksPerRound is every hour (300 * 12 second block times) pub const DefaultBlocksPerRound: u32 = 300; - /// Reward payments and collator exit requests are delayed by 2 hours (2 * 300 * block_time) - pub const BondDuration: u32 = 2; + /// Collator candidate exits are delayed by 2 hours (2 * 300 * block_time) + pub const LeaveCandidatesDelay: u32 = 2; + /// Nominator exits are delayed by 2 hours (2 * 300 * block_time) + pub const LeaveNominatorsDelay: u32 = 2; + /// Nomination revocations are delayed by 2 hours (2 * 300 * block_time) + pub const RevokeNominationDelay: u32 = 2; + /// Reward payments are delayed by 2 hours (2 * 300 * block_time) + pub const RewardPaymentDelay: u32 = 2; /// Minimum 8 collators selected per round, default at genesis and minimum forever after pub const MinSelectedCandidates: u32 = 8; /// Maximum 10 nominators per collator @@ -611,7 +617,10 @@ impl parachain_staking::Config for Runtime { type MonetaryGovernanceOrigin = EnsureRoot; type MinBlocksPerRound = MinBlocksPerRound; type DefaultBlocksPerRound = DefaultBlocksPerRound; - type BondDuration = BondDuration; + type LeaveCandidatesDelay = LeaveCandidatesDelay; + type LeaveNominatorsDelay = LeaveNominatorsDelay; + type RevokeNominationDelay = RevokeNominationDelay; + type RewardPaymentDelay = RewardPaymentDelay; type MinSelectedCandidates = MinSelectedCandidates; type MaxNominatorsPerCollator = MaxNominatorsPerCollator; type MaxCollatorsPerNominator = MaxCollatorsPerNominator; diff --git a/runtime/moonriver/tests/integration_test.rs b/runtime/moonriver/tests/integration_test.rs index 030365c186..af6050b1e4 100644 --- a/runtime/moonriver/tests/integration_test.rs +++ b/runtime/moonriver/tests/integration_test.rs @@ -1328,7 +1328,7 @@ fn leave_nominators_via_precompile() { let mut call_data = Vec::::from([0u8; 36]); call_data[0..4].copy_from_slice(&Keccak256::digest(b"leave_nominators(uint256)")[0..4]); nomination_count.to_big_endian(&mut call_data[4..]); - + run_to_block(1); assert_ok!(Call::EVM(pallet_evm::Call::::call( AccountId::from(CHARLIE), staking_precompile_address, @@ -1339,48 +1339,9 @@ fn leave_nominators_via_precompile() { None, // Use the next nonce )) .dispatch(::Origin::root())); - + run_to_block(600); // Charlie is no longer a nominator assert!(!ParachainStaking::is_nominator(&AccountId::from(CHARLIE))); - - // Check for the right events. - let expected_events = vec![ - Event::Balances(pallet_balances::Event::Unreserved( - AccountId::from(CHARLIE), - 500 * MOVR, - )), - Event::ParachainStaking(parachain_staking::Event::NominatorLeftCollator( - AccountId::from(CHARLIE), - AccountId::from(ALICE), - 500 * MOVR, - 1_000 * MOVR, - )), - Event::Balances(pallet_balances::Event::Unreserved( - AccountId::from(CHARLIE), - 500 * MOVR, - )), - Event::ParachainStaking(parachain_staking::Event::NominatorLeftCollator( - AccountId::from(CHARLIE), - AccountId::from(BOB), - 500 * MOVR, - 1_000 * MOVR, - )), - Event::ParachainStaking(parachain_staking::Event::NominatorLeft( - AccountId::from(CHARLIE), - 1_000 * MOVR, - )), - Event::EVM(pallet_evm::Event::::Executed( - staking_precompile_address, - )), - ]; - - assert_eq!( - System::events() - .into_iter() - .map(|e| e.event) - .collect::>(), - expected_events - ); }); } @@ -1415,7 +1376,7 @@ fn revoke_nomination_via_precompile() { call_data[0..4] .copy_from_slice(&Keccak256::digest(b"revoke_nomination(address)")[0..4]); call_data[16..36].copy_from_slice(&ALICE); - + run_to_block(1); assert_ok!(Call::EVM(pallet_evm::Call::::call( AccountId::from(CHARLIE), staking_precompile_address, @@ -1426,34 +1387,9 @@ fn revoke_nomination_via_precompile() { None, // Use the next nonce )) .dispatch(::Origin::root())); - + run_to_block(600); // Charlie is still a nominator because only nomination to Alice was revoked assert!(ParachainStaking::is_nominator(&AccountId::from(CHARLIE))); - - // Check for the right events. - let expected_events = vec![ - Event::Balances(pallet_balances::Event::Unreserved( - AccountId::from(CHARLIE), - 500 * MOVR, - )), - Event::ParachainStaking(parachain_staking::Event::NominatorLeftCollator( - AccountId::from(CHARLIE), - AccountId::from(ALICE), - 500 * MOVR, - 1_000 * MOVR, - )), - Event::EVM(pallet_evm::Event::::Executed( - staking_precompile_address, - )), - ]; - - assert_eq!( - System::events() - .into_iter() - .map(|e| e.event) - .collect::>(), - expected_events - ); }); } diff --git a/runtime/moonshadow/src/lib.rs b/runtime/moonshadow/src/lib.rs index 5d45e2d552..c3e1423374 100644 --- a/runtime/moonshadow/src/lib.rs +++ b/runtime/moonshadow/src/lib.rs @@ -572,8 +572,14 @@ parameter_types! { pub const MinBlocksPerRound: u32 = 10; /// Default BlocksPerRound is every hour (300 * 12 second block times) pub const DefaultBlocksPerRound: u32 = 300; - /// Reward payments and collator exit requests are delayed by 2 hours (2 * 300 * block_time) - pub const BondDuration: u32 = 2; + /// Collator candidate exits are delayed by 2 hours (2 * 300 * block_time) + pub const LeaveCandidatesDelay: u32 = 2; + /// Nominator exits are delayed by 2 hours (2 * 300 * block_time) + pub const LeaveNominatorsDelay: u32 = 2; + /// Nomination revocations are delayed by 2 hours (2 * 300 * block_time) + pub const RevokeNominationDelay: u32 = 2; + /// Reward payments are delayed by 2 hours (2 * 300 * block_time) + pub const RewardPaymentDelay: u32 = 2; /// Minimum 8 collators selected per round, default at genesis and minimum forever after pub const MinSelectedCandidates: u32 = 8; /// Maximum 10 nominators per collator @@ -597,7 +603,10 @@ impl parachain_staking::Config for Runtime { type MonetaryGovernanceOrigin = EnsureRoot; type MinBlocksPerRound = MinBlocksPerRound; type DefaultBlocksPerRound = DefaultBlocksPerRound; - type BondDuration = BondDuration; + type LeaveCandidatesDelay = LeaveCandidatesDelay; + type LeaveNominatorsDelay = LeaveNominatorsDelay; + type RevokeNominationDelay = RevokeNominationDelay; + type RewardPaymentDelay = RewardPaymentDelay; type MinSelectedCandidates = MinSelectedCandidates; type MaxNominatorsPerCollator = MaxNominatorsPerCollator; type MaxCollatorsPerNominator = MaxCollatorsPerNominator; diff --git a/runtime/moonshadow/tests/integration_test.rs b/runtime/moonshadow/tests/integration_test.rs index c806855f3f..8ea21ec393 100644 --- a/runtime/moonshadow/tests/integration_test.rs +++ b/runtime/moonshadow/tests/integration_test.rs @@ -1315,7 +1315,7 @@ fn leave_nominators_via_precompile() { let mut call_data = Vec::::from([0u8; 36]); call_data[0..4].copy_from_slice(&Keccak256::digest(b"leave_nominators(uint256)")[0..4]); nomination_count.to_big_endian(&mut call_data[4..]); - + run_to_block(1); assert_ok!(Call::EVM(pallet_evm::Call::::call( AccountId::from(CHARLIE), staking_precompile_address, @@ -1326,48 +1326,9 @@ fn leave_nominators_via_precompile() { None, // Use the next nonce )) .dispatch(::Origin::root())); - + run_to_block(600); // Charlie is no longer a nominator assert!(!ParachainStaking::is_nominator(&AccountId::from(CHARLIE))); - - // Check for the right events. - let expected_events = vec![ - Event::Balances(pallet_balances::Event::Unreserved( - AccountId::from(CHARLIE), - 500 * MSHD, - )), - Event::ParachainStaking(parachain_staking::Event::NominatorLeftCollator( - AccountId::from(CHARLIE), - AccountId::from(ALICE), - 500 * MSHD, - 1_000 * MSHD, - )), - Event::Balances(pallet_balances::Event::Unreserved( - AccountId::from(CHARLIE), - 500 * MSHD, - )), - Event::ParachainStaking(parachain_staking::Event::NominatorLeftCollator( - AccountId::from(CHARLIE), - AccountId::from(BOB), - 500 * MSHD, - 1_000 * MSHD, - )), - Event::ParachainStaking(parachain_staking::Event::NominatorLeft( - AccountId::from(CHARLIE), - 1_000 * MSHD, - )), - Event::EVM(pallet_evm::Event::::Executed( - staking_precompile_address, - )), - ]; - - assert_eq!( - System::events() - .into_iter() - .map(|e| e.event) - .collect::>(), - expected_events - ); }); } @@ -1402,7 +1363,7 @@ fn revoke_nomination_via_precompile() { call_data[0..4] .copy_from_slice(&Keccak256::digest(b"revoke_nomination(address)")[0..4]); call_data[16..36].copy_from_slice(&ALICE); - + run_to_block(1); assert_ok!(Call::EVM(pallet_evm::Call::::call( AccountId::from(CHARLIE), staking_precompile_address, @@ -1413,34 +1374,9 @@ fn revoke_nomination_via_precompile() { None, // Use the next nonce )) .dispatch(::Origin::root())); - + run_to_block(600); // Charlie is still a nominator because only nomination to Alice was revoked assert!(ParachainStaking::is_nominator(&AccountId::from(CHARLIE))); - - // Check for the right events. - let expected_events = vec![ - Event::Balances(pallet_balances::Event::Unreserved( - AccountId::from(CHARLIE), - 500 * MSHD, - )), - Event::ParachainStaking(parachain_staking::Event::NominatorLeftCollator( - AccountId::from(CHARLIE), - AccountId::from(ALICE), - 500 * MSHD, - 1_000 * MSHD, - )), - Event::EVM(pallet_evm::Event::::Executed( - staking_precompile_address, - )), - ]; - - assert_eq!( - System::events() - .into_iter() - .map(|e| e.event) - .collect::>(), - expected_events - ); }); } diff --git a/tests/tests/test-stake.ts b/tests/tests/test-stake.ts index 9a0af27acb..7e974228db 100644 --- a/tests/tests/test-stake.ts +++ b/tests/tests/test-stake.ts @@ -144,7 +144,7 @@ describeDevMoonbeam("Staking - Join Nominators", (context) => { .signAndSend(ethan); await context.createBlock(); - const nominatorsAfter = await context.polkadotApi.query.parachainStaking.nominatorState(ETHAN); + const nominatorsAfter = await context.polkadotApi.query.parachainStaking.nominatorState2(ETHAN); expect( ( nominatorsAfter.toHuman() as { @@ -155,28 +155,28 @@ describeDevMoonbeam("Staking - Join Nominators", (context) => { }); }); -describeDevMoonbeam("Staking - Revoke Nomination", (context) => { - let ethan; - before("should succesfully call nominate on ALITH", async function () { - //nominate - const keyring = new Keyring({ type: "ethereum" }); - ethan = await keyring.addFromUri(ETHAN_PRIVKEY, null, "ethereum"); - await context.polkadotApi.tx.parachainStaking - .nominate(ALITH, MIN_GLMR_NOMINATOR, 0, 0) - .signAndSend(ethan); - await context.createBlock(); - }); - it("should succesfully revoke nomination for ALITH", async function () { - await context.polkadotApi.tx.parachainStaking - .revokeNomination(ALITH) //TODO: when converting to test add .leaveNominators() - // that should produce the same behavior - .signAndSend(ethan); - await context.createBlock(); - const nominatorsAfterRevocation = - await context.polkadotApi.query.parachainStaking.nominatorState(ETHAN); - expect(nominatorsAfterRevocation.toHuman() === null).to.equal( - true, - "there should be no nominator" - ); - }); -}); +// // TODO: bring back when we figure out how to get `NominatorState2.revocations` +// describeDevMoonbeam("Staking - Revoke Nomination", (context) => { +// let ethan; +// before("should succesfully call nominate on ALITH", async function () { +// //nominate +// const keyring = new Keyring({ type: "ethereum" }); +// ethan = await keyring.addFromUri(ETHAN_PRIVKEY, null, "ethereum"); +// await context.polkadotApi.tx.parachainStaking +// .nominate(ALITH, MIN_GLMR_NOMINATOR, 0, 0) +// .signAndSend(ethan); +// await context.createBlock(); +// }); +// it("should succesfully revoke nomination for ALITH", async function () { +// await context.polkadotApi.tx.parachainStaking.revokeNomination(ALITH).signAndSend(ethan); +// await context.createBlock(); +// const nominatorsAfterRevocation = +// await context.polkadotApi.query.parachainStaking.nominatorState2(ETHAN); +// expect( +// (nominatorsAfterRevocation.revocations[0] === ALITH).to.equal( +// true, +// "revocation didnt go through" +// ) +// ); +// }); +// });