From 48443c5694472b3796f14daa5f5c6b0dc754bc5f Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Thu, 17 Oct 2024 16:57:16 +0800 Subject: [PATCH] Part.2 --- Cargo.lock | 1 - pallet/staking/Cargo.toml | 8 - pallet/staking/src/benchmarking.rs | 99 +---- pallet/staking/src/lib.rs | 381 ++++++----------- pallet/staking/src/mock.rs | 180 +++----- pallet/staking/src/tests.rs | 631 ++++++----------------------- pallet/staking/src/weights.rs | 12 +- 7 files changed, 311 insertions(+), 1001 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd69a3d60..6ab30f853 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3008,7 +3008,6 @@ dependencies = [ name = "darwinia-staking" version = "6.7.1" dependencies = [ - "darwinia-deposit", "darwinia-ethtx-forwarder", "dc-inflation", "dc-types", diff --git a/pallet/staking/Cargo.toml b/pallet/staking/Cargo.toml index 1a96de304..9c20d6d35 100644 --- a/pallet/staking/Cargo.toml +++ b/pallet/staking/Cargo.toml @@ -17,8 +17,6 @@ scale-info = { workspace = true } darwinia-ethtx-forwarder = { workspace = true } dc-inflation = { workspace = true } dc-types = { workspace = true } -# darwinia optional -darwinia-deposit = { workspace = true, optional = true } # frontier fp-evm = { workspace = true } @@ -39,9 +37,6 @@ frame-benchmarking = { workspace = true, optional = true } pretty_env_logger = { version = "0.5" } serde = { version = "1.0", features = ["derive"] } -# darwinia -darwinia-deposit = { workspace = true, features = ["std"] } - # polkadot-sdk pallet-balances = { workspace = true, features = ["std"] } pallet-session = { workspace = true, features = ["std"] } @@ -61,8 +56,6 @@ std = [ # darwinia "darwinia-ethtx-forwarder/std", "dc-inflation/std", - # darwinia optional - "darwinia-deposit?/std", # frontier "fp-evm/std", @@ -82,7 +75,6 @@ std = [ runtime-benchmarks = [ # darwinia - "darwinia-deposit", "darwinia-ethtx-forwarder/runtime-benchmarks", # polkadot-sdk diff --git a/pallet/staking/src/benchmarking.rs b/pallet/staking/src/benchmarking.rs index 9d72a1b35..404fb2486 100644 --- a/pallet/staking/src/benchmarking.rs +++ b/pallet/staking/src/benchmarking.rs @@ -18,7 +18,6 @@ // darwinia use crate::*; -use darwinia_deposit::SimpleAsset; use dc_types::UNIT; // polkadot-sdk use frame_benchmarking::v2; @@ -32,108 +31,20 @@ mod benchmarks { // polkadot-sdk use frame_support::traits::Currency; - fn deposit_for(who: &T::AccountId, count: u32) -> Vec> - where - T: Config + darwinia_deposit::Config, - { - (0..count.min(<::MaxDeposits>::get()) as u16) - .map(|x| { - >::lock( - RawOrigin::Signed(who.to_owned()).into(), - UNIT, - 1, - ) - .unwrap(); - - x.into() - }) - .collect() - } - #[benchmark] - fn stake(x: Linear<0, 1_023>) { + fn unstake_all_for() { let a = frame_benchmarking::whitelisted_caller(); - - // Remove `+ 1` after https://github.com/paritytech/substrate/pull/13655. - ::Ring::make_free_balance_be(&a, 1_024 * UNIT + 1); - ::Kton::mint(&a, UNIT).unwrap(); - - let deposits = deposit_for::(&a, x); - - // Worst-case scenario: - // - // The total number of deposit items has reached `darwinia_deposits::Config::MaxDeposits`. - #[extrinsic_call] - _(RawOrigin::Signed(a), UNIT, deposits); - } - - #[benchmark] - fn unstake(x: Linear<0, 1_023>) { - let a = frame_benchmarking::whitelisted_caller(); - - // Remove `+ 1` after https://github.com/paritytech/substrate/pull/13655. - ::Ring::make_free_balance_be(&a, 1_024 * UNIT + 1); - ::Kton::mint(&a, UNIT).unwrap(); - - let deposits = deposit_for::(&a, x); - - >::stake(RawOrigin::Signed(a.clone()).into(), UNIT, deposits.clone()).unwrap(); - - // Worst-case scenario: - // - // The total number of deposit items has reached `darwinia_deposits::Config::MaxDeposits`. - #[extrinsic_call] - _(RawOrigin::Signed(a), UNIT, deposits); - } - - #[benchmark] - fn collect() { - let a = frame_benchmarking::whitelisted_caller(); - - // Worst-case scenario: - // - // None. - #[extrinsic_call] - _(RawOrigin::Signed(a), Default::default()); - } - - #[benchmark] - fn nominate() { - let a = frame_benchmarking::whitelisted_caller::(); let a_cloned = a.clone(); - // Remove `+ 1` after https://github.com/paritytech/substrate/pull/13655. - ::Ring::make_free_balance_be(&a, UNIT + 1); - - >::stake(RawOrigin::Signed(a.clone()).into(), UNIT, Default::default()).unwrap(); - >::collect(RawOrigin::Signed(a.clone()).into(), Default::default()).unwrap(); - // Worst-case scenario: // - // Nominate the target collator successfully. + // The total number of deposit items has reached `darwinia_deposits::Config::MaxDeposits`. #[extrinsic_call] _(RawOrigin::Signed(a), a_cloned); } #[benchmark] - fn chill() { - let a = frame_benchmarking::whitelisted_caller::(); - - ::Ring::make_free_balance_be(&a, UNIT); - - >::stake(RawOrigin::Signed(a.clone()).into(), UNIT, Default::default()).unwrap(); - >::collect(RawOrigin::Signed(a.clone()).into(), Default::default()).unwrap(); - >::nominate(RawOrigin::Signed(a.clone()).into(), a.clone()).unwrap(); - - // Worst-case scenario: - // - // Collect and nominate at the same time. - #[extrinsic_call] - _(RawOrigin::Signed(a)); - } - - #[benchmark] - fn payout() { + fn allocate_ring_staking_reward_of() { let a = frame_benchmarking::whitelisted_caller::(); let a_cloned = a.clone(); @@ -142,12 +53,12 @@ mod benchmarks { } #[benchmark] - fn set_rate_limit() { + fn set_ring_staking_contract() { // Worst-case scenario: // // Set successfully. #[extrinsic_call] - _(RawOrigin::Root, 1); + _(RawOrigin::Root, frame_benchmarking::whitelisted_caller::()); } #[benchmark] diff --git a/pallet/staking/src/lib.rs b/pallet/staking/src/lib.rs index a438ef8f4..845076eff 100644 --- a/pallet/staking/src/lib.rs +++ b/pallet/staking/src/lib.rs @@ -16,11 +16,11 @@ // You should have received a copy of the GNU General Public License // along with Darwinia. If not, see . -//! # Darwinia parachain staking pallet +//! # Darwinia Staking Pallet //! //! ## Overview //! -//! This is a completely specialized stake pallet designed only for Darwinia parachain. +//! This is a completely specialized stake pallet designed only for Darwinia. //! So, this pallet will eliminate the generic parameters as much as possible. #![cfg_attr(not(feature = "std"), no_std)] @@ -50,36 +50,13 @@ use frame_support::{ DefaultNoBound, PalletId, }; use frame_system::pallet_prelude::*; -use pallet_session::ShouldEndSession as _; use sp_core::H160; use sp_runtime::{ - traits::{AccountIdConversion, Convert, One, Zero}, + traits::{AccountIdConversion, Convert, One}, Perbill, }; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; -/// Make it easier to call a function on a specific collators storage. -#[macro_export] -macro_rules! call_on_cache { - ($s_e:expr, <$s:ident<$t:ident>>$($f:tt)*) => {{ - match $s_e { - ($crate::CacheState::$s, _, _) => Ok(<$crate::CollatorsCache0<$t>>$($f)*), - (_, $crate::CacheState::$s, _) => Ok(<$crate::CollatorsCache1<$t>>$($f)*), - (_, _, $crate::CacheState::$s) => Ok(<$crate::CollatorsCache2<$t>>$($f)*), - _ => { - log::error!("collators cache states must be correct; qed"); - - Err("[pallet::staking] collators cache states must be correct; qed") - }, - } - }}; - (<$s:ident<$t:ident>>$($f:tt)*) => {{ - let s = <$crate::CacheStates<$t>>::get(); - - $crate::call_on_cache!(s, <$s<$t>>$($f)*) - }}; -} - const PAYOUT_FRAC: Perbill = Perbill::from_percent(40); #[frame_support::pallet] @@ -98,9 +75,6 @@ pub mod pallet { /// Unix time interface. type UnixTime: UnixTime; - /// Pass [`pallet_session::Config::ShouldEndSession`]'s result to here. - type ShouldEndSession: Get; - /// Currency interface to pay the reward. type Currency: Currency; @@ -125,6 +99,8 @@ pub mod pallet { Payout { who: T::AccountId, amount: Balance }, /// Unable to pay the staker's reward. Unpaid { who: T::AccountId, amount: Balance }, + /// Unstake all stakes for the account. + UnstakeAllFor { who: T::AccountId }, } #[pallet::error] @@ -142,38 +118,6 @@ pub mod pallet { #[pallet::getter(fn ledger_of)] pub type Ledgers = StorageMap<_, Blake2_128Concat, T::AccountId, Ledger>; - /// Cache states. - /// - /// To avoid extra DB RWs during new session, such as: - /// ```nocompile - /// previous = current; - /// current = next; - /// next = elect(); - /// ``` - /// - /// Now, with data: - /// ```nocompile - /// cache1 == previous; - /// cache2 == current; - /// cache3 == next; - /// ``` - /// Just need to shift the marker and write the storage map once: - /// ```nocompile - /// mark(cache3, current); - /// mark(cache2, previous); - /// mark(cache1, next); - /// cache1 = elect(); - /// ``` - #[pallet::storage] - #[pallet::getter(fn exposure_cache_states)] - pub type CacheStates = - StorageValue<_, (CacheState, CacheState, CacheState), ValueQuery, CacheStatesDefault>; - /// Default value for [`CacheStates`]. - #[pallet::type_value] - pub fn CacheStatesDefault() -> (CacheState, CacheState, CacheState) { - (CacheState::Previous, CacheState::Current, CacheState::Next) - } - /// The ideal number of active collators. #[pallet::storage] #[pallet::getter(fn collator_count)] @@ -183,7 +127,7 @@ pub mod pallet { #[pallet::storage] #[pallet::unbounded] #[pallet::getter(fn authored_block_count)] - pub type AuthoredBlocksCount = + pub type AuthoredBlockCount = StorageValue<_, (BlockNumberFor, BTreeMap>), ValueQuery>; /// All outstanding rewards since the last payment. @@ -211,25 +155,6 @@ pub mod pallet { #[pallet::getter(fn kton_staking_contract)] pub type KtonStakingContract = StorageValue<_, T::AccountId>; - // TODO: use `BoundedVec`. - /// Collators cache 0. - #[pallet::storage] - #[pallet::unbounded] - #[pallet::getter(fn collators_cache_0)] - pub type CollatorsCache0 = StorageValue<_, Vec, ValueQuery>; - - /// Collators cache 1. - #[pallet::storage] - #[pallet::unbounded] - #[pallet::getter(fn collators_cache_1)] - pub type CollatorsCache1 = StorageValue<_, Vec, ValueQuery>; - - /// Collators cache 2. - #[pallet::storage] - #[pallet::unbounded] - #[pallet::getter(fn collators_cache_2)] - pub type CollatorsCache2 = StorageValue<_, Vec, ValueQuery>; - #[derive(DefaultNoBound)] #[pallet::genesis_config] pub struct GenesisConfig { @@ -239,7 +164,8 @@ pub mod pallet { pub elapsed_time: Moment, /// Genesis collator count. pub collator_count: u32, - _marker: PhantomData, + #[allow(missing_docs)] + pub _marker: PhantomData, } #[pallet::genesis_build] @@ -259,66 +185,17 @@ pub mod pallet { pub struct Pallet(_); #[pallet::hooks] impl Hooks> for Pallet { - fn on_initialize(_: BlockNumberFor) -> Weight { - // There are already plenty of tasks to handle during the new session, - // so refrain from assigning any additional ones here. - if !T::ShouldEndSession::get() { - call_on_cache!(>::get()) - .map(|cs| { - let mut cs = cs.into_iter(); - let w = cs - .by_ref() - // ? make this value adjustable. - .take(1) - .fold(Weight::zero(), |acc, c| { - acc + Self::payout_for_inner(c).unwrap_or(Zero::zero()) - }); - let _ = call_on_cache!(>::put(cs.collect::>())); - - w - }) - .unwrap_or_default() - } else { - Zero::zero() - } - } - fn on_idle(_: BlockNumberFor, mut remaining_weight: Weight) -> Weight { - // At least 1 read weight is required. - if let Some(rw) = remaining_weight.checked_sub(&T::DbWeight::get().reads(1)) { - remaining_weight = rw; - } else { - return remaining_weight; - } - - #[cfg(feature = "test")] - let wt = Weight::zero().add_ref_time(1); - #[cfg(not(feature = "test"))] - let wt = T::WeightInfo::unstake_all(); - let mut ledger_to_migrate = Vec::new(); - - for (w, l) in >::iter() { - if let Some(rw) = remaining_weight.checked_sub(&wt) { - remaining_weight = rw; - - ledger_to_migrate.push((w, l)); - } else { - break; - } - } - - for (w, l) in ledger_to_migrate { - let _ = Self::unstake_all_for_inner(w, l); - } + Self::idle_unstake(&mut remaining_weight); remaining_weight } } #[pallet::call] impl Pallet { - /// Withdraw all stakes from the staking pool. - #[pallet::call_index(1)] - #[pallet::weight(::WeightInfo::unstake_all())] + /// Withdraw all stakes. + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::unstake_all_for())] pub fn unstake_all_for(origin: OriginFor, who: T::AccountId) -> DispatchResult { ensure_signed(origin)?; @@ -329,13 +206,17 @@ pub mod pallet { Ok(()) } - /// Making the payout for the specified. - #[pallet::call_index(8)] - #[pallet::weight(::WeightInfo::payout_for())] - pub fn payout_for(origin: OriginFor, who: T::AccountId) -> DispatchResult { + /// Allocate the RING staking rewards to the designated RING staking contract of a + /// particular collator. + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::allocate_ring_staking_reward_of())] + pub fn allocate_ring_staking_reward_of( + origin: OriginFor, + who: T::AccountId, + ) -> DispatchResult { ensure_signed(origin)?; - Self::payout_for_inner(who)?; + Self::allocate_ring_staking_reward_of_inner(who)?; Ok(()) } @@ -345,7 +226,7 @@ pub mod pallet { /// This will apply to the incoming session. /// /// Require root origin. - #[pallet::call_index(7)] + #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::set_collator_count())] pub fn set_collator_count(origin: OriginFor, count: u32) -> DispatchResult { ensure_root(origin)?; @@ -360,7 +241,9 @@ pub mod pallet { } /// Set the RING reward distribution contract address. - #[pallet::call_index(11)] + /// + /// Require root origin. + #[pallet::call_index(3)] #[pallet::weight(::WeightInfo::set_ring_staking_contract())] pub fn set_ring_staking_contract( origin: OriginFor, @@ -374,7 +257,9 @@ pub mod pallet { } /// Set the KTON reward distribution contract address. - #[pallet::call_index(10)] + /// + /// Require root origin. + #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::set_kton_staking_contract())] pub fn set_kton_staking_contract( origin: OriginFor, @@ -391,114 +276,112 @@ pub mod pallet { where T: Config, { - /// Distribute the session reward to staking pot and update the stakers' reward record. - pub fn distribute_session_reward(amount: Balance) { - let who = ::Treasury::get(); + /// Allocate the session reward. + pub fn allocate_session_reward(amount: Balance) { + let treasury = ::Treasury::get(); - if T::IssuingManager::reward(&who, amount).is_ok() { - Self::deposit_event(Event::Payout { who, amount }); + if T::IssuingManager::reward(&treasury, amount).is_ok() { + Self::deposit_event(Event::Payout { who: treasury, amount }); } else { - Self::deposit_event(Event::Unpaid { who, amount }); + Self::deposit_event(Event::Unpaid { who: treasury, amount }); } - let reward_r = amount.saturating_div(2); - let reward_k = amount.saturating_sub(reward_r); - let (b_total, map) = >::take(); + let reward_to_ring_staking = amount.saturating_div(2); + let reward_to_kton_staking = amount.saturating_sub(reward_to_ring_staking); + let (total_block_count, author_map) = >::take(); - map.into_iter().for_each(|(c, b)| { - let r = Perbill::from_rational(b, b_total).mul_floor(reward_r); + author_map.into_iter().for_each(|(who, authored_block_count)| { + let incoming_reward = + Perbill::from_rational(authored_block_count, total_block_count) + .mul_floor(reward_to_ring_staking); - >::mutate(c, |u| *u = u.map(|u| u + r).or(Some(r))); + >::mutate(who, |maybe_pending_reward| { + *maybe_pending_reward = maybe_pending_reward + .map(|pending_reward| pending_reward + incoming_reward) + .or(Some(incoming_reward)) + }); }); - T::KtonStaking::distribute(None, reward_k); + T::KtonStaking::allocate(None, reward_to_kton_staking); } - fn unstake_all_for_inner(who: T::AccountId, ledger: Ledger) -> DispatchResult { - T::Currency::transfer( - &account_id(), - &who, - ledger.ring, - ExistenceRequirement::AllowDeath, - )?; + pub(crate) fn allocate_ring_staking_reward_of_inner(who: T::AccountId) -> DispatchResult { + let amount = >::take(&who).ok_or(>::NoReward)?; - Ok(()) - } + T::RingStaking::allocate(Some(who.clone()), amount); - /// Pay the reward to the RING staking contract. - fn payout_for_inner(collator: T::AccountId) -> Result { - if call_on_cache!(>::get()).unwrap_or_default().contains(&collator) { - T::RingStaking::distribute( - Some(collator.clone()), - >::take(&collator).ok_or(>::NoReward)?, - ); - } else { - // Impossible case. - - Err(>::NoReward)?; - } + Self::deposit_event(Event::Payout { who, amount }); - Ok(::WeightInfo::payout_for()) + Ok(()) } - /// Update the record of block production. fn note_authors(authors: &[T::AccountId]) { - >::mutate(|(total, map)| { - authors.iter().cloned().for_each(|c| { - *total += One::one(); - - map.entry(c).and_modify(|p_| *p_ += One::one()).or_insert(One::one()); + >::mutate(|(total_block_count, author_map)| { + authors.iter().cloned().for_each(|who| { + author_map + .entry(who) + .and_modify(|authored_block_count| *authored_block_count += One::one()) + .or_insert(One::one()); + + *total_block_count += One::one(); }); }); } - /// Prepare the session state. - fn prepare_new_session(index: u32) -> Option> { - >::shift_cache_states(); - - call_on_cache!(>::kill()).ok()?; - + fn prepare_new_session(i: u32) -> Option> { let bn = >::block_number(); - log::info!("assembling new collators for new session {index} at #{bn:?}",); - - let cs = Self::elect().unwrap_or_default(); + log::info!("assembling new collators for new session {i} at #{bn:?}",); - if cs.is_empty() { - // TODO: update this once the migration is completed. - // Possible case under the hybrid election mode. + let collators = T::RingStaking::elect(>::get()).unwrap_or_default(); - // Impossible case. - // - // But if there is an issue, retain the old collators; do not alter the session - // collators if any error occurs to prevent the chain from stalling. + if collators.is_empty() { None } else { - Some(cs) + Some(collators) } } - /// Shift the cache states. - /// - /// Previous Current Next - /// Next Previous Current - /// Current Next Previous - /// - /// ```nocompile - /// loop { mutate(2, 0, 1) } - /// ``` - fn shift_cache_states() { - let (s0, s1, s2) = >::get(); + fn idle_unstake(remaining_weight: &mut Weight) { + // At least 1 read weight is required. + if let Some(rw) = remaining_weight.checked_sub(&T::DbWeight::get().reads(1)) { + *remaining_weight = rw; + } else { + return; + } + + #[cfg(feature = "test")] + let weight = Weight::zero().add_ref_time(1); + #[cfg(not(feature = "test"))] + let weight = T::WeightInfo::unstake_all_for(); + let mut ledgers_to_migrate = Vec::new(); + + for (who, l) in >::iter() { + if let Some(rw) = remaining_weight.checked_sub(&weight) { + *remaining_weight = rw; + + ledgers_to_migrate.push((who, l)); + } else { + break; + } + } - >::put((s2, s0, s1)); + for (who, l) in ledgers_to_migrate { + let _ = Self::unstake_all_for_inner(who, l); + } } - fn elect() -> Option> { - let winners = T::RingStaking::elect(>::get())?; + fn unstake_all_for_inner(who: T::AccountId, ledger: Ledger) -> DispatchResult { + T::Currency::transfer( + &account_id(), + &who, + ledger.ring, + ExistenceRequirement::AllowDeath, + )?; - call_on_cache!(>::put(winners.clone())).ok()?; + Self::deposit_event(Event::UnstakeAllFor { who }); - Some(winners) + Ok(()) } } impl pallet_authorship::EventHandler> for Pallet @@ -519,11 +402,19 @@ pub mod pallet { fn start_session(_: u32) {} - fn new_session(index: u32) -> Option> { - let maybe_collators = Self::prepare_new_session(index); + // No election in genesis. + // Since RING contract isn't available at this time. + fn new_session_genesis(i: u32) -> Option> { + Self::prepare_new_session(i); + + None + } + + fn new_session(i: u32) -> Option> { + let maybe_collators = Self::prepare_new_session(i); if maybe_collators.is_none() { - log::error!("fail to elect collators for session {index}"); + log::error!("fail to elect collators for session {i}"); } maybe_collators @@ -542,7 +433,7 @@ where let inflation = Self::inflate(); let reward = Self::calculate_reward(inflation); - >::distribute_session_reward(reward); + >::allocate_session_reward(reward); } /// Inflation settings. @@ -571,45 +462,13 @@ pub trait Election { } impl Election for () {} -/// Distribute the reward to a contract. +/// Allocate the reward to a contract. pub trait Reward { - /// Distribute the reward. - fn distribute(_: Option, _: Balance) {} + /// Allocate the reward. + fn allocate(_: Option, _: Balance) {} } impl Reward for () {} -/// UnstakeAll trait that stakes must be implemented. -pub trait UnstakeAll { - /// Account type. - type AccountId; - - /// Withdraw all stakes from the staking pool. - fn unstake_all(who: &Self::AccountId) -> DispatchResult; -} - -/// Cache state. -#[allow(missing_docs)] -#[cfg_attr(any(test, feature = "runtime-benchmarks", feature = "try-runtime"), derive(PartialEq))] -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] -pub enum CacheState { - Previous, - Current, - Next, -} - -/// Session ending checker. -pub struct ShouldEndSession(PhantomData); -impl Get for ShouldEndSession -where - T: frame_system::Config + pallet_session::Config, -{ - fn get() -> bool { - ::ShouldEndSession::should_end_session( - >::block_number(), - ) - } -} - /// Issue new token from pallet-balances. pub struct BalanceIssuing(PhantomData); impl IssuingManager for BalanceIssuing @@ -691,7 +550,7 @@ where T: Config + darwinia_ethtx_forwarder::Config, T::AccountId: From + Into, { - fn elect(n: u32) -> Option> { + fn elect(x: u32) -> Option> { const ZERO: [u8; 20] = [0; 20]; let Some(rsc) = >::get() else { @@ -717,7 +576,7 @@ where state_mutability: StateMutability::View, }; let input = function - .encode_input(&[Token::Uint(n.into())]) + .encode_input(&[Token::Uint(x.into())]) .map_err(|e| log::error!("failed to encode input due to {e:?}")) .ok()?; @@ -731,7 +590,7 @@ where .map_err(|e| log::error!("failed to forward call due to {e:?}")) .ok() .and_then(|i| { - log::info!("getTopCollators({n})'s execution info {i:?}"); + log::info!("getTopCollators({x})'s execution info {i:?}"); function .decode_output(&i.value) @@ -760,7 +619,7 @@ where T: Config + darwinia_ethtx_forwarder::Config, T::AccountId: Into, { - fn distribute(who: Option, amount: Balance) { + fn allocate(who: Option, amount: Balance) { let Some(who) = who else { log::error!("who must be some; qed"); @@ -807,7 +666,7 @@ where T: Config + darwinia_ethtx_forwarder::Config, T::AccountId: Into, { - fn distribute(_: Option, amount: Balance) { + fn allocate(_: Option, amount: Balance) { let Some(ksc) = >::get() else { log::error!("KTON staking contract must be some; qed"); diff --git a/pallet/staking/src/mock.rs b/pallet/staking/src/mock.rs index d2245aa9f..802bce517 100644 --- a/pallet/staking/src/mock.rs +++ b/pallet/staking/src/mock.rs @@ -26,7 +26,10 @@ use serde::{Deserialize, Serialize}; use crate::*; use dc_types::UNIT; // polkadot-sdk -use frame_support::{assert_ok, derive_impl, traits::OnInitialize}; +use frame_support::{ + assert_ok, derive_impl, + traits::{OnFinalize, OnInitialize}, +}; use sp_core::H160; use sp_io::TestExternalities; use sp_runtime::{BuildStorage, RuntimeAppPublic}; @@ -90,28 +93,6 @@ impl pallet_balances::Config for Runtime { type ExistentialDeposit = (); } -pub enum KtonMinting {} -impl darwinia_deposit::SimpleAsset for KtonMinting { - type AccountId = AccountId; - - fn mint(_: &Self::AccountId, _: Balance) -> sp_runtime::DispatchResult { - Ok(()) - } - - fn burn(_: &Self::AccountId, _: Balance) -> sp_runtime::DispatchResult { - Ok(()) - } -} -impl darwinia_deposit::Config for Runtime { - type DepositMigrator = (); - type Kton = KtonMinting; - type MaxDeposits = frame_support::traits::ConstU32<16>; - type Ring = Balances; - type RuntimeEvent = RuntimeEvent; - type Treasury = TreasuryAcct; - type WeightInfo = (); -} - frame_support::parameter_types! { pub static SessionHandlerCollators: Vec = Vec::new(); pub static SessionChangeBlock: BlockNumber = 0; @@ -121,7 +102,7 @@ sp_runtime::impl_opaque_keys! { pub uint: SessionHandler, } } -pub type Period = frame_support::traits::ConstU64<3>; +pub type Period = frame_support::traits::ConstU64<5>; pub struct SessionHandler; impl pallet_session::SessionHandler for SessionHandler { const KEY_TYPE_IDS: &'static [sp_runtime::KeyTypeId] = @@ -209,6 +190,7 @@ impl pallet_treasury::Config for Runtime { frame_support::parameter_types! { pub static InflationType: u8 = 0; + pub static NextCollatorId: u64 = 1; } pub enum StatedOnSessionEnd {} impl crate::IssuingManager for StatedOnSessionEnd { @@ -237,46 +219,25 @@ impl crate::IssuingManager for StatedOnSessionEnd { } } pub enum RingStaking {} -impl crate::Stake for RingStaking { - type AccountId = AccountId; - type Item = Balance; +impl crate::Election for RingStaking { + fn elect(x: u32) -> Option> { + let start = NextCollatorId::get(); + let end = start + x as u64; - fn stake(who: &Self::AccountId, item: Self::Item) -> sp_runtime::DispatchResult { - >::transfer( - who, - &crate::account_id(), - item, - frame_support::traits::ExistenceRequirement::AllowDeath, - ) - } + assert!(end < 1_000); - fn unstake(who: &Self::AccountId, item: Self::Item) -> sp_runtime::DispatchResult { - >::transfer( - &crate::account_id(), - who, - item, - frame_support::traits::ExistenceRequirement::AllowDeath, - ) - } -} -impl crate::Election for RingStaking { - fn elect(n: u32) -> Option> { Some( - (100..(100 + n) as u64) + (start..end) .map(|i| { let who = AccountId(i); - if Session::set_keys( + assert_ok!(Session::set_keys( RuntimeOrigin::signed(who), SessionKeys { uint: i.into() }, Vec::new(), - ) - .is_ok() - { - who - } else { - AccountId(0) - } + )); + + who }) .collect(), ) @@ -285,8 +246,11 @@ impl crate::Election for RingStaking { impl crate::Reward for RingStaking { fn distribute(who: Option, amount: Balance) { let Some(who) = who else { return }; - let _ = - Balances::transfer_keep_alive(RuntimeOrigin::signed(TreasuryAcct::get()), who, amount); + let _ = Balances::transfer_keep_alive( + RuntimeOrigin::signed(Treasury::account_id()), + who, + amount, + ); } } pub enum KtonStaking {} @@ -301,27 +265,20 @@ impl crate::Reward for KtonStaking { } impl crate::Config for Runtime { type Currency = Balances; - type Deposit = Deposit; type IssuingManager = StatedOnSessionEnd; type KtonStaking = KtonStaking; - type MaxDeposits = ::MaxDeposits; - type Ring = RingStaking; type RingStaking = RingStaking; type RuntimeEvent = RuntimeEvent; - type ShouldEndSession = crate::ShouldEndSession; type Treasury = TreasuryAcct; type UnixTime = Timestamp; type WeightInfo = (); } -#[cfg(not(feature = "runtime-benchmarks"))] -impl crate::DepositConfig for Runtime {} frame_support::construct_runtime! { pub enum Runtime { System: frame_system, Timestamp: pallet_timestamp, Balances: pallet_balances, - Deposit: darwinia_deposit, Session: pallet_session, Treasury: pallet_treasury, Staking: crate, @@ -344,110 +301,74 @@ impl Efflux { } } -pub struct ExtBuilder { - collator_count: u32, - genesis_collator: bool, -} +pub struct ExtBuilder; impl ExtBuilder { - pub fn inflation_type(self, r#type: u8) -> Self { - INFLATION_TYPE.with(|v| *v.borrow_mut() = r#type); + // pub fn inflation_type(self, r#type: u8) -> Self { + // INFLATION_TYPE.with(|v| *v.borrow_mut() = r#type); - self - } - - pub fn collator_count(mut self, collator_count: u32) -> Self { - self.collator_count = collator_count; - - self - } - - pub fn genesis_collator(mut self) -> Self { - self.genesis_collator = true; - - self - } + // self + // } pub fn build(self) -> TestExternalities { - // let _ = pretty_env_logger::try_init(); + let _ = pretty_env_logger::try_init(); let mut storage = >::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { - balances: (1..=10) - .map(|i| (AccountId(i), 1_000 * UNIT)) - .chain([(TreasuryAcct::get(), 1_000_000 * UNIT)]) + balances: (1..=1_000) + .map(|i| (AccountId(i), 100)) + .chain([(Treasury::account_id(), 1 << 64), (account_id(), 1 << 64)]) + .collect(), + } + .assimilate_storage(&mut storage) + .unwrap(); + pallet_session::GenesisConfig:: { + keys: (1..=3) + .map(|i| (AccountId(i), AccountId(i), SessionKeys { uint: i.into() })) .collect(), } .assimilate_storage(&mut storage) .unwrap(); crate::GenesisConfig:: { - rate_limit: 100 * UNIT, - collator_count: self.collator_count, - collators: if self.genesis_collator { - (1..=self.collator_count as u64).map(|i| (AccountId(i), i as _)).collect() - } else { - Default::default() - }, + collator_count: 3, + collators: (1..=3).map(|i| AccountId(i)).collect(), ..Default::default() } .assimilate_storage(&mut storage) .unwrap(); - if self.genesis_collator { - pallet_session::GenesisConfig:: { - keys: (1..=self.collator_count as u64) - .map(|i| (AccountId(i), AccountId(i), SessionKeys { uint: i.into() })) - .collect(), - } - .assimilate_storage(&mut storage) - .unwrap(); - } let mut ext = TestExternalities::from(storage); ext.execute_with(|| { - >::put(AccountId(718)); - >::put(AccountId(719)); + >::put(AccountId(1_001)); + >::put(AccountId(1_002)); + preset_session_keys(); new_session(); }); ext } } -impl Default for ExtBuilder { - fn default() -> Self { - Self { collator_count: 1, genesis_collator: false } - } -} - -pub fn preset_collator_wait_list(n: u64) { - (10..10 + n).for_each(|i| { - let who = AccountId(i); - let _ = >::deposit_creating(&who, i as _); +pub fn preset_session_keys() { + (1..1_000).for_each(|i| { assert_ok!(Session::set_keys( - RuntimeOrigin::signed(who), + RuntimeOrigin::signed(AccountId(i)), SessionKeys { uint: i.into() }, Vec::new() )); - assert_ok!(Staking::stake(RuntimeOrigin::signed(who), i as _, Vec::new())); - assert_ok!(Staking::collect(RuntimeOrigin::signed(who), Perbill::zero())); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(who), who)); - }); - (100..100 + n).for_each(|i| { - let who = AccountId(i); - let _ = >::deposit_creating(&who, i as _); }); } pub fn initialize_block(number: BlockNumber) { System::set_block_number(number); Efflux::time(1); - >::on_initialize(number); + AllPalletsWithSystem::on_initialize(number); } pub fn finalize_block(number: BlockNumber) { - >::on_finalize(number); + AllPalletsWithSystem::on_finalize(number); } pub fn new_session() { @@ -457,9 +378,6 @@ pub fn new_session() { (now..target).for_each(|_| Efflux::block(1)); } -pub fn payout() { - crate::call_on_cache!(>::get().into_iter().for_each(|c| { - let _ = Staking::payout_inner(c); - })) - .unwrap(); +pub fn events() -> Vec> { + System::read_events_for_pallet() } diff --git a/pallet/staking/src/tests.rs b/pallet/staking/src/tests.rs index 9a661d12e..279fa7da4 100644 --- a/pallet/staking/src/tests.rs +++ b/pallet/staking/src/tests.rs @@ -16,568 +16,199 @@ // You should have received a copy of the GNU General Public License // along with Darwinia. If not, see . -// core -use core::time::Duration; // darwinia use crate::{mock::*, *}; -use darwinia_deposit::Error as DepositError; -use dc_types::UNIT; // polkadot-sdk -use frame_support::{assert_noop, assert_ok, BoundedVec}; -use sp_runtime::{assert_eq_error_rate, DispatchError, Perbill}; +use frame_support::{assert_noop, assert_ok}; +use sp_runtime::DispatchError; #[test] -fn exposure_cache_should_work() { - ExtBuilder::default().build().execute_with(|| {}); -} - -#[test] -fn stake_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_eq!(System::account(AccountId(1)).consumers, 0); - assert!(Staking::ledger_of(AccountId(1)).is_none()); - assert_eq!(Balances::free_balance(AccountId(1)), 1_000 * UNIT); - assert_eq!(Staking::rate_limit_state(), RateLimiter::Pos(0)); - - // Stake 1 RING. - assert_ok!(Staking::stake(RuntimeOrigin::signed(AccountId(1)), UNIT, Vec::new())); - assert_eq!(System::account(AccountId(1)).consumers, 1); - assert_eq!( - Staking::ledger_of(AccountId(1)).unwrap(), - Ledger { ring: UNIT, deposits: Default::default() } - ); - assert_eq!(Balances::free_balance(AccountId(1)), 999 * UNIT); - assert_eq!(Staking::rate_limit_state(), RateLimiter::Pos(UNIT)); +fn get_top_collators_should_work() { + const ZERO: [u8; 20] = [0; 20]; - // Stake invalid deposit. - assert_noop!( - Staking::stake(RuntimeOrigin::signed(AccountId(1)), 0, vec![0]), - >::DepositNotFound - ); + let data = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 148, 244, 240, 74, 89, 79, 214, 144, 224, + 254, 164, 111, 40, 130, 165, 178, 97, 83, 167, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + #[allow(deprecated)] + let function = Function { + name: "getTopCollators".to_owned(), + inputs: vec![Param { + name: "k".to_owned(), + kind: ParamType::Uint(256), + internal_type: None, + }], + outputs: vec![Param { + name: "collators".to_owned(), + kind: ParamType::Array(Box::new(ParamType::Address)), + internal_type: None, + }], + constant: None, + state_mutability: StateMutability::View, + }; + let output = function + .decode_output(&data) + .map(|o| { + let Some(Token::Array(addrs)) = o.into_iter().next() else { return Vec::new() }; - // Stake 1 deposit. - assert_eq!(System::account(AccountId(1)).consumers, 1); - assert_ok!(Deposit::lock(RuntimeOrigin::signed(AccountId(1)), UNIT, 1)); - assert_ok!(Staking::stake(RuntimeOrigin::signed(AccountId(1)), 0, vec![0])); - assert_eq!( - Staking::ledger_of(AccountId(1)).unwrap(), - Ledger { ring: UNIT, deposits: BoundedVec::truncate_from(vec![0]) } - ); - assert_eq!(Staking::rate_limit_state(), RateLimiter::Pos(2 * UNIT)); + addrs + .into_iter() + .filter_map(|addr| match addr { + Token::Address(addr) if addr.0 != ZERO => Some(addr.0), + _ => None, + }) + .collect() + }) + .unwrap(); - // Stake 2 RING and 2 deposits. - assert_eq!(System::account(AccountId(1)).consumers, 2); - assert_ok!(Deposit::lock(RuntimeOrigin::signed(AccountId(1)), UNIT, 1)); - assert_ok!(Deposit::lock(RuntimeOrigin::signed(AccountId(1)), UNIT, 1)); - assert_ok!(Staking::stake(RuntimeOrigin::signed(AccountId(1)), 2 * UNIT, vec![1, 2])); - assert_eq!(Balances::free_balance(AccountId(1)), 994 * UNIT); - assert_eq!( - Staking::ledger_of(AccountId(1)).unwrap(), - Ledger { ring: 3 * UNIT, deposits: BoundedVec::truncate_from(vec![0, 1, 2]) } - ); - assert_eq!(Staking::rate_limit_state(), RateLimiter::Pos(6 * UNIT)); - }); + assert_eq!( + output, + [ + // 0x94f4f04a594fd690e0fea46f2882a5b26153a72f. + [ + 148, 244, 240, 74, 89, 79, 214, 144, 224, 254, 164, 111, 40, 130, 165, 178, 97, 83, + 167, 47 + ] + ] + ); } #[test] -fn unstake_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Deposit::lock(RuntimeOrigin::signed(AccountId(1)), UNIT, 1)); - assert_ok!(Deposit::lock(RuntimeOrigin::signed(AccountId(1)), UNIT, 1)); - assert_ok!(Deposit::lock(RuntimeOrigin::signed(AccountId(1)), UNIT, 1)); - assert_ok!(Staking::stake(RuntimeOrigin::signed(AccountId(1)), 3 * UNIT, vec![0, 1, 2])); - assert_eq!(Balances::free_balance(AccountId(1)), 994 * UNIT); - assert_eq!(Staking::rate_limit_state(), RateLimiter::Pos(6 * UNIT)); +fn collator_caches_should_work() { + ExtBuilder.build().execute_with(|| { + assert!(call_on_cache!(>::get()).unwrap().is_empty()); + assert!(call_on_cache!(>::get()).unwrap().is_empty()); assert_eq!( - Staking::ledger_of(AccountId(1)).unwrap(), - Ledger { ring: 3 * UNIT, deposits: BoundedVec::truncate_from(vec![0, 1, 2]) } + call_on_cache!(>::get()).unwrap(), + vec![AccountId(1), AccountId(2), AccountId(3)] ); assert_eq!( - Deposit::deposit_of(AccountId(1)) - .unwrap() - .into_iter() - .map(|d| d.in_use) - .collect::>(), - [true, true, true] + >::get(), + (CacheState::Previous, CacheState::Current, CacheState::Next) ); - // Unstake 1 RING. - assert_ok!(Staking::unstake(RuntimeOrigin::signed(AccountId(1)), UNIT, Vec::new())); - assert_eq!(Staking::rate_limit_state(), RateLimiter::Pos(5 * UNIT)); - assert_eq!(Balances::free_balance(AccountId(1)), 995 * UNIT); - assert_eq!( - Staking::ledger_of(AccountId(1)).unwrap(), - Ledger { ring: 2 * UNIT, deposits: BoundedVec::truncate_from(vec![0, 1, 2]) } - ); + Staking::shift_cache_states(); - // Unstake invalid deposit. - assert_noop!( - Staking::unstake(RuntimeOrigin::signed(AccountId(1)), 0, vec![3]), - >::DepositNotFound - ); - - // Unstake 1 deposit. - Efflux::block(1); - assert_ok!(Staking::unstake(RuntimeOrigin::signed(AccountId(1)), 0, vec![1])); - assert_eq!(Staking::rate_limit_state(), RateLimiter::Pos(4 * UNIT)); + assert!(call_on_cache!(>::get()).unwrap().is_empty()); assert_eq!( - Staking::ledger_of(AccountId(1)).unwrap(), - Ledger { ring: 2 * UNIT, deposits: BoundedVec::truncate_from(vec![0, 2]) } + call_on_cache!(>::get()).unwrap(), + vec![AccountId(1), AccountId(2), AccountId(3)] ); + assert!(call_on_cache!(>::get()).unwrap().is_empty()); assert_eq!( - Deposit::deposit_of(AccountId(1)) - .unwrap() - .into_iter() - .map(|d| d.in_use) - .collect::>(), - [true, false, true] + >::get(), + (CacheState::Next, CacheState::Previous, CacheState::Current) ); - // Unstake 2 RING and 2 deposits. - Efflux::block(1); - assert_ok!(Staking::unstake(RuntimeOrigin::signed(AccountId(1)), 2 * UNIT, vec![0, 2])); - assert_eq!(Staking::rate_limit_state(), RateLimiter::Pos(0)); - assert!(Staking::ledger_of(AccountId(1)).is_none()); - assert_eq!( - Deposit::deposit_of(AccountId(1)) - .unwrap() - .into_iter() - .map(|d| d.in_use) - .collect::>(), - [false, false, false] - ); - - // Prepare rate limit test data. - assert_ok!(Deposit::lock(RuntimeOrigin::signed(AccountId(1)), 100 * UNIT + 1, 1)); - >::put(200 * UNIT + 2); - assert_ok!(Staking::stake(RuntimeOrigin::signed(AccountId(1)), 100 * UNIT + 1, vec![3])); - >::put(100 * UNIT); - >::kill(); + Staking::shift_cache_states(); - // Unstake 100 UNIT + 1. - assert_noop!( - Staking::unstake(RuntimeOrigin::signed(AccountId(1)), 100 * UNIT + 1, Vec::new()), - >::ExceedRateLimit - ); - - // Unstake 100 UNIT + 1. - assert_noop!( - Staking::unstake(RuntimeOrigin::signed(AccountId(1)), 0, vec![3]), - >::ExceedRateLimit + assert_eq!( + call_on_cache!(>::get()).unwrap(), + vec![AccountId(1), AccountId(2), AccountId(3)] ); - - // Unstake RING(100 UNIT + 1) and deposit(100 UNIT + 1). - assert_noop!( - Staking::unstake(RuntimeOrigin::signed(AccountId(1)), 100 * UNIT + 1, vec![3]), - >::ExceedRateLimit + assert!(call_on_cache!(>::get()).unwrap().is_empty()); + assert!(call_on_cache!(>::get()).unwrap().is_empty()); + assert_eq!( + >::get(), + (CacheState::Current, CacheState::Next, CacheState::Previous) ); - }); -} - -#[test] -fn collect_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert!(Staking::collator_of(AccountId(1)).is_none()); - assert_ok!(Staking::stake(RuntimeOrigin::signed(AccountId(1)), UNIT, Vec::new())); - (0..=99).for_each(|c| { - let c = Perbill::from_percent(c); + Staking::shift_cache_states(); - assert_ok!(Staking::collect(RuntimeOrigin::signed(AccountId(1)), c)); - assert_eq!(Staking::collator_of(AccountId(1)).unwrap(), c); - }); - }); -} - -#[test] -fn nominate_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Staking::stake(RuntimeOrigin::signed(AccountId(1)), UNIT, Vec::new())); - assert_ok!(Staking::collect(RuntimeOrigin::signed(AccountId(1)), Perbill::zero())); - - (2..=10).for_each(|i| { - assert!(Staking::nominator_of(AccountId(i)).is_none()); - assert_ok!(Staking::stake(RuntimeOrigin::signed(AccountId(i)), UNIT, Vec::new())); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(AccountId(i)), AccountId(1))); - assert_eq!(Staking::nominator_of(AccountId(i)).unwrap(), AccountId(1)); - }); - }); -} - -#[test] -fn chill_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(Staking::stake(RuntimeOrigin::signed(AccountId(1)), UNIT, Vec::new())); - assert_ok!(Staking::collect(RuntimeOrigin::signed(AccountId(1)), Perbill::zero())); - (2..=10).for_each(|i| { - assert_ok!(Staking::stake(RuntimeOrigin::signed(AccountId(i)), UNIT, Vec::new())); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(AccountId(i)), AccountId(1))); - }); - assert!(Staking::collator_of(AccountId(1)).is_some()); - (2..=10).for_each(|i| assert!(Staking::nominator_of(AccountId(i)).is_some())); - - (1..=10).for_each(|i| { - assert_ok!(Staking::chill(RuntimeOrigin::signed(AccountId(i)))); - }); - assert!(Staking::collator_of(AccountId(1)).is_none()); - (2..=10).for_each(|i| assert!(Staking::nominator_of(AccountId(i)).is_none())); - }); -} - -#[test] -fn set_collator_count_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - Staking::set_collator_count(RuntimeOrigin::signed(AccountId(1)), 1), - DispatchError::BadOrigin - ); - assert_noop!( - Staking::set_collator_count(RuntimeOrigin::root(), 0), - >::ZeroCollatorCount + assert!(call_on_cache!(>::get()).unwrap().is_empty()); + assert!(call_on_cache!(>::get()).unwrap().is_empty()); + assert_eq!( + call_on_cache!(>::get()).unwrap(), + vec![AccountId(1), AccountId(2), AccountId(3)] ); - assert_ok!(Staking::set_collator_count(RuntimeOrigin::root(), 1)); - }); -} - -#[test] -fn elect_should_work() { - ExtBuilder::default().collator_count(3).build().execute_with(|| { - (1..=5).for_each(|i| { - assert_ok!(Staking::stake( - RuntimeOrigin::signed(AccountId(i)), - i as Balance * UNIT, - Vec::new() - )); - assert_ok!(Staking::collect(RuntimeOrigin::signed(AccountId(i)), Perbill::zero())); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(AccountId(i)), AccountId(i))); - }); - (6..=10).for_each(|i| { - assert_ok!(Staking::stake( - RuntimeOrigin::signed(AccountId(i)), - i as Balance * UNIT, - Vec::new() - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(AccountId(i)), AccountId(i - 5))); - }); - assert_eq!( - Staking::elect(Staking::collator_count()).unwrap(), - vec![AccountId(5), AccountId(4), AccountId(3)] + >::get(), + (CacheState::Previous, CacheState::Current, CacheState::Next) ); }); } #[test] -fn payout_should_work() { - ExtBuilder::default().collator_count(5).build().execute_with(|| { - (1..=5).for_each(|i| { - assert_ok!(Staking::stake( - RuntimeOrigin::signed(AccountId(i)), - i as Balance * UNIT, - Vec::new() - )); - assert_ok!(Staking::collect( - RuntimeOrigin::signed(AccountId(i)), - Perbill::from_percent(i as u32 * 10) - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(AccountId(i)), AccountId(i))); - }); - (6..=10).for_each(|i| { - assert_ok!(Staking::stake( - RuntimeOrigin::signed(AccountId(i)), - (11 - i as Balance) * UNIT, - Vec::new() - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(AccountId(i)), AccountId(i - 5))); - }); - new_session(); - (1..=10).for_each(|i| { - assert_eq!( - Balances::free_balance(AccountId(i)), - (1_000 - if i < 6 { i } else { 11 - i }) as Balance * UNIT - ) - }); - - let session_duration = Duration::new(12 * 600, 0).as_millis(); - let kton_staking_contract_balance = - Balances::free_balance(>::get().unwrap()); - Efflux::time(session_duration - >::get() as Moment); - Staking::note_authors(&[ - AccountId(1), - AccountId(2), - AccountId(3), - AccountId(4), - AccountId(5), - ]); - new_session(); - new_session(); - payout(); - - let rewards = [ - 182149362040072859745, - 340012143096539162113, - 473588342440801457194, - 582877959635701275045, - 667880995628415300546, - 546448087213114754098, - 388585306229508196721, - 255009107468123861566, - 145719489836065573771, - 60716453916211293261, - ]; - let half_reward = PAYOUT_FRAC - * dc_inflation::issuing_in_period(session_duration, Timestamp::now()).unwrap() - / 2; - assert_eq_error_rate!(half_reward, rewards.iter().sum::(), UNIT); - assert_eq_error_rate!( - half_reward, - Balances::free_balance(>::get().unwrap()) - - kton_staking_contract_balance, - UNIT - ); - assert_eq!( - rewards.as_slice(), - (1..=10) - .map(|i| Balances::free_balance(AccountId(i)) - - (1_000 - if i < 6 { i } else { 11 - i }) as Balance * UNIT) - .collect::>() - ); - }); - - ExtBuilder::default().inflation_type(1).collator_count(5).build().execute_with(|| { - (1..=5).for_each(|i| { - assert_ok!(Staking::stake( - RuntimeOrigin::signed(AccountId(i)), - i as Balance * UNIT, - Vec::new() - )); - assert_ok!(Staking::collect( - RuntimeOrigin::signed(AccountId(i)), - Perbill::from_percent(i as u32 * 10) - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(AccountId(i)), AccountId(i))); - }); - (6..=10).for_each(|i| { - assert_ok!(Staking::stake( - RuntimeOrigin::signed(AccountId(i)), - (11 - i as Balance) * UNIT, - Vec::new() - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(AccountId(i)), AccountId(i - 5))); - }); - new_session(); - new_session(); - (1..=10).for_each(|i| { - assert_eq!( - Balances::free_balance(AccountId(i)), - (1_000 - if i < 6 { i } else { 11 - i }) as Balance * UNIT - ) - }); - - let total_issuance = Balances::total_issuance(); - let session_duration = Duration::new(12 * 600, 0).as_millis(); - Efflux::time(session_duration - >::get() as Moment); - Staking::note_authors(&[ - AccountId(1), - AccountId(2), - AccountId(3), - AccountId(4), - AccountId(5), - ]); - new_session(); - payout(); - - let rewards = [ - 499999998800000000000, - 933333332800000000000, - 1300000000000000000000, - 1599999999200000000000, - 1833333333000000000000, - 1499999999400000000000, - 1066666665600000000000, - 700000000000000000000, - 399999999600000000000, - 166666666000000000000, - ]; +fn elect_should_work() { + ExtBuilder.build().execute_with(|| { assert_eq!( - rewards.as_slice(), - (1..=10) - .map(|i| Balances::free_balance(AccountId(i)) - - (1_000 - if i < 6 { i } else { 11 - i }) as Balance * UNIT) - .collect::>() + call_on_cache!(>::get()).unwrap(), + vec![AccountId(1), AccountId(2), AccountId(3)] ); - assert_eq!(Balances::total_issuance(), total_issuance); - - assert_ok!(Balances::transfer_all( - RuntimeOrigin::signed(Treasury::account_id()), - AccountId(0), - false - )); - Staking::note_authors(&[AccountId(1)]); - System::reset_events(); + NEXT_COLLATOR_ID.with(|v| *v.borrow_mut() = 4); new_session(); - payout(); assert_eq!( - System::events() - .into_iter() - .filter_map(|e| match e.event { - RuntimeEvent::Staking(e @ Event::Unpaid { .. }) => Some(e), - _ => None, - }) - .collect::>(), - vec![ - // Pay to collator failed. - Event::Unpaid { who: AccountId(6), amount: 7499999997000000000000 }, - // Pay to nominator failed. - Event::Unpaid { who: AccountId(1), amount: 2499999994000000000000 } - ] + call_on_cache!(>::get()).unwrap(), + vec![AccountId(4), AccountId(5), AccountId(6)] ); }); } #[test] fn auto_payout_should_work() { - ExtBuilder::default().collator_count(2).build().execute_with(|| { - (1..=2).for_each(|i| { - assert_ok!(Staking::stake( - RuntimeOrigin::signed(AccountId(i)), - i as Balance * UNIT, - Vec::new() - )); - assert_ok!(Staking::collect( - RuntimeOrigin::signed(AccountId(i)), - Perbill::from_percent(i as u32 * 10) - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(AccountId(i)), AccountId(i))); - }); - (3..=4).for_each(|i| { - assert_ok!(Staking::stake( - RuntimeOrigin::signed(AccountId(i)), - (5 - i as Balance) * UNIT, - Vec::new() - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(AccountId(i)), AccountId(i - 2))); - }); - new_session(); - new_session(); + ExtBuilder.build().execute_with(|| { + Efflux::block(1); - Efflux::time(>::get() as Moment); - Staking::note_authors(&[AccountId(1), AccountId(2)]); - new_session(); - (1..=4).for_each(|i| { - assert_eq!( - Balances::free_balance(AccountId(i)), - (1_000 - if i < 3 { i } else { 5 - i }) as Balance * UNIT - ) - }); + (1..=3).for_each(|i| >::insert(AccountId(i), i as Balance)); + System::reset_events(); Efflux::block(1); - assert_eq!( - [ - Balances::free_balance(AccountId(1)), - Balances::free_balance(AccountId(2)), - Balances::free_balance(AccountId(3)), - Balances::free_balance(AccountId(4)), - ], - [ - 999000607164541135398, - 998000000000000000000, - 998000910746811475409, - 999000000000000000000 - ] - ); + dbg!(>::iter().collect::>()); + assert_eq!(events(), vec![Event::Payout { who: AccountId(2), amount: 2 }]); + System::reset_events(); Efflux::block(1); - assert_eq!( - [ - Balances::free_balance(AccountId(1)), - Balances::free_balance(AccountId(2)), - Balances::free_balance(AccountId(3)), - Balances::free_balance(AccountId(4)), - ], - [ - 999000607164541135398, - 998001113134992106860, - 998000910746811475409, - 999000404776360655738 - ] - ); + dbg!(>::iter().collect::>()); + assert_eq!(events(), vec![Event::Payout { who: AccountId(3), amount: 3 }]); + System::reset_events(); Efflux::block(1); - assert_eq!( - [ - Balances::free_balance(AccountId(1)), - Balances::free_balance(AccountId(2)), - Balances::free_balance(AccountId(3)), - Balances::free_balance(AccountId(4)), - ], - [ - 999000607164541135398, - 998001113134992106860, - 998000910746811475409, - 999000404776360655738 - ] - ); + dbg!(>::iter().collect::>()); + assert_eq!(events(), vec![Event::Payout { who: AccountId(1), amount: 1 }]); }); } #[test] fn on_new_session_should_work() { - ExtBuilder::default().collator_count(2).genesis_collator().build().execute_with(|| {}); + ExtBuilder.build().execute_with(|| {}); } #[test] -fn get_top_collators_should_work() { - const ZERO: [u8; 20] = [0; 20]; +fn unstake_all_for_should_work() { + ExtBuilder.build().execute_with(|| { + assert_noop!( + Staking::unstake_all_for(RuntimeOrigin::signed(AccountId(1)), AccountId(1)), + >::NoRecord + ); - let data = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 148, 244, 240, 74, 89, 79, 214, 144, 224, - 254, 164, 111, 40, 130, 165, 178, 97, 83, 167, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ]; - #[allow(deprecated)] - let function = Function { - name: "getTopCollators".to_owned(), - inputs: vec![Param { - name: "k".to_owned(), - kind: ParamType::Uint(256), - internal_type: None, - }], - outputs: vec![Param { - name: "collators".to_owned(), - kind: ParamType::Array(Box::new(ParamType::Address)), - internal_type: None, - }], - constant: None, - state_mutability: StateMutability::View, - }; - let output = function - .decode_output(&data) - .map(|o| { - let Some(Token::Array(addrs)) = o.into_iter().next() else { return Vec::new() }; + >::insert(AccountId(1), Ledger { ring: 1, deposits: Default::default() }); + assert_ok!(Staking::unstake_all_for(RuntimeOrigin::signed(AccountId(1)), AccountId(1))); + }); +} - addrs - .into_iter() - .filter_map(|addr| match addr { - Token::Address(addr) if addr.0 != ZERO => Some(addr.0), - _ => None, - }) - .collect() - }) - .unwrap(); +#[test] +fn payout_for_should_work() { + ExtBuilder.build().execute_with(|| {}); +} - assert_eq!( - output, - [ - // 0x94f4f04a594fd690e0fea46f2882a5b26153a72f. - [ - 148, 244, 240, 74, 89, 79, 214, 144, 224, 254, 164, 111, 40, 130, 165, 178, 97, 83, - 167, 47 - ] - ] - ); +#[test] +fn set_collator_count_should_work() { + ExtBuilder.build().execute_with(|| { + assert_noop!( + Staking::set_collator_count(RuntimeOrigin::signed(AccountId(1)), 1), + DispatchError::BadOrigin + ); + assert_noop!( + Staking::set_collator_count(RuntimeOrigin::root(), 0), + >::ZeroCollatorCount + ); + + assert_ok!(Staking::set_collator_count(RuntimeOrigin::root(), 1)); + assert_eq!(Staking::collator_count(), 1); + }); } diff --git a/pallet/staking/src/weights.rs b/pallet/staking/src/weights.rs index 0034cf691..5c619a2db 100644 --- a/pallet/staking/src/weights.rs +++ b/pallet/staking/src/weights.rs @@ -52,8 +52,8 @@ use core::marker::PhantomData; /// Weight functions needed for darwinia_staking. pub trait WeightInfo { - fn unstake_all() -> Weight; - fn payout_for() -> Weight; + fn unstake_all_for() -> Weight; + fn allocate_ring_staking_reward_of() -> Weight; fn set_ring_staking_contract() -> Weight; fn set_kton_staking_contract() -> Weight; fn set_collator_count() -> Weight; @@ -73,7 +73,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: `DarwiniaStaking::RateLimit` (r:1 w:0) /// Proof: `DarwiniaStaking::RateLimit` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// The range of component `x` is `[0, 1023]`. - fn unstake_all() -> Weight { + fn unstake_all_for() -> Weight { // Proof Size summary in bytes: // Measured: `2330` // Estimated: `83902` @@ -91,7 +91,7 @@ impl WeightInfo for SubstrateWeight { /// Proof: `DarwiniaStaking::PendingRewards` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:32 w:32) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) - fn payout_for() -> Weight { + fn allocate_ring_staking_reward_of() -> Weight { // Proof Size summary in bytes: // Measured: `2330` // Estimated: `83902` @@ -149,7 +149,7 @@ impl WeightInfo for () { /// Storage: `DarwiniaStaking::RateLimit` (r:1 w:0) /// Proof: `DarwiniaStaking::RateLimit` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// The range of component `x` is `[0, 1023]`. - fn unstake_all() -> Weight { + fn unstake_all_for() -> Weight { // Proof Size summary in bytes: // Measured: `2330` // Estimated: `83902` @@ -159,7 +159,7 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(35_u64)) .saturating_add(RocksDbWeight::get().writes(34_u64)) } - fn payout_for() -> Weight { + fn allocate_ring_staking_reward_of() -> Weight { // Proof Size summary in bytes: // Measured: `2330` // Estimated: `83902`