diff --git a/pallet/staking/src/lib.rs b/pallet/staking/src/lib.rs index 845076eff..a45589a92 100644 --- a/pallet/staking/src/lib.rs +++ b/pallet/staking/src/lib.rs @@ -186,6 +186,7 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_idle(_: BlockNumberFor, mut remaining_weight: Weight) -> Weight { + Self::idle_allocate_ring_staking_reward(&mut remaining_weight); Self::idle_unstake(&mut remaining_weight); remaining_weight @@ -199,9 +200,9 @@ pub mod pallet { pub fn unstake_all_for(origin: OriginFor, who: T::AccountId) -> DispatchResult { ensure_signed(origin)?; - let l = >::take(&who).ok_or(>::NoRecord)?; + let leger = >::take(&who).ok_or(>::NoRecord)?; - Self::unstake_all_for_inner(who, l)?; + Self::unstake_all_for_inner(who, leger)?; Ok(()) } @@ -216,7 +217,9 @@ pub mod pallet { ) -> DispatchResult { ensure_signed(origin)?; - Self::allocate_ring_staking_reward_of_inner(who)?; + let amount = >::take(&who).ok_or(>::NoReward)?; + + Self::allocate_ring_staking_reward_of_inner(who, amount)?; Ok(()) } @@ -305,9 +308,10 @@ pub mod pallet { T::KtonStaking::allocate(None, reward_to_kton_staking); } - pub(crate) fn allocate_ring_staking_reward_of_inner(who: T::AccountId) -> DispatchResult { - let amount = >::take(&who).ok_or(>::NoReward)?; - + pub(crate) fn allocate_ring_staking_reward_of_inner( + who: T::AccountId, + amount: Balance, + ) -> DispatchResult { T::RingStaking::allocate(Some(who.clone()), amount); Self::deposit_event(Event::Payout { who, amount }); @@ -342,6 +346,37 @@ pub mod pallet { } } + fn idle_allocate_ring_staking_reward(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(test)] + let weight = Weight::zero().add_ref_time(1); + #[cfg(not(test))] + let weight = T::WeightInfo::allocate_ring_staking_reward_of(); + let mut reward_to_allocate = Vec::new(); + + for (who, amount) in >::iter() { + if let Some(rw) = remaining_weight.checked_sub(&weight) { + *remaining_weight = rw; + + reward_to_allocate.push((who, amount)); + } else { + break; + } + } + + for (who, amount) in reward_to_allocate { + let _ = Self::allocate_ring_staking_reward_of_inner(who.clone(), amount); + + >::remove(&who); + } + } + 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)) { @@ -350,9 +385,9 @@ pub mod pallet { return; } - #[cfg(feature = "test")] + #[cfg(test)] let weight = Weight::zero().add_ref_time(1); - #[cfg(not(feature = "test"))] + #[cfg(not(test))] let weight = T::WeightInfo::unstake_all_for(); let mut ledgers_to_migrate = Vec::new(); @@ -367,7 +402,9 @@ pub mod pallet { } for (who, l) in ledgers_to_migrate { - let _ = Self::unstake_all_for_inner(who, l); + let _ = Self::unstake_all_for_inner(who.clone(), l); + + >::remove(&who); } } diff --git a/pallet/staking/src/mock.rs b/pallet/staking/src/mock.rs index 802bce517..289bacd9d 100644 --- a/pallet/staking/src/mock.rs +++ b/pallet/staking/src/mock.rs @@ -28,7 +28,7 @@ use dc_types::UNIT; // polkadot-sdk use frame_support::{ assert_ok, derive_impl, - traits::{OnFinalize, OnInitialize}, + traits::{OnFinalize, OnIdle, OnInitialize}, }; use sp_core::H160; use sp_io::TestExternalities; @@ -244,7 +244,7 @@ impl crate::Election for RingStaking { } } impl crate::Reward for RingStaking { - fn distribute(who: Option, amount: Balance) { + fn allocate(who: Option, amount: Balance) { let Some(who) = who else { return }; let _ = Balances::transfer_keep_alive( RuntimeOrigin::signed(Treasury::account_id()), @@ -255,7 +255,7 @@ impl crate::Reward for RingStaking { } pub enum KtonStaking {} impl crate::Reward for KtonStaking { - fn distribute(_: Option, amount: Balance) { + fn allocate(_: Option, amount: Balance) { let _ = Balances::transfer_keep_alive( RuntimeOrigin::signed(TreasuryAcct::get()), >::get().unwrap(), @@ -329,13 +329,9 @@ impl ExtBuilder { } .assimilate_storage(&mut storage) .unwrap(); - crate::GenesisConfig:: { - collator_count: 3, - collators: (1..=3).map(|i| AccountId(i)).collect(), - ..Default::default() - } - .assimilate_storage(&mut storage) - .unwrap(); + crate::GenesisConfig:: { collator_count: 3, ..Default::default() } + .assimilate_storage(&mut storage) + .unwrap(); let mut ext = TestExternalities::from(storage); @@ -368,6 +364,7 @@ pub fn initialize_block(number: BlockNumber) { } pub fn finalize_block(number: BlockNumber) { + AllPalletsWithSystem::on_idle(number, Weight::MAX); AllPalletsWithSystem::on_finalize(number); } diff --git a/pallet/staking/src/tests.rs b/pallet/staking/src/tests.rs index 279fa7da4..eb2dce83a 100644 --- a/pallet/staking/src/tests.rs +++ b/pallet/staking/src/tests.rs @@ -19,7 +19,7 @@ // darwinia use crate::{mock::*, *}; // polkadot-sdk -use frame_support::{assert_noop, assert_ok}; +use frame_support::{assert_noop, assert_ok, traits::OnIdle}; use sp_runtime::DispatchError; #[test] @@ -77,99 +77,53 @@ fn get_top_collators_should_work() { } #[test] -fn collator_caches_should_work() { +fn elect_should_work() { ExtBuilder.build().execute_with(|| { - 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_eq!( - >::get(), - (CacheState::Previous, CacheState::Current, CacheState::Next) - ); - - Staking::shift_cache_states(); - - assert!(call_on_cache!(>::get()).unwrap().is_empty()); - assert_eq!( - call_on_cache!(>::get()).unwrap(), - vec![AccountId(1), AccountId(2), AccountId(3)] - ); - assert!(call_on_cache!(>::get()).unwrap().is_empty()); - assert_eq!( - >::get(), - (CacheState::Next, CacheState::Previous, CacheState::Current) - ); - - Staking::shift_cache_states(); - - assert_eq!( - call_on_cache!(>::get()).unwrap(), - vec![AccountId(1), AccountId(2), AccountId(3)] - ); - 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) - ); - - Staking::shift_cache_states(); + NEXT_COLLATOR_ID.with(|v| *v.borrow_mut() = 4); - 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_eq!( - >::get(), - (CacheState::Previous, CacheState::Current, CacheState::Next) + ::RingStaking::elect(>::get()).unwrap(), + vec![AccountId(4), AccountId(5), AccountId(6)] ); }); } #[test] -fn elect_should_work() { +fn on_idle_allocate_ring_staking_reward_should_work() { ExtBuilder.build().execute_with(|| { - assert_eq!( - call_on_cache!(>::get()).unwrap(), - vec![AccountId(1), AccountId(2), AccountId(3)] - ); + (1..=512).for_each(|i| >::insert(AccountId(i), 1)); - NEXT_COLLATOR_ID.with(|v| *v.borrow_mut() = 4); - new_session(); + System::reset_events(); + AllPalletsWithSystem::on_idle(0, Weight::zero().add_ref_time(128)); + assert_eq!(events().into_iter().filter(|e| matches!(e, Event::Payout { .. })).count(), 128); - assert_eq!( - call_on_cache!(>::get()).unwrap(), - vec![AccountId(4), AccountId(5), AccountId(6)] - ); + System::reset_events(); + AllPalletsWithSystem::on_idle(0, Weight::MAX); + assert_eq!(events().into_iter().filter(|e| matches!(e, Event::Payout { .. })).count(), 384); }); } #[test] -fn auto_payout_should_work() { +fn on_idle_unstake_should_work() { ExtBuilder.build().execute_with(|| { - Efflux::block(1); - - (1..=3).for_each(|i| >::insert(AccountId(i), i as Balance)); + (1..=512).for_each(|i| { + >::insert( + AccountId(i), + Ledger { ring: i as _, deposits: BoundedVec::new() }, + ) + }); System::reset_events(); - Efflux::block(1); - dbg!(>::iter().collect::>()); - assert_eq!(events(), vec![Event::Payout { who: AccountId(2), amount: 2 }]); + AllPalletsWithSystem::on_idle(0, Weight::zero().add_ref_time(128)); + assert_eq!(>::iter().count(), 384); System::reset_events(); - Efflux::block(1); - dbg!(>::iter().collect::>()); - assert_eq!(events(), vec![Event::Payout { who: AccountId(3), amount: 3 }]); + AllPalletsWithSystem::on_idle(0, Weight::MAX); + assert_eq!(>::iter().count(), 0); - System::reset_events(); - Efflux::block(1); - dbg!(>::iter().collect::>()); - assert_eq!(events(), vec![Event::Payout { who: AccountId(1), amount: 1 }]); + (1..512).for_each(|who| { + assert_eq!(Balances::free_balance(AccountId(who)), 100 + who as Balance); + }); }); } @@ -186,14 +140,27 @@ fn unstake_all_for_should_work() { >::NoRecord ); - >::insert(AccountId(1), Ledger { ring: 1, deposits: Default::default() }); + >::insert(AccountId(1), Ledger { ring: 1, deposits: BoundedVec::new() }); assert_ok!(Staking::unstake_all_for(RuntimeOrigin::signed(AccountId(1)), AccountId(1))); }); } #[test] -fn payout_for_should_work() { - ExtBuilder.build().execute_with(|| {}); +fn allocate_ring_staking_reward_of_should_work() { + ExtBuilder.build().execute_with(|| { + let who = AccountId(1); + + assert_noop!( + Staking::allocate_ring_staking_reward_of(RuntimeOrigin::signed(who), who), + >::NoReward + ); + + >::insert(who, 1); + System::reset_events(); + + assert_ok!(Staking::allocate_ring_staking_reward_of(RuntimeOrigin::signed(who), who)); + assert_eq!(events(), vec![Event::Payout { who, amount: 1 }]); + }); } #[test] @@ -212,3 +179,29 @@ fn set_collator_count_should_work() { assert_eq!(Staking::collator_count(), 1); }); } + +#[test] +fn set_ring_staking_contract_should_work() { + ExtBuilder.build().execute_with(|| { + assert_noop!( + Staking::set_ring_staking_contract(RuntimeOrigin::signed(AccountId(1)), AccountId(1)), + DispatchError::BadOrigin + ); + + assert_ok!(Staking::set_ring_staking_contract(RuntimeOrigin::root(), AccountId(1))); + assert_eq!(Staking::ring_staking_contract(), Some(AccountId(1))); + }); +} + +#[test] +fn set_kton_staking_contract_should_work() { + ExtBuilder.build().execute_with(|| { + assert_noop!( + Staking::set_kton_staking_contract(RuntimeOrigin::signed(AccountId(1)), AccountId(1)), + DispatchError::BadOrigin + ); + + assert_ok!(Staking::set_kton_staking_contract(RuntimeOrigin::root(), AccountId(1))); + assert_eq!(Staking::kton_staking_contract(), Some(AccountId(1))); + }); +}