From 3be4e9a42a9938ccf8580dd4536670a64d294ffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lo=C3=AFs?= Date: Mon, 4 Mar 2024 14:34:03 +0100 Subject: [PATCH] Go back to block based staking rounds and make inflation dynamic (slot based) (#2690) * make staking rounds block based again and inflation slot based * remove unused imports * create migration skeleton * implement migration * integrate round migration to common migrations * apply proportion duration to the current round * compile benchs and test * add moonbase migration to multiply round length by 2 * compute round duration before round update * fix some bugs * fix some rust tests * fix migration * apply suggestions * First staking rewards at round 3 * revert moonwall config local changes * fix rounds per year * fix periods calculation in reset_round() * fix rustc warning * fmt * rework compute issuance * Remove Staked storage item * Remove Staked storage item on rust tests * fix two rust tests more * fix test_on_initialize_weights * fix payouts_follow_delegation_changes test * Update pallets/parachain-staking/src/lib.rs Co-authored-by: Rodrigo Quelhas <22591718+RomarQ@users.noreply.github.com> * Comment moonbase migration * typo * Update runtime/moonbase/src/migrations.rs Co-authored-by: Rodrigo Quelhas <22591718+RomarQ@users.noreply.github.com> * Update runtime/moonbase/src/migrations.rs * Update pallets/parachain-staking/src/lib.rs --------- Co-authored-by: Agusrodri Co-authored-by: Rodrigo Quelhas <22591718+RomarQ@users.noreply.github.com> --- pallets/parachain-staking/src/benchmarks.rs | 18 +- .../src/delegation_requests.rs | 15 +- pallets/parachain-staking/src/inflation.rs | 12 +- pallets/parachain-staking/src/lib.rs | 162 ++++++++------- pallets/parachain-staking/src/migrations.rs | 188 ++++++++++++++++++ pallets/parachain-staking/src/mock.rs | 3 +- pallets/parachain-staking/src/tests.rs | 68 +++---- pallets/parachain-staking/src/types.rs | 12 +- precompiles/parachain-staking/src/mock.rs | 3 +- runtime/common/src/apis.rs | 4 +- runtime/common/src/migrations.rs | 150 ++------------ runtime/moonbase/src/lib.rs | 27 +-- runtime/moonbase/src/migrations.rs | 43 ++++ runtime/moonbase/tests/common/mod.rs | 2 +- runtime/moonbase/tests/integration_test.rs | 31 ++- runtime/moonbeam/src/lib.rs | 29 +-- runtime/moonbeam/tests/integration_test.rs | 20 +- runtime/moonriver/src/lib.rs | 29 +-- runtime/moonriver/tests/integration_test.rs | 20 +- .../test-rewards-auto-compound-pov.ts | 2 +- 20 files changed, 467 insertions(+), 371 deletions(-) create mode 100644 runtime/moonbase/src/migrations.rs diff --git a/pallets/parachain-staking/src/benchmarks.rs b/pallets/parachain-staking/src/benchmarks.rs index e8009ea0b8..8de269f97d 100644 --- a/pallets/parachain-staking/src/benchmarks.rs +++ b/pallets/parachain-staking/src/benchmarks.rs @@ -20,7 +20,7 @@ use crate::{ AwardedPts, BalanceOf, BottomDelegations, Call, CandidateBondLessRequest, Config, DelegationAction, EnableMarkingOffline, Pallet, ParachainBondConfig, ParachainBondInfo, Points, - Range, RewardPayment, Round, ScheduledRequest, Staked, TopDelegations, + Range, RewardPayment, Round, ScheduledRequest, TopDelegations, }; use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; use frame_support::traits::{Currency, Get, OnFinalize, OnInitialize}; @@ -205,7 +205,7 @@ fn roll_to_and_author(round_delay: u32, author: T::AccountId) { let total_rounds = round_delay + 1u32; let round_length: BlockNumberFor = Pallet::::round().length.into(); let mut now = >::block_number() + 1u32.into(); - let first: BlockNumberFor = (Pallet::::round().first as u32).into(); + let first: BlockNumberFor = Pallet::::round().first; let end = first + (round_length * total_rounds.into()); while now < end { parachain_staking_on_finalize::(author.clone()); @@ -1542,15 +1542,19 @@ benchmarks! { prepare_staking_payouts { let reward_delay = <::RewardPaymentDelay as Get>::get(); - let round = reward_delay + 2u32; - let payout_round = round - reward_delay; + let round = crate::RoundInfo { + current: reward_delay + 2u32, + length: 10, + first: 5u32.into(), + first_slot: 5, + }; + let current_slot = 15; + let payout_round = round.current - reward_delay; // may need: // > - // > // > // ensure parachain bond account exists so that deposit_into_existing succeeds >::insert(payout_round, 100); - >::insert(payout_round, min_candidate_stk::()); // set an account in the bond config so that we will measure the payout to it let account = create_funded_user::( @@ -1563,7 +1567,7 @@ benchmarks! { percent: Percent::from_percent(50), }); - }: { Pallet::::prepare_staking_payouts(round); } + }: { Pallet::::prepare_staking_payouts(round, current_slot); } verify { } diff --git a/pallets/parachain-staking/src/delegation_requests.rs b/pallets/parachain-staking/src/delegation_requests.rs index fc26ea046d..30764e988d 100644 --- a/pallets/parachain-staking/src/delegation_requests.rs +++ b/pallets/parachain-staking/src/delegation_requests.rs @@ -82,7 +82,7 @@ impl Pallet { let mut scheduled_requests = >::get(&collator); let actual_weight = - T::WeightInfo::schedule_revoke_delegation(scheduled_requests.len() as u32); + ::WeightInfo::schedule_revoke_delegation(scheduled_requests.len() as u32); ensure!( !scheduled_requests @@ -131,8 +131,9 @@ impl Pallet { let mut state = >::get(&delegator).ok_or(>::DelegatorDNE)?; let mut scheduled_requests = >::get(&collator); - let actual_weight = - T::WeightInfo::schedule_delegator_bond_less(scheduled_requests.len() as u32); + let actual_weight = ::WeightInfo::schedule_delegator_bond_less( + scheduled_requests.len() as u32, + ); ensure!( !scheduled_requests @@ -211,7 +212,7 @@ impl Pallet { let mut state = >::get(&delegator).ok_or(>::DelegatorDNE)?; let mut scheduled_requests = >::get(&collator); let actual_weight = - T::WeightInfo::cancel_delegation_request(scheduled_requests.len() as u32); + ::WeightInfo::cancel_delegation_request(scheduled_requests.len() as u32); let request = Self::cancel_request_with_state(&delegator, &mut state, &mut scheduled_requests) @@ -270,7 +271,8 @@ impl Pallet { match request.action { DelegationAction::Revoke(amount) => { - let actual_weight = T::WeightInfo::execute_delegator_revoke_delegation_worst(); + let actual_weight = + ::WeightInfo::execute_delegator_revoke_delegation_worst(); // revoking last delegation => leaving set of delegators let leaving = if state.delegations.0.len() == 1usize { @@ -321,7 +323,8 @@ impl Pallet { Ok(Some(actual_weight).into()) } DelegationAction::Decrease(_) => { - let actual_weight = T::WeightInfo::execute_delegator_revoke_delegation_worst(); + let actual_weight = + ::WeightInfo::execute_delegator_revoke_delegation_worst(); // remove from pending requests let amount = scheduled_requests.remove(request_idx).action.amount(); diff --git a/pallets/parachain-staking/src/inflation.rs b/pallets/parachain-staking/src/inflation.rs index 64f346aab5..f8d37d3056 100644 --- a/pallets/parachain-staking/src/inflation.rs +++ b/pallets/parachain-staking/src/inflation.rs @@ -25,9 +25,13 @@ use sp_runtime::{Perbill, RuntimeDebug}; use substrate_fixed::transcendental::pow as floatpow; use substrate_fixed::types::I64F64; +// Milliseconds per year +const MS_PER_YEAR: u64 = 31_557_600_000; + fn rounds_per_year() -> u32 { - let blocks_per_round = >::round().length; - T::SlotsPerYear::get() / blocks_per_round + let blocks_per_round = >::round().length as u64; + let blocks_per_year = MS_PER_YEAR / T::BlockTime::get(); + (blocks_per_year / blocks_per_round) as u32 } #[derive( @@ -133,8 +137,8 @@ impl InflationInfo { } /// Reset round inflation rate based on changes to round length pub fn reset_round(&mut self, new_length: u32) { - let periods = T::SlotsPerYear::get() / new_length; - self.round = perbill_annual_to_perbill_round(self.annual, periods); + let periods = (MS_PER_YEAR / T::BlockTime::get()) / (new_length as u64); + self.round = perbill_annual_to_perbill_round(self.annual, periods as u32); } /// Set staking expectations pub fn set_expectations(&mut self, expect: Range) { diff --git a/pallets/parachain-staking/src/lib.rs b/pallets/parachain-staking/src/lib.rs index 5511a359b7..acadf403a1 100644 --- a/pallets/parachain-staking/src/lib.rs +++ b/pallets/parachain-staking/src/lib.rs @@ -182,16 +182,19 @@ pub mod pallet { /// Handler to notify the runtime when a new round begin. /// If you don't need it, you can specify the type `()`. type OnNewRound: OnNewRound; - /// Get the slot number to use as clocktime for staking rounds + /// Get the current slot number type SlotProvider: Get; - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; + /// Get the slot duration in milliseconds + #[pallet::constant] + type SlotDuration: Get; + /// Get the average time beetween 2 blocks in milliseconds + #[pallet::constant] + type BlockTime: Get; /// Maximum candidates #[pallet::constant] type MaxCandidates: Get; - /// Average number of slots per year - /// A slot here is the unit of time for staking rounds (provided by SlotProvider) - type SlotsPerYear: Get; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; } #[pallet::error] @@ -258,7 +261,7 @@ pub mod pallet { pub enum Event { /// Started new round. NewRound { - starting_block: u64, + starting_block: BlockNumberFor, round: RoundIndex, selected_collators_number: u32, total_balance: BalanceOf, @@ -430,7 +433,7 @@ pub mod pallet { /// Set blocks per round BlocksPerRoundSet { current_round: RoundIndex, - first_block: u64, + first_block: BlockNumberFor, old: u32, new: u32, new_per_round_inflation_min: Perbill, @@ -453,39 +456,42 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { - fn on_initialize(_n: BlockNumberFor) -> Weight { - let mut weight = T::WeightInfo::base_on_initialize(); + fn on_initialize(n: BlockNumberFor) -> Weight { + let mut weight = ::WeightInfo::base_on_initialize(); - // fetch slot number - let slot: u64 = T::SlotProvider::get().into(); + let mut round = >::get(); + if round.should_update(n.into()) { + // fetch current slot number + let current_slot: u64 = T::SlotProvider::get().into(); - // account for SlotProvider read - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); + // account for SlotProvider read + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); + + // Compute round duration in slots + let round_duration = (current_slot.saturating_sub(round.first_slot)) + .saturating_mul(T::SlotDuration::get()); - let mut round = >::get(); - if round.should_update(slot) { // mutate round - round.update(slot); + round.update(n.into(), current_slot); // notify that new round begin weight = weight.saturating_add(T::OnNewRound::on_new_round(round.current)); // pay all stakers for T::RewardPaymentDelay rounds ago - weight = weight.saturating_add(Self::prepare_staking_payouts(round.current)); + weight = + weight.saturating_add(Self::prepare_staking_payouts(round, round_duration)); // select top collator candidates for next round let (extra_weight, collator_count, _delegation_count, total_staked) = Self::select_top_candidates(round.current); weight = weight.saturating_add(extra_weight); // start next round >::put(round); - // snapshot total stake - >::insert(round.current, >::get()); Self::deposit_event(Event::NewRound { starting_block: round.first, round: round.current, selected_collators_number: collator_count, total_balance: total_staked, }); - // account for Round and Staked writes - weight = weight.saturating_add(T::DbWeight::get().reads_writes(0, 2)); + // account for Round write + weight = weight.saturating_add(T::DbWeight::get().reads_writes(0, 1)); } else { weight = weight.saturating_add(Self::handle_delayed_payouts(round.current)); } @@ -520,7 +526,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn round)] /// Current round index and next round scheduled transition - pub type Round = StorageValue<_, RoundInfo, ValueQuery>; + pub type Round = StorageValue<_, RoundInfo>, ValueQuery>; #[pallet::storage] #[pallet::getter(fn delegator_state)] @@ -641,11 +647,6 @@ pub mod pallet { pub type DelayedPayouts = StorageMap<_, Twox64Concat, RoundIndex, DelayedPayout>, OptionQuery>; - #[pallet::storage] - #[pallet::getter(fn staked)] - /// Total counted stake for selected candidates in the round - pub type Staked = StorageMap<_, Twox64Concat, RoundIndex, BalanceOf, ValueQuery>; - #[pallet::storage] #[pallet::getter(fn inflation_config)] /// Inflation configuration @@ -807,12 +808,11 @@ pub mod pallet { // Choose top TotalSelected collator candidates let (_, v_count, _, total_staked) = >::select_top_candidates(1u32); // Start Round 1 at Block 0 - let round: RoundInfo = RoundInfo::new(1u32, 0u64, self.blocks_per_round); + let round: RoundInfo> = + RoundInfo::new(1u32, Zero::zero(), self.blocks_per_round, 0); >::put(round); - // Snapshot total stake - >::insert(1u32, >::get()); >::deposit_event(Event::NewRound { - starting_block: u64::default(), + starting_block: Zero::zero(), round: 1u32, selected_collators_number: v_count, total_balance: total_staked, @@ -1280,7 +1280,7 @@ pub mod pallet { /// rewards for rounds while the request is pending use the reduced bonded amount. /// A bond less may not be performed if any other scheduled request is pending. #[pallet::call_index(24)] - #[pallet::weight(T::WeightInfo::schedule_delegator_bond_less( + #[pallet::weight(::WeightInfo::schedule_delegator_bond_less( T::MaxTopDelegationsPerCandidate::get() + T::MaxBottomDelegationsPerCandidate::get() ))] pub fn schedule_delegator_bond_less( @@ -1480,7 +1480,8 @@ pub mod pallet { impl Pallet { pub fn set_candidate_bond_to_zero(acc: &T::AccountId) -> Weight { - let actual_weight = T::WeightInfo::set_candidate_bond_to_zero(T::MaxCandidates::get()); + let actual_weight = + ::WeightInfo::set_candidate_bond_to_zero(T::MaxCandidates::get()); if let Some(mut state) = >::get(&acc) { state.bond_less::(acc.clone(), state.bond); >::insert(&acc, state); @@ -1547,7 +1548,7 @@ pub mod pallet { pub fn go_offline_inner(collator: T::AccountId) -> DispatchResultWithPostInfo { let mut state = >::get(&collator).ok_or(Error::::CandidateDNE)?; let mut candidates = >::get(); - let actual_weight = T::WeightInfo::go_offline(candidates.0.len() as u32); + let actual_weight = ::WeightInfo::go_offline(candidates.0.len() as u32); ensure!( state.is_active(), @@ -1571,7 +1572,7 @@ pub mod pallet { pub fn go_online_inner(collator: T::AccountId) -> DispatchResultWithPostInfo { let mut state = >::get(&collator).ok_or(Error::::CandidateDNE)?; let mut candidates = >::get(); - let actual_weight = T::WeightInfo::go_online(candidates.0.len() as u32); + let actual_weight = ::WeightInfo::go_online(candidates.0.len() as u32); ensure!( !state.is_active(), @@ -1616,7 +1617,8 @@ pub mod pallet { more: BalanceOf, ) -> DispatchResultWithPostInfo { let mut state = >::get(&collator).ok_or(Error::::CandidateDNE)?; - let actual_weight = T::WeightInfo::candidate_bond_more(T::MaxCandidates::get()); + let actual_weight = + ::WeightInfo::candidate_bond_more(T::MaxCandidates::get()); state .bond_more::(collator.clone(), more) @@ -1636,7 +1638,8 @@ pub mod pallet { candidate: T::AccountId, ) -> DispatchResultWithPostInfo { let mut state = >::get(&candidate).ok_or(Error::::CandidateDNE)?; - let actual_weight = T::WeightInfo::execute_candidate_bond_less(T::MaxCandidates::get()); + let actual_weight = + ::WeightInfo::execute_candidate_bond_less(T::MaxCandidates::get()); state .execute_bond_less::(candidate.clone()) @@ -1657,7 +1660,7 @@ pub mod pallet { // TODO use these to return actual weight used via `execute_leave_candidates_ideal` let actual_delegation_count = state.delegation_count; - let actual_weight = T::WeightInfo::execute_leave_candidates_ideal( + let actual_weight = ::WeightInfo::execute_leave_candidates_ideal( actual_delegation_count, actual_auto_compound_delegation_count, ); @@ -1773,18 +1776,18 @@ pub mod pallet { >::put(candidates); } - /// Compute round issuance based on total staked for the given round - fn compute_issuance(staked: BalanceOf) -> BalanceOf { + /// Compute round issuance based on duration of the given round + fn compute_issuance(round_duration: u64, round_length: u32) -> BalanceOf { + let ideal_duration: BalanceOf = round_length + .saturating_mul(T::BlockTime::get() as u32) + .into(); let config = >::get(); let round_issuance = crate::inflation::round_issuance_range::(config.round); - // TODO: consider interpolation instead of bounded range - if staked < config.expect.min { - round_issuance.min - } else if staked > config.expect.max { - round_issuance.max - } else { - round_issuance.ideal - } + + // Initial formula: (round_duration / ideal_duration) * ideal_issuance + // We multiply before the division to reduce rounding effects + BalanceOf::::from(round_duration as u32).saturating_mul(round_issuance.ideal) + / (ideal_duration) } /// Remove delegation from candidate state @@ -1809,27 +1812,37 @@ pub mod pallet { Ok(()) } - pub(crate) fn prepare_staking_payouts(now: RoundIndex) -> Weight { - // payout is now - delay rounds ago => now - delay > 0 else return early - let delay = T::RewardPaymentDelay::get(); - if now <= delay { - return Weight::zero(); - } - let round_to_payout = now.saturating_sub(delay); - let total_points = >::get(round_to_payout); - if total_points.is_zero() { + pub(crate) fn prepare_staking_payouts( + round_info: RoundInfo>, + round_duration: u64, + ) -> Weight { + let RoundInfo { + current: now, + length: round_length, + .. + } = round_info; + + // This function is called right after the round index increment, + // and the goal is to compute the payout informations for the round that just ended. + // We don't need to saturate here because the genesis round is 1. + let prepare_payout_for_round = now - 1; + + // Return early if there is no blocks for this round + if >::get(prepare_payout_for_round).is_zero() { return Weight::zero(); } - let total_staked = >::take(round_to_payout); - let total_issuance = Self::compute_issuance(total_staked); - let mut left_issuance = total_issuance; + + // Compute total issuance based on round duration + let total_issuance = Self::compute_issuance(round_duration, round_length); + // reserve portion of issuance for parachain bond account + let mut left_issuance = total_issuance; let bond_config = >::get(); let parachain_bond_reserve = bond_config.percent * total_issuance; if let Ok(imb) = T::Currency::deposit_into_existing(&bond_config.account, parachain_bond_reserve) { - // update round issuance iff transfer succeeds + // update round issuance if transfer succeeds left_issuance = left_issuance.saturating_sub(imb.peek()); Self::deposit_event(Event::ReservedForParachainBond { account: bond_config.account, @@ -1843,8 +1856,9 @@ pub mod pallet { collator_commission: >::get(), }; - >::insert(round_to_payout, payout); - T::WeightInfo::prepare_staking_payouts() + >::insert(prepare_payout_for_round, payout); + + ::WeightInfo::prepare_staking_payouts() } /// Wrapper around pay_one_collator_reward which handles the following logic: @@ -1983,16 +1997,17 @@ pub mod pallet { } } - extra_weight = - extra_weight.saturating_add(T::WeightInfo::pay_one_collator_reward_best( + extra_weight = extra_weight.saturating_add( + ::WeightInfo::pay_one_collator_reward_best( num_paid_delegations, num_auto_compounding, num_scheduled_requests as u32, - )); + ), + ); ( RewardPayment::Paid, - T::WeightInfo::pay_one_collator_reward(num_delegators as u32) + ::WeightInfo::pay_one_collator_reward(num_delegators as u32) .saturating_add(extra_weight), ) } else { @@ -2080,7 +2095,7 @@ pub mod pallet { total_exposed_amount: *snapshot_total, }) } - let weight = T::WeightInfo::select_top_candidates(0, 0); + let weight = ::WeightInfo::select_top_candidates(0, 0); return (weight, collator_count, delegation_count, total); } @@ -2133,7 +2148,10 @@ pub mod pallet { ); let avg_delegator_count = delegation_count.checked_div(collator_count).unwrap_or(0); - let weight = T::WeightInfo::select_top_candidates(collator_count, avg_delegator_count); + let weight = ::WeightInfo::select_top_candidates( + collator_count, + avg_delegator_count, + ); (weight, collator_count, delegation_count, total) } @@ -2197,7 +2215,7 @@ pub mod pallet { Error::::PendingDelegationRevoke ); - let actual_weight = T::WeightInfo::delegator_bond_more( + let actual_weight = ::WeightInfo::delegator_bond_more( >::decode_len(&candidate).unwrap_or_default() as u32, ); let in_top = state @@ -2232,7 +2250,7 @@ pub mod pallet { rewards: amount_transferred.peek(), }); } - T::WeightInfo::mint_collator_reward() + ::WeightInfo::mint_collator_reward() } /// Mint and compound delegation rewards. The function mints the amount towards the diff --git a/pallets/parachain-staking/src/migrations.rs b/pallets/parachain-staking/src/migrations.rs index de1629368d..3971d6e87a 100644 --- a/pallets/parachain-staking/src/migrations.rs +++ b/pallets/parachain-staking/src/migrations.rs @@ -15,3 +15,191 @@ // along with Moonbeam. If not, see . //! # Migrations + +use crate::{types::RoundInfo, Config, RoundIndex}; +use frame_support::pallet_prelude::*; +use frame_support::storage::generator::StorageValue; +use frame_support::storage::unhashed; +use frame_support::traits::OnRuntimeUpgrade; +use frame_system::pallet_prelude::*; +use sp_runtime::Saturating; + +#[cfg(feature = "try-runtime")] +use sp_std::vec::Vec; + +/// Multiply round length by 2 +pub struct MultiplyRoundLenBy2(core::marker::PhantomData); + +impl OnRuntimeUpgrade for MultiplyRoundLenBy2 +where + T: Config, + BlockNumberFor: From + Into, +{ + fn on_runtime_upgrade() -> frame_support::pallet_prelude::Weight { + let mut round = crate::Round::::get(); + + // Multiply round length by 2 + round.length = round.length * 2; + + crate::Round::::put(round); + + Default::default() + } +} + +/// Migrates RoundInfo and add the field first_slot +pub struct MigrateRoundWithFirstSlot(core::marker::PhantomData); + +#[derive(Decode)] +struct RoundInfoRt2800 { + /// Current round index + pub current: RoundIndex, + /// The first block of the current round + pub first: u64, + /// The length of the current round in number of blocks + pub length: u32, +} +impl> From for RoundInfo { + fn from(round: RoundInfoRt2800) -> Self { + Self { + current: round.current, + first: (round.first as u32).into(), + length: round.length, + first_slot: 0, + } + } +} + +#[derive(Decode)] +struct RoundInfoRt2700 { + /// Current round index + pub current: RoundIndex, + /// The first block of the current round + pub first: u32, + /// The length of the current round in number of blocks + pub length: u32, +} +impl> From for RoundInfo { + fn from(round: RoundInfoRt2700) -> Self { + Self { + current: round.current, + first: round.first.into(), + length: round.length, + first_slot: 0, + } + } +} + +impl OnRuntimeUpgrade for MigrateRoundWithFirstSlot +where + T: Config, + BlockNumberFor: From + Into, +{ + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + let raw_key = crate::Round::::storage_value_final_key(); + let maybe_raw_value = unhashed::get_raw(&raw_key); + let len = maybe_raw_value + .expect("ParachainStaking.Round should exist!") + .len(); + ensure!( + len == 12 || len == 16, + "ParachainStaking.Round should have 12 or 16 bytes length!" + ); + + Ok(Vec::new()) + } + + fn on_runtime_upgrade() -> frame_support::pallet_prelude::Weight { + let raw_key = crate::Round::::storage_value_final_key(); + + // Read old round info + let mut round: RoundInfo> = if let Some(bytes) = + unhashed::get_raw(&raw_key) + { + let len = bytes.len(); + match len { + // Migration already done + 20 => { + log::info!("MigrateRoundWithFirstSlot already applied."); + return Default::default(); + } + // Migrate from rt2800 + 16 => match RoundInfoRt2800::decode(&mut &bytes[..]) { + Ok(round) => round.into(), + Err(e) => panic!("corrupted storage: fail to decode RoundInfoRt2800: {}", e), + }, + // Migrate from rt2700 + 12 => match RoundInfoRt2700::decode(&mut &bytes[..]) { + Ok(round) => round.into(), + Err(e) => panic!("corrupted storage: fail to decode RoundInfoRt2700: {}", e), + }, + // Storage corrupted + x => panic!( + "corrupted storage: parachainStaking.Round invalid length: {} bytes", + x + ), + } + } else { + panic!("corrupted storage: parachainStaking.Round don't exist"); + }; + + // Compute new field `first_slot`` + round.first_slot = compute_theoretical_first_slot( + >::block_number(), + round.first, + u64::from(T::SlotProvider::get()), + T::BlockTime::get(), + ); + + // Fill DelayedPayouts for rounds N and N-1 + if let Some(delayed_payout) = + >::get(round.current.saturating_sub(2)) + { + >::insert( + round.current.saturating_sub(1), + delayed_payout.clone(), + ); + >::insert(round.current, delayed_payout); + } + + // Apply the migration (write new Round value) + crate::Round::::put(round); + + Default::default() + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + let round = crate::Round::::get(); // Should panic if SCALE decode fail + Ok(()) + } +} + +fn compute_theoretical_first_slot>( + current_block: BlockNumber, + first_block: BlockNumber, + current_slot: u64, + block_time: u64, +) -> u64 { + let blocks_since_first: u64 = (current_block.saturating_sub(first_block)).into(); + let slots_since_first = match block_time { + 12_000 => blocks_since_first * 2, + 6_000 => blocks_since_first, + _ => panic!("Unsupported BlockTime"), + }; + current_slot.saturating_sub(slots_since_first) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_compute_theoretical_first_slot() { + assert_eq!( + compute_theoretical_first_slot::(10, 5, 100, 12_000), + 90, + ); + } +} diff --git a/pallets/parachain-staking/src/mock.rs b/pallets/parachain-staking/src/mock.rs index 03f83c421e..8e2f1baaa3 100644 --- a/pallets/parachain-staking/src/mock.rs +++ b/pallets/parachain-staking/src/mock.rs @@ -161,7 +161,8 @@ impl Config for Test { type SlotProvider = StakingRoundSlotProvider; type WeightInfo = (); type MaxCandidates = MaxCandidates; - type SlotsPerYear = frame_support::traits::ConstU32<{ 31_557_600 / 6 }>; + type SlotDuration = frame_support::traits::ConstU64<6_000>; + type BlockTime = frame_support::traits::ConstU64<6_000>; } pub(crate) struct ExtBuilder { diff --git a/pallets/parachain-staking/src/tests.rs b/pallets/parachain-staking/src/tests.rs index 36843b3bd5..a582e1701b 100644 --- a/pallets/parachain-staking/src/tests.rs +++ b/pallets/parachain-staking/src/tests.rs @@ -5395,6 +5395,9 @@ fn payouts_follow_delegation_changes() { ]) .build() .execute_with(|| { + // ~ set block author as 1 for all blocks this round + set_author(1, 1, 100); + set_author(2, 1, 100); roll_to_round_begin(2); // chooses top TotalSelectedCandidates (5), in order assert_events_eq!( @@ -5425,8 +5428,10 @@ fn payouts_follow_delegation_changes() { total_balance: 130, }, ); - // ~ set block author as 1 for all blocks this round - set_author(2, 1, 100); + + set_author(3, 1, 100); + set_author(4, 1, 100); + roll_to_round_begin(4); // distribute total issuance to collator 1 and its delegators 6, 7, 19 assert_events_eq!( @@ -5477,10 +5482,7 @@ fn payouts_follow_delegation_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); - set_author(6, 1, 100); roll_blocks(1); // 1. ensure delegators are paid for 2 rounds after they leave @@ -5547,6 +5549,8 @@ fn payouts_follow_delegation_changes() { rewards: 8, }, ); + + set_author(6, 1, 100); // keep paying 6 (note: inflation is in terms of total issuance so that's why 1 is 21) roll_to_round_begin(6); assert_ok!(ParachainStaking::execute_delegation_request( @@ -5663,6 +5667,7 @@ fn payouts_follow_delegation_changes() { rewards: 10, }, ); + set_author(8, 1, 100); roll_to_round_begin(8); assert_events_eq!( Event::CollatorChosen { @@ -5696,7 +5701,7 @@ fn payouts_follow_delegation_changes() { assert_events_eq!( Event::Rewarded { account: 1, - rewards: 33, + rewards: 32, }, Event::Rewarded { account: 7, @@ -5707,7 +5712,7 @@ fn payouts_follow_delegation_changes() { rewards: 11, }, ); - set_author(8, 1, 100); + set_author(9, 1, 100); roll_to_round_begin(9); // no more paying 6 assert_events_eq!( @@ -5754,7 +5759,6 @@ fn payouts_follow_delegation_changes() { }, ); roll_blocks(1); - set_author(9, 1, 100); assert_ok!(ParachainStaking::delegate( RuntimeOrigin::signed(8), 1, @@ -5770,6 +5774,7 @@ fn payouts_follow_delegation_changes() { auto_compound: Percent::zero(), }); + set_author(10, 1, 100); roll_to_round_begin(10); // new delegation is not rewarded yet assert_events_eq!( @@ -5815,7 +5820,6 @@ fn payouts_follow_delegation_changes() { rewards: 12, }, ); - set_author(10, 1, 100); roll_to_round_begin(11); // new delegation not rewarded yet assert_events_eq!( @@ -5850,7 +5854,7 @@ fn payouts_follow_delegation_changes() { assert_events_eq!( Event::Rewarded { account: 1, - rewards: 38, + rewards: 37, }, Event::Rewarded { account: 7, @@ -5900,15 +5904,15 @@ fn payouts_follow_delegation_changes() { }, Event::Rewarded { account: 7, - rewards: 11, + rewards: 10, }, Event::Rewarded { account: 10, - rewards: 11, + rewards: 10, }, Event::Rewarded { account: 8, - rewards: 11, + rewards: 10, }, ); }); @@ -6313,13 +6317,14 @@ fn no_rewards_paid_until_after_reward_payment_delay() { .with_candidates(vec![(1, 20), (2, 20), (3, 20)]) .build() .execute_with(|| { - roll_to_round_begin(2); // payouts for round 1 set_author(1, 1, 1); set_author(1, 2, 1); set_author(1, 2, 1); set_author(1, 3, 1); set_author(1, 3, 1); + + roll_to_round_begin(2); assert_events_eq!( Event::CollatorChosen { round: 2, @@ -6440,27 +6445,15 @@ fn deferred_payment_storage_items_are_cleaned_up() { assert!(>::contains_key(1, 1)); assert!(>::contains_key(1, 2)); - assert!( - !>::contains_key(1), - "DelayedPayouts shouldn't be populated until after RewardPaymentDelay" - ); assert!( >::contains_key(1), "Points should be populated during current round" ); - assert!( - >::contains_key(1), - "Staked should be populated when round changes" - ); assert!( !>::contains_key(2), "Points should not be populated until author noted" ); - assert!( - >::contains_key(2), - "Staked should be populated when round changes" - ); // first payout occurs in round 3 roll_to_round_begin(3); @@ -6500,24 +6493,17 @@ fn deferred_payment_storage_items_are_cleaned_up() { "DelayedPayouts should be populated after RewardPaymentDelay" ); assert!(>::contains_key(1)); - assert!( - !>::contains_key(1), - "Staked should be cleaned up after round change" - ); - assert!(!>::contains_key(2)); assert!( !>::contains_key(2), "We never rewarded points for round 2" ); - assert!(>::contains_key(2)); assert!(!>::contains_key(3)); assert!( !>::contains_key(3), "We never awarded points for round 3" ); - assert!(>::contains_key(3)); // collator 1 has been paid in this last block and associated storage cleaned up assert!(!>::contains_key(1, 1)); @@ -6557,7 +6543,6 @@ fn deferred_payment_storage_items_are_cleaned_up() { // collators have both been paid and storage fully cleaned up for round 1 assert!(!>::contains_key(1, 2)); assert!(!>::contains_key(1, 2)); - assert!(!>::contains_key(1)); assert!(!>::contains_key(1)); // points should be cleaned up assert!(!>::contains_key(1)); @@ -6592,7 +6577,6 @@ fn deferred_payment_and_at_stake_storage_items_cleaned_up_for_candidates_not_pro assert!(>::contains_key(1, 1)); assert!(>::contains_key(1, 2)); assert!(!>::contains_key(1, 3)); - assert!(>::contains_key(1)); assert!(>::contains_key(1)); roll_to_round_begin(3); assert!(>::contains_key(1)); @@ -6605,7 +6589,6 @@ fn deferred_payment_and_at_stake_storage_items_cleaned_up_for_candidates_not_pro assert!(!>::contains_key(1, 1)); assert!(!>::contains_key(1, 2)); assert!(!>::contains_key(1, 3)); - assert!(!>::contains_key(1)); assert!(!>::contains_key(1)); assert!(!>::contains_key(1)); }); @@ -6720,7 +6703,7 @@ fn deferred_payment_steady_state_event_flow() { total_exposed_amount: 400, }, Event::NewRound { - starting_block: (round as u64 - 1) * 5, + starting_block: (round as u32 - 1) * 5, round: round as u32, selected_collators_number: 4, total_balance: 1600, @@ -8693,12 +8676,11 @@ fn test_on_initialize_weights() { let weight = ParachainStaking::on_initialize(1); // TODO: build this with proper db reads/writes - assert_eq!(Weight::from_parts(302168000, 0), weight); + assert_eq!(Weight::from_parts(277168000, 0), weight); // roll to the end of the round, then run on_init again, we should see round change... + set_author(3, 1, 100); // must set some points for prepare_staking_payouts roll_to_round_end(3); - set_author(2, 1, 100); // must set some points for prepare_staking_payouts - System::set_block_number(System::block_number() + 1); let block = System::block_number() + 1; let weight = ParachainStaking::on_initialize(block); @@ -8708,7 +8690,7 @@ fn test_on_initialize_weights() { // // following this assertion, we add individual weights together to show that we can // derive this number independently. - let expected_on_init = 2504547135; + let expected_on_init = 2404547135; assert_eq!(Weight::from_parts(expected_on_init, 32562), weight); // assemble weight manually to ensure it is well understood @@ -8726,8 +8708,8 @@ fn test_on_initialize_weights() { .ref_time(); // SlotProvider read expected_weight += RocksDbWeight::get().reads_writes(1, 0).ref_time(); - // Round and Staked writes, done in on-round-change code block inside on_initialize() - expected_weight += RocksDbWeight::get().reads_writes(0, 2).ref_time(); + // Round write, done in on-round-change code block inside on_initialize() + expected_weight += RocksDbWeight::get().reads_writes(0, 1).ref_time(); // more reads/writes manually accounted for for on_finalize expected_weight += RocksDbWeight::get().reads_writes(3, 2).ref_time(); diff --git a/pallets/parachain-staking/src/types.rs b/pallets/parachain-staking/src/types.rs index 1ad0a19be8..8d77323d98 100644 --- a/pallets/parachain-staking/src/types.rs +++ b/pallets/parachain-staking/src/types.rs @@ -170,7 +170,7 @@ impl Default for CollatorSnapshot { } } -#[derive(Default, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(Clone, Default, Encode, Decode, RuntimeDebug, TypeInfo)] /// Info needed to make delayed payments to stakers after round end pub struct DelayedPayout { /// Total round reward (result of compute_issuance() at round end) @@ -1713,16 +1713,19 @@ pub struct RoundInfo { pub first: BlockNumber, /// The length of the current round in number of blocks pub length: u32, + /// The first slot of the current round + pub first_slot: u64, } impl< B: Copy + sp_std::ops::Add + sp_std::ops::Sub + From + PartialOrd, > RoundInfo { - pub fn new(current: RoundIndex, first: B, length: u32) -> RoundInfo { + pub fn new(current: RoundIndex, first: B, length: u32, first_slot: u64) -> RoundInfo { RoundInfo { current, first, length, + first_slot, } } /// Check if the round should be updated @@ -1730,9 +1733,10 @@ impl< now - self.first >= self.length.into() } /// New round - pub fn update(&mut self, now: B) { + pub fn update(&mut self, now: B, now_slot: u64) { self.current = self.current.saturating_add(1u32); self.first = now; + self.first_slot = now_slot; } } impl< @@ -1740,7 +1744,7 @@ impl< > Default for RoundInfo { fn default() -> RoundInfo { - RoundInfo::new(1u32, 1u32.into(), 20u32) + RoundInfo::new(1u32, 1u32.into(), 20u32, 0) } } diff --git a/precompiles/parachain-staking/src/mock.rs b/precompiles/parachain-staking/src/mock.rs index 4e543e2736..0dcb75d292 100644 --- a/precompiles/parachain-staking/src/mock.rs +++ b/precompiles/parachain-staking/src/mock.rs @@ -221,7 +221,8 @@ impl pallet_parachain_staking::Config for Runtime { type SlotProvider = StakingRoundSlotProvider; type WeightInfo = (); type MaxCandidates = MaxCandidates; - type SlotsPerYear = frame_support::traits::ConstU32<{ 31_557_600 / 6 }>; + type SlotDuration = frame_support::traits::ConstU64<6_000>; + type BlockTime = frame_support::traits::ConstU64<6_000>; } pub(crate) struct ExtBuilder { diff --git a/runtime/common/src/apis.rs b/runtime/common/src/apis.rs index bff59e6227..c2f237ba4f 100644 --- a/runtime/common/src/apis.rs +++ b/runtime/common/src/apis.rs @@ -506,8 +506,6 @@ macro_rules! impl_runtime_apis_plus_common { use pallet_parachain_staking::Config as PalletParachainStakingConfig; let block_number = parent_header.number + 1; - let parachain_staking_slot: u64 = - ::SlotProvider::get().into(); // The Moonbeam runtimes use an entropy source that needs to do some accounting // work during block initialization. Therefore we initialize it here to match @@ -523,7 +521,7 @@ macro_rules! impl_runtime_apis_plus_common { // of the first block in the new round, the only way to accurately predict the // authors is to compute the selection during prediction. if pallet_parachain_staking::Pallet::::round() - .should_update(parachain_staking_slot) { + .should_update(block_number) { // get author account id use nimbus_primitives::AccountLookup; let author_account_id = if let Some(account) = diff --git a/runtime/common/src/migrations.rs b/runtime/common/src/migrations.rs index 40b0db2afe..e3a7f12d5f 100644 --- a/runtime/common/src/migrations.rs +++ b/runtime/common/src/migrations.rs @@ -24,158 +24,37 @@ use frame_support::ensure; #[cfg(feature = "try-runtime")] use frame_support::migration::get_storage_value; use frame_support::{ - parameter_types, - sp_runtime::traits::{Block as BlockT, Header as HeaderT}, - storage::unhashed::contains_prefixed_key, - traits::OnRuntimeUpgrade, + parameter_types, storage::unhashed::contains_prefixed_key, traits::OnRuntimeUpgrade, weights::Weight, }; use frame_system::pallet_prelude::BlockNumberFor; use pallet_author_slot_filter::Config as AuthorSlotFilterConfig; use pallet_migrations::{GetMigrations, Migration}; -use pallet_parachain_staking::{Round, RoundIndex, RoundInfo}; -use parity_scale_codec::{Decode, Encode}; -use sp_consensus_slots::Slot; -use sp_core::Get; +use pallet_parachain_staking::migrations::MigrateRoundWithFirstSlot; use sp_std::{marker::PhantomData, prelude::*, vec}; -#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode)] -pub struct OldRoundInfo { - pub current: RoundIndex, - pub first: BlockNumber, - pub length: u32, -} -pub struct UpdateFirstRoundNumberValue(pub PhantomData); -impl Migration for UpdateFirstRoundNumberValue +pub struct PalletStakingRoundMigration(PhantomData); +impl Migration for PalletStakingRoundMigration where - T: pallet_parachain_staking::Config, - T: pallet_async_backing::Config, - T: frame_system::Config, - u32: From<<<::Block as BlockT>::Header as HeaderT>::Number>, + Runtime: pallet_parachain_staking::Config, + BlockNumberFor: Into, { fn friendly_name(&self) -> &str { - "MM_UpdateFirstRoundNumberValue" - } - - fn migrate(&self, _available_weight: Weight) -> Weight { - let _ = Round::::translate::>, _>(|v0| { - let old_current = v0 - .expect("old current round value must be present!") - .current; - let old_first: u32 = v0.expect("old first should be present!").first.into(); - let old_length = v0.expect("old round length value must be present!").length; - - // Fetch the last parachain block - let para_block: u32 = frame_system::Pallet::::block_number().into(); - - // Calculate how many blocks have passed so far in this round - let para_block_diff: u64 = para_block.saturating_sub(old_first).into(); - - // Read the last relay slot from the SlotInfo storage - let relay_slot = pallet_async_backing::Pallet::::slot_info() - .unwrap_or((Slot::from(284_000_000u64), 0u32)) - .0; - - // Calculate the new first - let new_first = u64::from(relay_slot).saturating_sub(para_block_diff); - - Some(RoundInfo { - current: old_current, - first: new_first, - length: old_length, - }) - }); - - T::DbWeight::get().reads_writes(1, 1) + "MM_MigrateRoundWithFirstSlot" } #[cfg(feature = "try-runtime")] fn pre_upgrade(&self) -> Result, sp_runtime::DispatchError> { - let module: &[u8] = b"ParachainStaking"; - let item: &[u8] = b"Round"; - let pre_round_info = get_storage_value::>>(module, item, &[]); - Ok(pre_round_info.unwrap_or_default().encode()) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(&self, state: Vec) -> Result<(), sp_runtime::DispatchError> { - let pre_round_info = - > as Decode>::decode(&mut &*state).unwrap_or_default(); - let post_round_info = pallet_parachain_staking::Pallet::::round(); - - let slot_after = pallet_async_backing::Pallet::::slot_info() - .unwrap_or((Slot::from(280_000_000u64), 0u32)) - .0; - - ensure!( - u64::from(slot_after) > post_round_info.first, - "Post-round first must be lower than last relay slot" - ); - ensure!( - post_round_info.current >= pre_round_info.current, - "Post-round number must be higher than or equal pre-round one" - ); - ensure!( - pre_round_info.length == post_round_info.length, - "Post-round length must be equal to pre-round one" - ); - Ok(()) - } -} - -/// Translates the Round.first value type from BlockNumberFor to u64 -pub struct UpdateFirstRoundNumberType(pub PhantomData); -impl Migration for UpdateFirstRoundNumberType -where - T: pallet_parachain_staking::Config, - T: frame_system::Config, - u64: From<<<::Block as BlockT>::Header as HeaderT>::Number>, -{ - fn friendly_name(&self) -> &str { - "MM_UpdateFirstRoundNumberType" + MigrateRoundWithFirstSlot::::pre_upgrade() } fn migrate(&self, _available_weight: Weight) -> Weight { - let _ = Round::::translate::>, _>(|v0| { - let old_current = v0 - .expect("old current round value must be present!") - .current; - - let new_first: u64 = v0.expect("old first should be present!").first.into(); - let old_length = v0.expect("old round length value must be present!").length; - - Some(RoundInfo { - current: old_current, - first: new_first, - length: old_length, - }) - }); - - T::DbWeight::get().reads_writes(1, 1) - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade(&self) -> Result, sp_runtime::DispatchError> { - let module: &[u8] = b"ParachainStaking"; - let item: &[u8] = b"Round"; - let pre_round_info = get_storage_value::>>(module, item, &[]); - Ok(pre_round_info.unwrap_or_default().encode()) + MigrateRoundWithFirstSlot::::on_runtime_upgrade() } #[cfg(feature = "try-runtime")] fn post_upgrade(&self, state: Vec) -> Result<(), sp_runtime::DispatchError> { - let pre_round_info = - > as Decode>::decode(&mut &*state).unwrap_or_default(); - let post_round_info = pallet_parachain_staking::Pallet::::round(); - ensure!( - post_round_info.first == u64::from(pre_round_info.first), - "Post-round number must be equal to pre-round one" - ); - ensure!( - pre_round_info.length == post_round_info.length, - "Post-round length must be equal to pre-round one" - ); - Ok(()) + MigrateRoundWithFirstSlot::::post_upgrade(state) } } @@ -247,6 +126,7 @@ where Runtime: pallet_balances::Config, Runtime: pallet_referenda::Config, Runtime::AccountId: Default, + BlockNumberFor: Into, { fn get_migrations() -> Vec> { // let migration_author_mapping_twox_to_blake = AuthorMappingTwoXToBlake:: { @@ -319,8 +199,9 @@ where // FixIncorrectPalletVersions::(Default::default()); // let pallet_referenda_migrate_v0_to_v1 = // PalletReferendaMigrateV0ToV1::(Default::default()); - // let pallet_collective_drop_gov_v1_collectives = - // PalletCollectiveDropGovV1Collectives::(Default::default()); + //let pallet_collective_drop_gov_v1_collectives = + // PalletCollectiveDropGovV1Collectives::(Default::default()); + let pallet_staking_round = PalletStakingRoundMigration::(Default::default()); let remove_pallet_democracy = RemovePalletDemocracy::(Default::default()); vec![ @@ -377,6 +258,9 @@ where // Box::new(fix_pallet_versions), // Box::new(pallet_referenda_migrate_v0_to_v1), // completed in runtime 2800 + //Box::new(pallet_collective_drop_gov_v1_collectives), + // completed in runtime 2801 + Box::new(pallet_staking_round), // Box::new(pallet_collective_drop_gov_v1_collectives), // completed in runtime 2900 Box::new(remove_pallet_democracy), diff --git a/runtime/moonbase/src/lib.rs b/runtime/moonbase/src/lib.rs index 8fb0f75228..4f2d226e70 100644 --- a/runtime/moonbase/src/lib.rs +++ b/runtime/moonbase/src/lib.rs @@ -33,6 +33,7 @@ pub mod governance; pub mod timestamp; pub mod xcm_config; +mod migrations; mod precompiles; // Re-export required by get! macro. @@ -101,8 +102,8 @@ use sp_runtime::TryRuntimeError; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, traits::{ - BlakeTwo256, Block as BlockT, DispatchInfoOf, Dispatchable, Header as HeaderT, - IdentityLookup, PostDispatchInfoOf, UniqueSaturatedInto, Zero, + BlakeTwo256, Block as BlockT, DispatchInfoOf, Dispatchable, IdentityLookup, + PostDispatchInfoOf, UniqueSaturatedInto, Zero, }, transaction_validity::{ InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, @@ -826,7 +827,8 @@ impl pallet_parachain_staking::Config for Runtime { type SlotProvider = RelayChainSlotProvider; type WeightInfo = moonbeam_weights::pallet_parachain_staking::WeightInfo; type MaxCandidates = ConstU32<200>; - type SlotsPerYear = ConstU32<{ 31_557_600 / 6 }>; + type SlotDuration = ConstU64<6_000>; + type BlockTime = ConstU64<6_000>; } impl pallet_author_inherent::Config for Runtime { @@ -1102,30 +1104,13 @@ impl pallet_proxy::Config for Runtime { type AnnouncementDepositFactor = ConstU128<{ currency::deposit(0, 56) }>; } -use pallet_migrations::{GetMigrations, Migration}; -pub struct ParachainStakingRoundMigration(sp_std::marker::PhantomData); - -impl GetMigrations for ParachainStakingRoundMigration -where - Runtime: pallet_parachain_staking::Config + pallet_async_backing::Config, - u32: From<<<::Block as BlockT>::Header as HeaderT>::Number>, -{ - fn get_migrations() -> Vec> { - vec![Box::new( - moonbeam_runtime_common::migrations::UpdateFirstRoundNumberValue::( - Default::default(), - ), - )] - } -} - impl pallet_migrations::Config for Runtime { type RuntimeEvent = RuntimeEvent; // TODO wire up our correct list of migrations here. Maybe this shouldn't be in // `moonbeam_runtime_common`. type MigrationsList = ( moonbeam_runtime_common::migrations::CommonMigrations, - ParachainStakingRoundMigration, + migrations::MoonbaseMigrations, ); type XcmExecutionManager = XcmExecutionManager; } diff --git a/runtime/moonbase/src/migrations.rs b/runtime/moonbase/src/migrations.rs new file mode 100644 index 0000000000..693f813e3c --- /dev/null +++ b/runtime/moonbase/src/migrations.rs @@ -0,0 +1,43 @@ +// Copyright 2024 Moonbeam Foundation 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 . + +//! # Moonbase specific Migrations + +use crate::Runtime; +use frame_support::{traits::OnRuntimeUpgrade, weights::Weight}; +use pallet_migrations::{GetMigrations, Migration}; +use pallet_parachain_staking::migrations::MultiplyRoundLenBy2; +use sp_std::{prelude::*, vec}; + +pub struct MoonbaseMigrations; + +impl GetMigrations for MoonbaseMigrations { + fn get_migrations() -> Vec> { + vec![Box::new(PalletStakingMultiplyRoundLenBy2)] + } +} + +// This migration should only be applied to runtimes with async backing enabled +pub struct PalletStakingMultiplyRoundLenBy2; +impl Migration for PalletStakingMultiplyRoundLenBy2 { + fn friendly_name(&self) -> &str { + "MM_MultiplyRoundLenBy2" + } + + fn migrate(&self, _available_weight: Weight) -> Weight { + MultiplyRoundLenBy2::::on_runtime_upgrade() + } +} diff --git a/runtime/moonbase/tests/common/mod.rs b/runtime/moonbase/tests/common/mod.rs index 815372b2d6..e715e7a609 100644 --- a/runtime/moonbase/tests/common/mod.rs +++ b/runtime/moonbase/tests/common/mod.rs @@ -85,7 +85,7 @@ pub fn run_to_block(n: u32, author: Option) { } } - increase_last_relay_slot_number(2); + increase_last_relay_slot_number(1); // Initialize the new block AuthorInherent::on_initialize(System::block_number()); diff --git a/runtime/moonbase/tests/integration_test.rs b/runtime/moonbase/tests/integration_test.rs index d0175aa706..bd7b714a8b 100644 --- a/runtime/moonbase/tests/integration_test.rs +++ b/runtime/moonbase/tests/integration_test.rs @@ -606,17 +606,16 @@ fn reward_block_authors() { )]) .build() .execute_with(|| { - increase_last_relay_slot_number(2); - for x in 2..1199 { - run_to_block(x, Some(NimbusId::from_slice(&ALICE_NIMBUS).unwrap())); - } + increase_last_relay_slot_number(1); + // Just before round 3 + run_to_block(2399, Some(NimbusId::from_slice(&ALICE_NIMBUS).unwrap())); // no rewards doled out yet assert_eq!( Balances::usable_balance(AccountId::from(ALICE)), 1_100 * UNIT, ); assert_eq!(Balances::usable_balance(AccountId::from(BOB)), 500 * UNIT,); - run_to_block(1201, Some(NimbusId::from_slice(&ALICE_NIMBUS).unwrap())); + run_to_block(2401, Some(NimbusId::from_slice(&ALICE_NIMBUS).unwrap())); // rewards minted and distributed assert_eq!( Balances::usable_balance(AccountId::from(ALICE)), @@ -650,15 +649,14 @@ fn reward_block_authors_with_parachain_bond_reserved() { )]) .build() .execute_with(|| { - increase_last_relay_slot_number(2); + increase_last_relay_slot_number(1); assert_ok!(ParachainStaking::set_parachain_bond_account( root_origin(), AccountId::from(CHARLIE), ),); - for x in 2..1199 { - run_to_block(x, Some(NimbusId::from_slice(&ALICE_NIMBUS).unwrap())); - } + // Stop just before round 2 + run_to_block(1199, Some(NimbusId::from_slice(&ALICE_NIMBUS).unwrap())); // no rewards doled out yet assert_eq!( @@ -667,7 +665,18 @@ fn reward_block_authors_with_parachain_bond_reserved() { ); assert_eq!(Balances::usable_balance(AccountId::from(BOB)), 500 * UNIT,); assert_eq!(Balances::usable_balance(AccountId::from(CHARLIE)), UNIT,); + + // Go to round 2 run_to_block(1201, Some(NimbusId::from_slice(&ALICE_NIMBUS).unwrap())); + + // 30% reserved for parachain bond + assert_eq!( + Balances::usable_balance(AccountId::from(CHARLIE)), + 47515000000000000000, + ); + + // Go to round 3 + run_to_block(2401, Some(NimbusId::from_slice(&ALICE_NIMBUS).unwrap())); // rewards minted and distributed assert_eq!( Balances::usable_balance(AccountId::from(ALICE)), @@ -677,10 +686,10 @@ fn reward_block_authors_with_parachain_bond_reserved() { Balances::usable_balance(AccountId::from(BOB)), 525841666640825000000, ); - // 30% reserved for parachain bond + // 30% again reserved for parachain bond assert_eq!( Balances::usable_balance(AccountId::from(CHARLIE)), - 47515000000000000000, + 94727725000000000000, ); }); } diff --git a/runtime/moonbeam/src/lib.rs b/runtime/moonbeam/src/lib.rs index a76faa9db2..c0f823db79 100644 --- a/runtime/moonbeam/src/lib.rs +++ b/runtime/moonbeam/src/lib.rs @@ -88,8 +88,8 @@ use sp_runtime::TryRuntimeError; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, traits::{ - BlakeTwo256, Block as BlockT, DispatchInfoOf, Dispatchable, Header as HeaderT, - IdentityLookup, PostDispatchInfoOf, UniqueSaturatedInto, Zero, + BlakeTwo256, Block as BlockT, DispatchInfoOf, Dispatchable, IdentityLookup, + PostDispatchInfoOf, UniqueSaturatedInto, Zero, }, transaction_validity::{ InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, @@ -801,7 +801,8 @@ impl pallet_parachain_staking::Config for Runtime { type SlotProvider = StakingRoundSlotProvider; type WeightInfo = moonbeam_weights::pallet_parachain_staking::WeightInfo; type MaxCandidates = ConstU32<200>; - type SlotsPerYear = ConstU32<{ 31_557_600 / 12 }>; + type SlotDuration = ConstU64<12_000>; + type BlockTime = ConstU64<12_000>; } impl pallet_author_inherent::Config for Runtime { @@ -1083,29 +1084,9 @@ impl pallet_proxy::Config for Runtime { type AnnouncementDepositFactor = ConstU128<{ currency::deposit(0, 56) }>; } -use pallet_migrations::{GetMigrations, Migration}; -pub struct ParachainStakingRoundMigration(sp_std::marker::PhantomData); - -impl GetMigrations for ParachainStakingRoundMigration -where - Runtime: pallet_parachain_staking::Config, - u64: From<<<::Block as BlockT>::Header as HeaderT>::Number>, -{ - fn get_migrations() -> Vec> { - vec![Box::new( - moonbeam_runtime_common::migrations::UpdateFirstRoundNumberType::( - Default::default(), - ), - )] - } -} - impl pallet_migrations::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type MigrationsList = ( - moonbeam_runtime_common::migrations::CommonMigrations, - ParachainStakingRoundMigration, - ); + type MigrationsList = (moonbeam_runtime_common::migrations::CommonMigrations,); type XcmExecutionManager = XcmExecutionManager; } diff --git a/runtime/moonbeam/tests/integration_test.rs b/runtime/moonbeam/tests/integration_test.rs index 3f07173762..3ec1073c92 100644 --- a/runtime/moonbeam/tests/integration_test.rs +++ b/runtime/moonbeam/tests/integration_test.rs @@ -657,10 +657,10 @@ fn reward_block_authors_with_parachain_bond_reserved() { root_origin(), AccountId::from(CHARLIE), ),); - for x in 2..3599 { - run_to_block(x, Some(NimbusId::from_slice(&ALICE_NIMBUS).unwrap())); - } - // no rewards doled out yet + + // Stop just before round 3 + run_to_block(3599, Some(NimbusId::from_slice(&ALICE_NIMBUS).unwrap())); + // no collators rewards doled out yet assert_eq!( Balances::usable_balance(AccountId::from(ALICE)), 8_010_000 * GLMR, @@ -669,12 +669,16 @@ fn reward_block_authors_with_parachain_bond_reserved() { Balances::usable_balance(AccountId::from(BOB)), 9_950_000 * GLMR, ); + // 30% reserved for parachain bond assert_eq!( Balances::usable_balance(AccountId::from(CHARLIE)), - 10_000 * GLMR, + 310300000000000000000000, ); + + // Go to round 3 run_to_block(3601, Some(NimbusId::from_slice(&ALICE_NIMBUS).unwrap())); - // rewards minted and distributed + + // collators rewards minted and distributed assert_eq!( Balances::usable_balance(AccountId::from(ALICE)), 8698492682878000000000000, @@ -683,10 +687,10 @@ fn reward_block_authors_with_parachain_bond_reserved() { Balances::usable_balance(AccountId::from(BOB)), 9962207316621500000000000, ); - // 30% reserved for parachain bond + // 30% reserved for parachain bond again assert_eq!( Balances::usable_balance(AccountId::from(CHARLIE)), - 310300000000000000000000, + 615104500000000000000000, ); }); } diff --git a/runtime/moonriver/src/lib.rs b/runtime/moonriver/src/lib.rs index 0faec14a56..5337023063 100644 --- a/runtime/moonriver/src/lib.rs +++ b/runtime/moonriver/src/lib.rs @@ -86,8 +86,8 @@ use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, serde::{Deserialize, Serialize}, traits::{ - BlakeTwo256, Block as BlockT, DispatchInfoOf, Dispatchable, Header as HeaderT, - IdentityLookup, PostDispatchInfoOf, UniqueSaturatedInto, Zero, + BlakeTwo256, Block as BlockT, DispatchInfoOf, Dispatchable, IdentityLookup, + PostDispatchInfoOf, UniqueSaturatedInto, Zero, }, transaction_validity::{ InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, @@ -802,7 +802,8 @@ impl pallet_parachain_staking::Config for Runtime { type SlotProvider = StakingRoundSlotProvider; type WeightInfo = moonbeam_weights::pallet_parachain_staking::WeightInfo; type MaxCandidates = ConstU32<200>; - type SlotsPerYear = ConstU32<{ 31_557_600 / 12 }>; + type SlotDuration = ConstU64<12_000>; + type BlockTime = ConstU64<12_000>; } impl pallet_author_inherent::Config for Runtime { @@ -1085,29 +1086,9 @@ impl pallet_proxy::Config for Runtime { type AnnouncementDepositFactor = ConstU128<{ currency::deposit(0, 56) }>; } -use pallet_migrations::{GetMigrations, Migration}; -pub struct ParachainStakingRoundMigration(sp_std::marker::PhantomData); - -impl GetMigrations for ParachainStakingRoundMigration -where - Runtime: pallet_parachain_staking::Config, - u64: From<<<::Block as BlockT>::Header as HeaderT>::Number>, -{ - fn get_migrations() -> Vec> { - vec![Box::new( - moonbeam_runtime_common::migrations::UpdateFirstRoundNumberType::( - Default::default(), - ), - )] - } -} - impl pallet_migrations::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type MigrationsList = ( - moonbeam_runtime_common::migrations::CommonMigrations, - ParachainStakingRoundMigration, - ); + type MigrationsList = (moonbeam_runtime_common::migrations::CommonMigrations,); type XcmExecutionManager = XcmExecutionManager; } diff --git a/runtime/moonriver/tests/integration_test.rs b/runtime/moonriver/tests/integration_test.rs index acac9c321c..641153c72f 100644 --- a/runtime/moonriver/tests/integration_test.rs +++ b/runtime/moonriver/tests/integration_test.rs @@ -645,16 +645,22 @@ fn reward_block_authors_with_parachain_bond_reserved() { root_origin(), AccountId::from(CHARLIE), ),); - for x in 2..1199 { - run_to_block(x, Some(NimbusId::from_slice(&ALICE_NIMBUS).unwrap())); - } - // no rewards doled out yet + + run_to_block(1199, Some(NimbusId::from_slice(&ALICE_NIMBUS).unwrap())); + + // no collator rewards doled out yet assert_eq!( Balances::usable_balance(AccountId::from(ALICE)), 10_100 * MOVR, ); assert_eq!(Balances::usable_balance(AccountId::from(BOB)), 9500 * MOVR,); - assert_eq!(Balances::usable_balance(AccountId::from(CHARLIE)), MOVR,); + + // 30% reserved for parachain bond + assert_eq!( + Balances::usable_balance(AccountId::from(CHARLIE)), + 452515000000000000000, + ); + run_to_block(1201, Some(NimbusId::from_slice(&ALICE_NIMBUS).unwrap())); // rewards minted and distributed assert_eq!( @@ -665,10 +671,10 @@ fn reward_block_authors_with_parachain_bond_reserved() { Balances::usable_balance(AccountId::from(BOB)), 9535834523343675000000, ); - // 30% reserved for parachain bond + // 30% reserved for parachain bond again assert_eq!( Balances::usable_balance(AccountId::from(CHARLIE)), - 452515000000000000000, + 910802725000000000000, ); }); } diff --git a/test/suites/dev/moonbase/test-staking/test-rewards-auto-compound-pov.ts b/test/suites/dev/moonbase/test-staking/test-rewards-auto-compound-pov.ts index 24481bfcfd..ed641c8a0e 100644 --- a/test/suites/dev/moonbase/test-staking/test-rewards-auto-compound-pov.ts +++ b/test/suites/dev/moonbase/test-staking/test-rewards-auto-compound-pov.ts @@ -84,7 +84,7 @@ describeSuite({ title: "should be under the limit of 3_500_000", test: async () => { // Moves to the next payout block - await jumpRounds(context, 1); + await jumpRounds(context, 2); const { block } = await context.createBlock(); const weights = await context.pjsApi.query.system.blockWeight();