referrerpolicy=no-referrer-when-downgrade

pallet_nomination_pools/
mock.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use super::*;
19use crate::{self as pools};
20use frame_support::{
21	assert_ok, derive_impl, ord_parameter_types, parameter_types,
22	traits::{fungible::Mutate, VariantCountOf},
23	PalletId,
24};
25use frame_system::{EnsureSignedBy, RawOrigin};
26use sp_runtime::{BuildStorage, DispatchResult, FixedU128};
27use sp_staking::{
28	Agent, DelegationInterface, DelegationMigrator, Delegator, OnStakingUpdate, Stake,
29};
30
31pub type BlockNumber = u64;
32pub type AccountId = u128;
33pub type Balance = u128;
34pub type RewardCounter = FixedU128;
35// This sneaky little hack allows us to write code exactly as we would do in the pallet in the tests
36// as well, e.g. `StorageItem::<T>::get()`.
37pub type T = Runtime;
38pub type Currency = <T as Config>::Currency;
39
40// Ext builder creates a pool with id 1.
41pub fn default_bonded_account() -> AccountId {
42	Pools::generate_bonded_account(1)
43}
44
45// Ext builder creates a pool with id 1.
46pub fn default_reward_account() -> AccountId {
47	Pools::generate_reward_account(1)
48}
49
50parameter_types! {
51	pub static MinJoinBondConfig: Balance = 2;
52	pub static CurrentEra: EraIndex = 0;
53	pub static BondingDuration: EraIndex = 3;
54	pub storage BondedBalanceMap: BTreeMap<AccountId, Balance> = Default::default();
55	// map from a user to a vec of eras and amounts being unlocked in each era.
56	pub storage UnbondingBalanceMap: BTreeMap<AccountId, Vec<(EraIndex, Balance)>> = Default::default();
57	#[derive(Clone, PartialEq)]
58	pub static MaxUnbonding: u32 = 8;
59	pub static StakingMinBond: Balance = 10;
60	pub storage Nominations: Option<Vec<AccountId>> = None;
61	pub static RestrictedAccounts: Vec<AccountId> = Vec::new();
62}
63pub struct StakingMock;
64
65impl StakingMock {
66	pub(crate) fn set_bonded_balance(who: AccountId, bonded: Balance) {
67		let mut x = BondedBalanceMap::get();
68		x.insert(who, bonded);
69		BondedBalanceMap::set(&x)
70	}
71	/// Mimics a slash towards a pool specified by `pool_id`.
72	/// This reduces the bonded balance of a pool by `amount` and calls [`Pools::on_slash`] to
73	/// enact changes in the nomination-pool pallet.
74	///
75	/// Does not modify any [`SubPools`] of the pool as [`Default::default`] is passed for
76	/// `slashed_unlocking`.
77	pub fn slash_by(pool_id: PoolId, amount: Balance) {
78		let acc = Pools::generate_bonded_account(pool_id);
79		let bonded = BondedBalanceMap::get();
80		let pre_total = bonded.get(&acc).unwrap();
81		Self::set_bonded_balance(acc, pre_total - amount);
82		DelegateMock::on_slash(acc, amount);
83		Pools::on_slash(&acc, pre_total - amount, &Default::default(), amount);
84	}
85}
86
87impl sp_staking::StakingInterface for StakingMock {
88	type Balance = Balance;
89	type AccountId = AccountId;
90	type CurrencyToVote = ();
91
92	fn minimum_nominator_bond() -> Self::Balance {
93		StakingMinBond::get()
94	}
95	fn minimum_validator_bond() -> Self::Balance {
96		StakingMinBond::get()
97	}
98
99	fn desired_validator_count() -> u32 {
100		unimplemented!("method currently not used in testing")
101	}
102
103	fn current_era() -> EraIndex {
104		CurrentEra::get()
105	}
106
107	fn bonding_duration() -> EraIndex {
108		BondingDuration::get()
109	}
110
111	fn status(
112		_: &Self::AccountId,
113	) -> Result<sp_staking::StakerStatus<Self::AccountId>, DispatchError> {
114		Nominations::get()
115			.map(|noms| sp_staking::StakerStatus::Nominator(noms))
116			.ok_or(DispatchError::Other("NotStash"))
117	}
118
119	fn is_virtual_staker(who: &Self::AccountId) -> bool {
120		AgentBalanceMap::get().contains_key(who)
121	}
122
123	fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult {
124		let mut x = BondedBalanceMap::get();
125		x.get_mut(who).map(|v| *v += extra);
126		BondedBalanceMap::set(&x);
127		Ok(())
128	}
129
130	fn unbond(who: &Self::AccountId, amount: Self::Balance) -> DispatchResult {
131		let mut x = BondedBalanceMap::get();
132		*x.get_mut(who).unwrap() = x.get_mut(who).unwrap().saturating_sub(amount);
133		BondedBalanceMap::set(&x);
134
135		let era = Self::current_era();
136		let unlocking_at = era + Self::bonding_duration();
137		let mut y = UnbondingBalanceMap::get();
138		y.entry(*who).or_insert(Default::default()).push((unlocking_at, amount));
139		UnbondingBalanceMap::set(&y);
140		Ok(())
141	}
142
143	fn set_payee(_stash: &Self::AccountId, _reward_acc: &Self::AccountId) -> DispatchResult {
144		unimplemented!("method currently not used in testing")
145	}
146
147	fn chill(_: &Self::AccountId) -> sp_runtime::DispatchResult {
148		Ok(())
149	}
150
151	fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result<bool, DispatchError> {
152		let mut unbonding_map = UnbondingBalanceMap::get();
153
154		// closure to calculate the current unlocking funds across all eras/accounts.
155		let unlocking = |pair: &Vec<(EraIndex, Balance)>| -> Balance {
156			pair.iter()
157				.try_fold(Zero::zero(), |acc: Balance, (_at, amount)| acc.checked_add(*amount))
158				.unwrap()
159		};
160
161		let staker_map = unbonding_map.get_mut(&who).ok_or("Nothing to unbond")?;
162		let unlocking_before = unlocking(&staker_map);
163
164		let current_era = Self::current_era();
165
166		staker_map.retain(|(unlocking_at, _amount)| *unlocking_at > current_era);
167
168		// if there was a withdrawal, notify the pallet.
169		let withdraw_amount = unlocking_before.saturating_sub(unlocking(&staker_map));
170		Pools::on_withdraw(&who, withdraw_amount);
171		DelegateMock::on_withdraw(who, withdraw_amount);
172
173		UnbondingBalanceMap::set(&unbonding_map);
174		Ok(UnbondingBalanceMap::get().get(&who).unwrap().is_empty() &&
175			BondedBalanceMap::get().get(&who).unwrap().is_zero())
176	}
177
178	fn bond(stash: &Self::AccountId, value: Self::Balance, _: &Self::AccountId) -> DispatchResult {
179		StakingMock::set_bonded_balance(*stash, value);
180		Ok(())
181	}
182
183	fn nominate(_: &Self::AccountId, nominations: Vec<Self::AccountId>) -> DispatchResult {
184		Nominations::set(&Some(nominations));
185		Ok(())
186	}
187
188	#[cfg(feature = "runtime-benchmarks")]
189	fn nominations(_: &Self::AccountId) -> Option<Vec<Self::AccountId>> {
190		Nominations::get()
191	}
192
193	fn stash_by_ctrl(_controller: &Self::AccountId) -> Result<Self::AccountId, DispatchError> {
194		unimplemented!("method currently not used in testing")
195	}
196
197	fn stake(who: &Self::AccountId) -> Result<Stake<Balance>, DispatchError> {
198		match (UnbondingBalanceMap::get().get(who), BondedBalanceMap::get().get(who).copied()) {
199			(None, None) => Err(DispatchError::Other("balance not found")),
200			(Some(v), None) => Ok(Stake {
201				total: v.into_iter().fold(0u128, |acc, &x| acc.saturating_add(x.1)),
202				active: 0,
203			}),
204			(None, Some(v)) => Ok(Stake { total: v, active: v }),
205			(Some(a), Some(b)) => Ok(Stake {
206				total: a.into_iter().fold(0u128, |acc, &x| acc.saturating_add(x.1)) + b,
207				active: b,
208			}),
209		}
210	}
211
212	fn election_ongoing() -> bool {
213		unimplemented!("method currently not used in testing")
214	}
215
216	fn force_unstake(_who: Self::AccountId) -> sp_runtime::DispatchResult {
217		unimplemented!("method currently not used in testing")
218	}
219
220	fn is_exposed_in_era(_who: &Self::AccountId, _era: &EraIndex) -> bool {
221		unimplemented!("method currently not used in testing")
222	}
223
224	#[cfg(feature = "runtime-benchmarks")]
225	fn add_era_stakers(
226		_current_era: &EraIndex,
227		_stash: &Self::AccountId,
228		_exposures: Vec<(Self::AccountId, Self::Balance)>,
229	) {
230		unimplemented!("method currently not used in testing")
231	}
232
233	#[cfg(feature = "runtime-benchmarks")]
234	fn set_current_era(_era: EraIndex) {
235		unimplemented!("method currently not used in testing")
236	}
237
238	#[cfg(feature = "runtime-benchmarks")]
239	fn max_exposure_page_size() -> sp_staking::Page {
240		unimplemented!("method currently not used in testing")
241	}
242
243	fn slash_reward_fraction() -> Perbill {
244		unimplemented!("method currently not used in testing")
245	}
246}
247
248parameter_types! {
249	// Map of agent to their (delegated balance, unclaimed withdrawal, pending slash).
250	pub storage AgentBalanceMap: BTreeMap<AccountId, (Balance, Balance, Balance)> = Default::default();
251	pub storage DelegatorBalanceMap: BTreeMap<AccountId, Balance> = Default::default();
252}
253pub struct DelegateMock;
254impl DelegationInterface for DelegateMock {
255	type Balance = Balance;
256	type AccountId = AccountId;
257	fn agent_balance(agent: Agent<Self::AccountId>) -> Option<Self::Balance> {
258		AgentBalanceMap::get()
259			.get(&agent.get())
260			.copied()
261			.map(|(delegated, _, pending)| delegated - pending)
262	}
263
264	fn agent_transferable_balance(agent: Agent<Self::AccountId>) -> Option<Self::Balance> {
265		AgentBalanceMap::get()
266			.get(&agent.get())
267			.copied()
268			.map(|(_, unclaimed_withdrawals, _)| unclaimed_withdrawals)
269	}
270
271	fn delegator_balance(delegator: Delegator<Self::AccountId>) -> Option<Self::Balance> {
272		DelegatorBalanceMap::get().get(&delegator.get()).copied()
273	}
274
275	fn register_agent(
276		agent: Agent<Self::AccountId>,
277		_reward_account: &Self::AccountId,
278	) -> DispatchResult {
279		let mut agents = AgentBalanceMap::get();
280		agents.insert(agent.get(), (0, 0, 0));
281		AgentBalanceMap::set(&agents);
282		Ok(())
283	}
284
285	fn remove_agent(agent: Agent<Self::AccountId>) -> DispatchResult {
286		let mut agents = AgentBalanceMap::get();
287		let agent = agent.get();
288		assert!(agents.contains_key(&agent));
289		agents.remove(&agent);
290		AgentBalanceMap::set(&agents);
291		Ok(())
292	}
293
294	fn delegate(
295		delegator: Delegator<Self::AccountId>,
296		agent: Agent<Self::AccountId>,
297		amount: Self::Balance,
298	) -> DispatchResult {
299		let delegator = delegator.get();
300		let mut delegators = DelegatorBalanceMap::get();
301		delegators.entry(delegator).and_modify(|b| *b += amount).or_insert(amount);
302		DelegatorBalanceMap::set(&delegators);
303
304		let agent = agent.get();
305		let mut agents = AgentBalanceMap::get();
306		agents
307			.get_mut(&agent)
308			.map(|(d, _, _)| *d += amount)
309			.ok_or(DispatchError::Other("agent not registered"))?;
310		AgentBalanceMap::set(&agents);
311
312		if BondedBalanceMap::get().contains_key(&agent) {
313			StakingMock::bond_extra(&agent, amount)
314		} else {
315			// reward account does not matter in this context.
316			StakingMock::bond(&agent, amount, &999)
317		}
318	}
319
320	fn withdraw_delegation(
321		delegator: Delegator<Self::AccountId>,
322		agent: Agent<Self::AccountId>,
323		amount: Self::Balance,
324		_num_slashing_spans: u32,
325	) -> DispatchResult {
326		let mut delegators = DelegatorBalanceMap::get();
327		delegators.get_mut(&delegator.get()).map(|b| *b -= amount);
328		DelegatorBalanceMap::set(&delegators);
329
330		let mut agents = AgentBalanceMap::get();
331		agents.get_mut(&agent.get()).map(|(d, u, _)| {
332			*d -= amount;
333			*u -= amount;
334		});
335		AgentBalanceMap::set(&agents);
336
337		Ok(())
338	}
339
340	fn pending_slash(agent: Agent<Self::AccountId>) -> Option<Self::Balance> {
341		AgentBalanceMap::get()
342			.get(&agent.get())
343			.copied()
344			.map(|(_, _, pending_slash)| pending_slash)
345	}
346
347	fn delegator_slash(
348		agent: Agent<Self::AccountId>,
349		delegator: Delegator<Self::AccountId>,
350		value: Self::Balance,
351		_maybe_reporter: Option<Self::AccountId>,
352	) -> DispatchResult {
353		let mut delegators = DelegatorBalanceMap::get();
354		delegators.get_mut(&delegator.get()).map(|b| *b -= value);
355		DelegatorBalanceMap::set(&delegators);
356
357		let mut agents = AgentBalanceMap::get();
358		agents.get_mut(&agent.get()).map(|(_, _, p)| {
359			p.saturating_reduce(value);
360		});
361		AgentBalanceMap::set(&agents);
362
363		Ok(())
364	}
365}
366
367impl DelegateMock {
368	pub fn set_agent_balance(who: AccountId, delegated: Balance) {
369		let mut agents = AgentBalanceMap::get();
370		agents.insert(who, (delegated, 0, 0));
371		AgentBalanceMap::set(&agents);
372	}
373
374	pub fn set_delegator_balance(who: AccountId, amount: Balance) {
375		let mut delegators = DelegatorBalanceMap::get();
376		delegators.insert(who, amount);
377		DelegatorBalanceMap::set(&delegators);
378	}
379
380	pub fn on_slash(agent: AccountId, amount: Balance) {
381		let mut agents = AgentBalanceMap::get();
382		agents.get_mut(&agent).map(|(_, _, p)| *p += amount);
383		AgentBalanceMap::set(&agents);
384	}
385
386	fn on_withdraw(agent: AccountId, amount: Balance) {
387		let mut agents = AgentBalanceMap::get();
388		// if agent exists, add the amount to unclaimed withdrawals.
389		agents.get_mut(&agent).map(|(_, u, _)| *u += amount);
390		AgentBalanceMap::set(&agents);
391	}
392}
393
394impl DelegationMigrator for DelegateMock {
395	type Balance = Balance;
396	type AccountId = AccountId;
397	fn migrate_nominator_to_agent(
398		_agent: Agent<Self::AccountId>,
399		_reward_account: &Self::AccountId,
400	) -> DispatchResult {
401		unimplemented!("not used in current unit tests")
402	}
403
404	fn migrate_delegation(
405		_agent: Agent<Self::AccountId>,
406		_delegator: Delegator<Self::AccountId>,
407		_value: Self::Balance,
408	) -> DispatchResult {
409		unimplemented!("not used in current unit tests")
410	}
411
412	#[cfg(feature = "runtime-benchmarks")]
413	fn force_kill_agent(_agent: Agent<Self::AccountId>) {
414		unimplemented!("not used in current unit tests")
415	}
416}
417
418#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
419impl frame_system::Config for Runtime {
420	type Nonce = u64;
421	type AccountId = AccountId;
422	type Lookup = sp_runtime::traits::IdentityLookup<Self::AccountId>;
423	type Block = Block;
424	type AccountData = pallet_balances::AccountData<Balance>;
425}
426
427parameter_types! {
428	pub static ExistentialDeposit: Balance = 5;
429}
430
431#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
432impl pallet_balances::Config for Runtime {
433	type Balance = Balance;
434	type ExistentialDeposit = ExistentialDeposit;
435	type AccountStore = System;
436	type FreezeIdentifier = RuntimeFreezeReason;
437	type MaxFreezes = VariantCountOf<RuntimeFreezeReason>;
438	type RuntimeFreezeReason = RuntimeFreezeReason;
439}
440
441pub struct BalanceToU256;
442impl Convert<Balance, U256> for BalanceToU256 {
443	fn convert(n: Balance) -> U256 {
444		n.into()
445	}
446}
447
448pub struct U256ToBalance;
449impl Convert<U256, Balance> for U256ToBalance {
450	fn convert(n: U256) -> Balance {
451		n.try_into().unwrap()
452	}
453}
454
455pub struct RestrictMock;
456impl Contains<AccountId> for RestrictMock {
457	fn contains(who: &AccountId) -> bool {
458		RestrictedAccounts::get().contains(who)
459	}
460}
461
462parameter_types! {
463	pub static PostUnbondingPoolsWindow: u32 = 2;
464	pub static MaxMetadataLen: u32 = 2;
465	pub static CheckLevel: u8 = 255;
466	pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
467}
468
469ord_parameter_types! {
470	pub const Admin: u128 = 42;
471}
472
473impl pools::Config for Runtime {
474	type RuntimeEvent = RuntimeEvent;
475	type WeightInfo = ();
476	type Currency = Balances;
477	type RuntimeFreezeReason = RuntimeFreezeReason;
478	type RewardCounter = RewardCounter;
479	type BalanceToU256 = BalanceToU256;
480	type U256ToBalance = U256ToBalance;
481	type StakeAdapter = adapter::DelegateStake<Self, StakingMock, DelegateMock>;
482	type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
483	type PalletId = PoolsPalletId;
484	type MaxMetadataLen = MaxMetadataLen;
485	type MaxUnbonding = MaxUnbonding;
486	type MaxPointsToBalance = frame_support::traits::ConstU8<10>;
487	type AdminOrigin = EnsureSignedBy<Admin, AccountId>;
488	type BlockNumberProvider = System;
489	type Filter = RestrictMock;
490}
491
492type Block = frame_system::mocking::MockBlock<Runtime>;
493frame_support::construct_runtime!(
494	pub enum Runtime {
495		System: frame_system,
496		Balances: pallet_balances,
497		Pools: pools,
498	}
499);
500
501pub struct ExtBuilder {
502	members: Vec<(AccountId, Balance)>,
503	max_members: Option<u32>,
504	max_members_per_pool: Option<u32>,
505	global_max_commission: Option<Perbill>,
506}
507
508impl Default for ExtBuilder {
509	fn default() -> Self {
510		Self {
511			members: Default::default(),
512			max_members: Some(4),
513			max_members_per_pool: Some(3),
514			global_max_commission: Some(Perbill::from_percent(90)),
515		}
516	}
517}
518
519#[cfg_attr(feature = "fuzzing", allow(dead_code))]
520impl ExtBuilder {
521	// Add members to pool 0.
522	pub fn add_members(mut self, members: Vec<(AccountId, Balance)>) -> Self {
523		self.members = members;
524		self
525	}
526
527	pub fn ed(self, ed: Balance) -> Self {
528		ExistentialDeposit::set(ed);
529		self
530	}
531
532	pub fn min_bond(self, min: Balance) -> Self {
533		StakingMinBond::set(min);
534		self
535	}
536
537	pub fn min_join_bond(self, min: Balance) -> Self {
538		MinJoinBondConfig::set(min);
539		self
540	}
541
542	pub fn with_check(self, level: u8) -> Self {
543		CheckLevel::set(level);
544		self
545	}
546
547	pub fn max_members(mut self, max: Option<u32>) -> Self {
548		self.max_members = max;
549		self
550	}
551
552	pub fn max_members_per_pool(mut self, max: Option<u32>) -> Self {
553		self.max_members_per_pool = max;
554		self
555	}
556
557	pub fn global_max_commission(mut self, commission: Option<Perbill>) -> Self {
558		self.global_max_commission = commission;
559		self
560	}
561
562	pub fn build(self) -> sp_io::TestExternalities {
563		sp_tracing::try_init_simple();
564		let mut storage =
565			frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
566
567		let _ = crate::GenesisConfig::<Runtime> {
568			min_join_bond: MinJoinBondConfig::get(),
569			min_create_bond: 2,
570			max_pools: Some(2),
571			max_members_per_pool: self.max_members_per_pool,
572			max_members: self.max_members,
573			global_max_commission: self.global_max_commission,
574		}
575		.assimilate_storage(&mut storage);
576
577		let mut ext = sp_io::TestExternalities::from(storage);
578
579		ext.execute_with(|| {
580			// for events to be deposited.
581			frame_system::Pallet::<Runtime>::set_block_number(1);
582
583			// make a pool
584			let amount_to_bond = Pools::depositor_min_bond();
585			Currency::set_balance(&10, amount_to_bond * 5);
586			assert_ok!(Pools::create(RawOrigin::Signed(10).into(), amount_to_bond, 900, 901, 902));
587			assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1]));
588			let last_pool = LastPoolId::<Runtime>::get();
589			for (account_id, bonded) in self.members {
590				<Runtime as Config>::Currency::set_balance(&account_id, bonded * 2);
591				assert_ok!(Pools::join(RawOrigin::Signed(account_id).into(), bonded, last_pool));
592			}
593		});
594
595		ext
596	}
597
598	pub fn build_and_execute(self, test: impl FnOnce()) {
599		self.build().execute_with(|| {
600			test();
601			Pools::do_try_state(CheckLevel::get()).unwrap();
602		})
603	}
604}
605
606pub fn unsafe_set_state(pool_id: PoolId, state: PoolState) {
607	BondedPools::<Runtime>::try_mutate(pool_id, |maybe_bonded_pool| {
608		maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| {
609			bonded_pool.state = state;
610		})
611	})
612	.unwrap()
613}
614
615parameter_types! {
616	storage PoolsEvents: u32 = 0;
617	storage BalancesEvents: u32 = 0;
618}
619
620/// Helper to run a specified amount of blocks.
621pub fn run_blocks(n: u64) {
622	let current_block = System::block_number();
623	System::run_to_block::<AllPalletsWithSystem>(n + current_block);
624}
625
626/// All events of this pallet.
627pub fn pool_events_since_last_call() -> Vec<super::Event<Runtime>> {
628	let events = System::events()
629		.into_iter()
630		.map(|r| r.event)
631		.filter_map(|e| if let RuntimeEvent::Pools(inner) = e { Some(inner) } else { None })
632		.collect::<Vec<_>>();
633	let already_seen = PoolsEvents::get();
634	PoolsEvents::set(&(events.len() as u32));
635	events.into_iter().skip(already_seen as usize).collect()
636}
637
638/// All events of the `Balances` pallet.
639pub fn balances_events_since_last_call() -> Vec<pallet_balances::Event<Runtime>> {
640	let events = System::events()
641		.into_iter()
642		.map(|r| r.event)
643		.filter_map(|e| if let RuntimeEvent::Balances(inner) = e { Some(inner) } else { None })
644		.collect::<Vec<_>>();
645	let already_seen = BalancesEvents::get();
646	BalancesEvents::set(&(events.len() as u32));
647	events.into_iter().skip(already_seen as usize).collect()
648}
649
650/// Same as `fully_unbond`, in permissioned setting.
651pub fn fully_unbond_permissioned(member: AccountId) -> DispatchResult {
652	let points = PoolMembers::<Runtime>::get(member)
653		.map(|d| d.active_points())
654		.unwrap_or_default();
655	Pools::unbond(RuntimeOrigin::signed(member), member, points)
656}
657
658pub fn pending_rewards_for_delegator(delegator: AccountId) -> Balance {
659	let member = PoolMembers::<T>::get(delegator).unwrap();
660	let bonded_pool = BondedPools::<T>::get(member.pool_id).unwrap();
661	let reward_pool = RewardPools::<T>::get(member.pool_id).unwrap();
662
663	assert!(!bonded_pool.points.is_zero());
664
665	let commission = bonded_pool.commission.current();
666	let current_rc = reward_pool
667		.current_reward_counter(member.pool_id, bonded_pool.points, commission)
668		.unwrap()
669		.0;
670
671	member.pending_rewards(current_rc).unwrap_or_default()
672}
673
674#[derive(PartialEq, Debug)]
675pub enum RewardImbalance {
676	// There is no reward deficit.
677	Surplus(Balance),
678	// There is a reward deficit.
679	Deficit(Balance),
680}
681
682pub fn pool_pending_rewards(pool: PoolId) -> Result<BalanceOf<T>, sp_runtime::DispatchError> {
683	let bonded_pool = BondedPools::<T>::get(pool).ok_or(Error::<T>::PoolNotFound)?;
684	let reward_pool = RewardPools::<T>::get(pool).ok_or(Error::<T>::PoolNotFound)?;
685
686	let current_rc = if !bonded_pool.points.is_zero() {
687		let commission = bonded_pool.commission.current();
688		reward_pool.current_reward_counter(pool, bonded_pool.points, commission)?.0
689	} else {
690		Default::default()
691	};
692
693	Ok(PoolMembers::<T>::iter()
694		.filter(|(_, d)| d.pool_id == pool)
695		.map(|(_, d)| d.pending_rewards(current_rc).unwrap_or_default())
696		.fold(0u32.into(), |acc: BalanceOf<T>, x| acc.saturating_add(x)))
697}
698
699pub fn reward_imbalance(pool: PoolId) -> RewardImbalance {
700	let pending_rewards = pool_pending_rewards(pool).expect("pool should exist");
701	let current_balance = RewardPool::<Runtime>::current_balance(pool);
702
703	if pending_rewards > current_balance {
704		RewardImbalance::Deficit(pending_rewards - current_balance)
705	} else {
706		RewardImbalance::Surplus(current_balance - pending_rewards)
707	}
708}
709
710pub fn set_pool_balance(who: AccountId, amount: Balance) {
711	StakingMock::set_bonded_balance(who, amount);
712	DelegateMock::set_agent_balance(who, amount);
713}
714
715pub fn member_delegation(who: AccountId) -> Balance {
716	<T as Config>::StakeAdapter::member_delegation_balance(Member::from(who))
717		.expect("who must be a pool member")
718}
719
720pub fn pool_balance(id: PoolId) -> Balance {
721	<T as Config>::StakeAdapter::total_balance(Pool::from(Pools::generate_bonded_account(id)))
722		.expect("who must be a bonded pool account")
723}
724
725pub fn add_to_restrict_list(who: &AccountId) {
726	if !RestrictedAccounts::get().contains(who) {
727		RestrictedAccounts::mutate(|l| l.push(*who));
728	}
729}
730
731pub fn remove_from_restrict_list(who: &AccountId) {
732	RestrictedAccounts::mutate(|l| l.retain(|x| x != who));
733}
734
735#[cfg(test)]
736mod test {
737	use super::*;
738	#[test]
739	fn u256_to_balance_convert_works() {
740		assert_eq!(U256ToBalance::convert(0u32.into()), Zero::zero());
741		assert_eq!(U256ToBalance::convert(Balance::max_value().into()), Balance::max_value())
742	}
743
744	#[test]
745	#[should_panic]
746	fn u256_to_balance_convert_panics_correctly() {
747		U256ToBalance::convert(U256::from(Balance::max_value()).saturating_add(1u32.into()));
748	}
749
750	#[test]
751	fn balance_to_u256_convert_works() {
752		assert_eq!(BalanceToU256::convert(0u32.into()), U256::zero());
753		assert_eq!(BalanceToU256::convert(Balance::max_value()), Balance::max_value().into())
754	}
755}