referrerpolicy=no-referrer-when-downgrade

pallet_nomination_pools/
adapter.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 crate::*;
19use frame_support::traits::tokens::{Fortitude::Polite, Preservation::Expendable};
20use sp_staking::{Agent, DelegationInterface, DelegationMigrator, Delegator};
21
22/// Types of stake strategies.
23///
24/// Useful for determining current staking strategy of a runtime and enforce integrity tests.
25#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq)]
26pub enum StakeStrategyType {
27	/// Member funds are transferred to pool account and staked.
28	///
29	/// This is the older staking strategy used by pools. For a new runtime, it is recommended to
30	/// use [`StakeStrategyType::Delegate`] strategy instead.
31	Transfer,
32	/// Member funds are delegated to pool account and staked.
33	Delegate,
34}
35
36/// A type that only belongs in context of a pool.
37///
38/// Maps directly [`Agent`] account.
39#[derive(Clone, Debug)]
40pub struct Pool<T>(T);
41impl<AccountID> Into<Agent<AccountID>> for Pool<AccountID> {
42	fn into(self) -> Agent<AccountID> {
43		Agent::from(self.0)
44	}
45}
46impl<T> From<T> for Pool<T> {
47	fn from(acc: T) -> Self {
48		Pool(acc)
49	}
50}
51
52impl<T> Pool<T> {
53	pub fn get(self) -> T {
54		self.0
55	}
56}
57
58/// A type that only belongs in context of a pool member.
59///
60/// Maps directly [`Delegator`] account.
61#[derive(Clone, Debug)]
62pub struct Member<T>(T);
63impl<AccountID> Into<Delegator<AccountID>> for Member<AccountID> {
64	fn into(self) -> Delegator<AccountID> {
65		Delegator::from(self.0)
66	}
67}
68impl<T> From<T> for Member<T> {
69	fn from(acc: T) -> Self {
70		Member(acc)
71	}
72}
73
74impl<T> Member<T> {
75	pub fn get(self) -> T {
76		self.0
77	}
78}
79
80/// An adapter trait that can support multiple staking strategies.
81///
82/// Depending on which staking strategy we want to use, the staking logic can be slightly
83/// different. Refer the two possible strategies currently: [`TransferStake`] and
84/// [`DelegateStake`] for more detail.
85pub trait StakeStrategy {
86	type Balance: frame_support::traits::tokens::Balance;
87	type AccountId: Clone + core::fmt::Debug;
88	type CoreStaking: StakingInterface<Balance = Self::Balance, AccountId = Self::AccountId>;
89
90	/// The type of staking strategy of the current adapter.
91	fn strategy_type() -> StakeStrategyType;
92
93	/// See [`StakingInterface::nominator_bonding_duration`].
94	///
95	/// Pool accounts are nominators, so they use the nominator bonding duration which can be
96	/// shorter than the validator bonding duration when nominators are not slashable.
97	fn bonding_duration() -> EraIndex {
98		Self::CoreStaking::nominator_bonding_duration()
99	}
100
101	/// See [`StakingInterface::current_era`].
102	///
103	/// Note: Named current_era for legacy interface compatibility. Returns active era which
104	/// should be used for all non-election staking logic.
105	fn current_era() -> EraIndex {
106		Self::CoreStaking::current_era()
107	}
108
109	/// See [`StakingInterface::minimum_nominator_bond`].
110	fn minimum_nominator_bond() -> Self::Balance {
111		Self::CoreStaking::minimum_nominator_bond()
112	}
113
114	/// Balance that can be transferred from pool account to member.
115	///
116	/// This is part of the pool balance that can be withdrawn.
117	fn transferable_balance(
118		pool_account: Pool<Self::AccountId>,
119		member_account: Member<Self::AccountId>,
120	) -> Self::Balance;
121
122	/// Total balance of the pool including amount that is actively staked.
123	fn total_balance(pool_account: Pool<Self::AccountId>) -> Option<Self::Balance>;
124
125	/// Amount of tokens delegated by the member.
126	fn member_delegation_balance(member_account: Member<Self::AccountId>) -> Option<Self::Balance>;
127
128	/// See [`StakingInterface::active_stake`].
129	fn active_stake(pool_account: Pool<Self::AccountId>) -> Self::Balance {
130		Self::CoreStaking::active_stake(&pool_account.0).unwrap_or_default()
131	}
132
133	/// See [`StakingInterface::total_stake`].
134	fn total_stake(pool_account: Pool<Self::AccountId>) -> Self::Balance {
135		Self::CoreStaking::total_stake(&pool_account.0).unwrap_or_default()
136	}
137
138	/// Which strategy the pool account is using.
139	///
140	/// This can be different from the [`Self::strategy_type`] of the adapter if the pool has not
141	/// migrated to the new strategy yet.
142	fn pool_strategy(pool_account: Pool<Self::AccountId>) -> StakeStrategyType {
143		match Self::CoreStaking::is_virtual_staker(&pool_account.0) {
144			true => StakeStrategyType::Delegate,
145			false => StakeStrategyType::Transfer,
146		}
147	}
148
149	/// See [`StakingInterface::nominate`].
150	fn nominate(
151		pool_account: Pool<Self::AccountId>,
152		validators: Vec<Self::AccountId>,
153	) -> DispatchResult {
154		Self::CoreStaking::nominate(&pool_account.0, validators)
155	}
156
157	/// See [`StakingInterface::chill`].
158	fn chill(pool_account: Pool<Self::AccountId>) -> DispatchResult {
159		Self::CoreStaking::chill(&pool_account.0)
160	}
161
162	/// Pledge `amount` towards `pool_account` and update the pool bond. Also see
163	/// [`StakingInterface::bond`].
164	fn pledge_bond(
165		who: Member<Self::AccountId>,
166		pool_account: Pool<Self::AccountId>,
167		reward_account: &Self::AccountId,
168		amount: Self::Balance,
169		bond_type: BondType,
170	) -> DispatchResult;
171
172	/// See [`StakingInterface::unbond`].
173	fn unbond(pool_account: Pool<Self::AccountId>, amount: Self::Balance) -> DispatchResult {
174		Self::CoreStaking::unbond(&pool_account.0, amount)
175	}
176
177	/// See [`StakingInterface::withdraw_unbonded`].
178	fn withdraw_unbonded(
179		pool_account: Pool<Self::AccountId>,
180		num_slashing_spans: u32,
181	) -> Result<bool, DispatchError> {
182		Self::CoreStaking::withdraw_unbonded(pool_account.0, num_slashing_spans)
183	}
184
185	/// Withdraw funds from pool account to member account.
186	fn member_withdraw(
187		who: Member<Self::AccountId>,
188		pool_account: Pool<Self::AccountId>,
189		amount: Self::Balance,
190		num_slashing_spans: u32,
191	) -> DispatchResult;
192
193	/// Dissolve the pool account.
194	fn dissolve(pool_account: Pool<Self::AccountId>) -> DispatchResult;
195
196	/// Check if there is any pending slash for the pool.
197	fn pending_slash(pool_account: Pool<Self::AccountId>) -> Self::Balance;
198
199	/// Slash the member account with `amount` against pending slashes for the pool.
200	fn member_slash(
201		who: Member<Self::AccountId>,
202		pool_account: Pool<Self::AccountId>,
203		amount: Self::Balance,
204		maybe_reporter: Option<Self::AccountId>,
205	) -> DispatchResult;
206
207	/// Migrate pool account from being a direct nominator to a delegated agent.
208	///
209	/// This is useful for migrating a pool account from [`StakeStrategyType::Transfer`] to
210	/// [`StakeStrategyType::Delegate`].
211	fn migrate_nominator_to_agent(
212		pool_account: Pool<Self::AccountId>,
213		reward_account: &Self::AccountId,
214	) -> DispatchResult;
215
216	/// Migrate member balance from pool account to member account.
217	///
218	/// This is useful for a pool account that migrated from [`StakeStrategyType::Transfer`] to
219	/// [`StakeStrategyType::Delegate`]. Its members can then migrate their delegated balance
220	/// back to their account.
221	///
222	/// Internally, the member funds that are locked in the pool account are transferred back and
223	/// locked in the member account.
224	fn migrate_delegation(
225		pool: Pool<Self::AccountId>,
226		delegator: Member<Self::AccountId>,
227		value: Self::Balance,
228	) -> DispatchResult;
229
230	/// List of validators nominated by the pool account.
231	#[cfg(feature = "runtime-benchmarks")]
232	fn nominations(pool_account: Pool<Self::AccountId>) -> Option<Vec<Self::AccountId>> {
233		Self::CoreStaking::nominations(&pool_account.0)
234	}
235
236	/// Remove the pool account as agent.
237	///
238	/// Useful for migrating pool account from a delegated agent to a direct nominator. Only used
239	/// in tests and benchmarks.
240	#[cfg(feature = "runtime-benchmarks")]
241	fn remove_as_agent(_pool: Pool<Self::AccountId>) {
242		// noop by default
243	}
244}
245
246/// A staking strategy implementation that supports transfer based staking.
247///
248/// In order to stake, this adapter transfers the funds from the member/delegator account to the
249/// pool account and stakes through the pool account on `Staking`.
250///
251/// This is the older Staking strategy used by pools. To switch to the newer [`DelegateStake`]
252/// strategy in an existing runtime, storage migration is required. See
253/// [`migration::unversioned::DelegationStakeMigration`]. For new runtimes, it is highly recommended
254/// to use the [`DelegateStake`] strategy.
255#[deprecated = "consider migrating to DelegateStake"]
256pub struct TransferStake<T: Config, Staking: StakingInterface>(PhantomData<(T, Staking)>);
257
258#[allow(deprecated)]
259impl<T: Config, Staking: StakingInterface<Balance = BalanceOf<T>, AccountId = T::AccountId>>
260	StakeStrategy for TransferStake<T, Staking>
261{
262	type Balance = BalanceOf<T>;
263	type AccountId = T::AccountId;
264	type CoreStaking = Staking;
265
266	fn strategy_type() -> StakeStrategyType {
267		StakeStrategyType::Transfer
268	}
269
270	fn transferable_balance(
271		pool_account: Pool<Self::AccountId>,
272		_: Member<Self::AccountId>,
273	) -> BalanceOf<T> {
274		// free/liquid balance of the pool account.
275		T::Currency::reducible_balance(&pool_account.get(), Expendable, Polite)
276	}
277
278	fn total_balance(pool_account: Pool<Self::AccountId>) -> Option<BalanceOf<T>> {
279		Some(T::Currency::total_balance(&pool_account.get()))
280	}
281
282	fn member_delegation_balance(
283		_member_account: Member<T::AccountId>,
284	) -> Option<Staking::Balance> {
285		// for transfer stake, no delegation exists.
286		None
287	}
288
289	fn pledge_bond(
290		who: Member<T::AccountId>,
291		pool_account: Pool<Self::AccountId>,
292		reward_account: &Self::AccountId,
293		amount: BalanceOf<T>,
294		bond_type: BondType,
295	) -> DispatchResult {
296		match bond_type {
297			BondType::Create => {
298				// first bond
299				T::Currency::transfer(&who.0, &pool_account.0, amount, Preservation::Expendable)?;
300				Staking::bond(&pool_account.0, amount, &reward_account)
301			},
302			BondType::Extra => {
303				// additional bond
304				T::Currency::transfer(&who.0, &pool_account.0, amount, Preservation::Preserve)?;
305				Staking::bond_extra(&pool_account.0, amount)
306			},
307		}
308	}
309
310	fn member_withdraw(
311		who: Member<Self::AccountId>,
312		pool_account: Pool<Self::AccountId>,
313		amount: BalanceOf<T>,
314		_num_slashing_spans: u32,
315	) -> DispatchResult {
316		T::Currency::transfer(&pool_account.0, &who.0, amount, Preservation::Expendable)?;
317
318		Ok(())
319	}
320
321	fn dissolve(pool_account: Pool<Self::AccountId>) -> DispatchResult {
322		defensive_assert!(
323			T::Currency::total_balance(&pool_account.clone().get()).is_zero(),
324			"dissolving pool should not have any balance"
325		);
326
327		// Defensively force set balance to zero.
328		T::Currency::set_balance(&pool_account.get(), Zero::zero());
329		Ok(())
330	}
331
332	fn pending_slash(_: Pool<Self::AccountId>) -> Self::Balance {
333		// for transfer stake strategy, slashing is greedy and never deferred.
334		Zero::zero()
335	}
336
337	fn member_slash(
338		_who: Member<Self::AccountId>,
339		_pool: Pool<Self::AccountId>,
340		_amount: Staking::Balance,
341		_maybe_reporter: Option<T::AccountId>,
342	) -> DispatchResult {
343		Err(Error::<T>::Defensive(DefensiveError::DelegationUnsupported).into())
344	}
345
346	fn migrate_nominator_to_agent(
347		_pool: Pool<Self::AccountId>,
348		_reward_account: &Self::AccountId,
349	) -> DispatchResult {
350		Err(Error::<T>::Defensive(DefensiveError::DelegationUnsupported).into())
351	}
352
353	fn migrate_delegation(
354		_pool: Pool<Self::AccountId>,
355		_delegator: Member<Self::AccountId>,
356		_value: Self::Balance,
357	) -> DispatchResult {
358		Err(Error::<T>::Defensive(DefensiveError::DelegationUnsupported).into())
359	}
360}
361
362/// A staking strategy implementation that supports delegation based staking.
363///
364/// In this approach, first the funds are delegated from delegator to the pool account and later
365/// staked with `Staking`. The advantage of this approach is that the funds are held in the
366/// user account itself and not in the pool account.
367///
368/// This is the newer staking strategy used by pools. Once switched to this and migrated, ideally
369/// the `TransferStake` strategy should not be used. Or a separate migration would be required for
370/// it which is not provided by this pallet.
371///
372/// Use [`migration::unversioned::DelegationStakeMigration`] to migrate to this strategy.
373pub struct DelegateStake<T: Config, Staking: StakingInterface, Delegation: DelegationInterface>(
374	PhantomData<(T, Staking, Delegation)>,
375);
376
377impl<
378		T: Config,
379		Staking: StakingInterface<Balance = BalanceOf<T>, AccountId = T::AccountId>,
380		Delegation: DelegationInterface<Balance = BalanceOf<T>, AccountId = T::AccountId>
381			+ DelegationMigrator<Balance = BalanceOf<T>, AccountId = T::AccountId>,
382	> StakeStrategy for DelegateStake<T, Staking, Delegation>
383{
384	type Balance = BalanceOf<T>;
385	type AccountId = T::AccountId;
386	type CoreStaking = Staking;
387
388	fn strategy_type() -> StakeStrategyType {
389		StakeStrategyType::Delegate
390	}
391
392	fn transferable_balance(
393		pool_account: Pool<Self::AccountId>,
394		member_account: Member<Self::AccountId>,
395	) -> BalanceOf<T> {
396		Delegation::agent_transferable_balance(pool_account.clone().into())
397			// pool should always be an agent.
398			.defensive_unwrap_or_default()
399			.min(Delegation::delegator_balance(member_account.into()).unwrap_or_default())
400	}
401
402	fn total_balance(pool_account: Pool<Self::AccountId>) -> Option<BalanceOf<T>> {
403		Delegation::agent_balance(pool_account.into())
404	}
405
406	fn member_delegation_balance(member_account: Member<T::AccountId>) -> Option<BalanceOf<T>> {
407		Delegation::delegator_balance(member_account.into())
408	}
409
410	fn pledge_bond(
411		who: Member<T::AccountId>,
412		pool_account: Pool<Self::AccountId>,
413		reward_account: &Self::AccountId,
414		amount: BalanceOf<T>,
415		bond_type: BondType,
416	) -> DispatchResult {
417		match bond_type {
418			BondType::Create => {
419				// first delegation. Register agent first.
420				Delegation::register_agent(pool_account.clone().into(), reward_account)?;
421				Delegation::delegate(who.into(), pool_account.into(), amount)
422			},
423			BondType::Extra => {
424				// additional delegation
425				Delegation::delegate(who.into(), pool_account.into(), amount)
426			},
427		}
428	}
429
430	fn member_withdraw(
431		who: Member<Self::AccountId>,
432		pool_account: Pool<Self::AccountId>,
433		amount: BalanceOf<T>,
434		num_slashing_spans: u32,
435	) -> DispatchResult {
436		Delegation::withdraw_delegation(who.into(), pool_account.into(), amount, num_slashing_spans)
437	}
438
439	fn dissolve(pool_account: Pool<Self::AccountId>) -> DispatchResult {
440		Delegation::remove_agent(pool_account.into())
441	}
442
443	fn pending_slash(pool_account: Pool<Self::AccountId>) -> Self::Balance {
444		Delegation::pending_slash(pool_account.into()).defensive_unwrap_or_default()
445	}
446
447	fn member_slash(
448		who: Member<Self::AccountId>,
449		pool_account: Pool<Self::AccountId>,
450		amount: BalanceOf<T>,
451		maybe_reporter: Option<T::AccountId>,
452	) -> DispatchResult {
453		Delegation::delegator_slash(pool_account.into(), who.into(), amount, maybe_reporter)
454	}
455
456	fn migrate_nominator_to_agent(
457		pool: Pool<Self::AccountId>,
458		reward_account: &Self::AccountId,
459	) -> DispatchResult {
460		Delegation::migrate_nominator_to_agent(pool.into(), reward_account)
461	}
462
463	fn migrate_delegation(
464		pool: Pool<Self::AccountId>,
465		delegator: Member<Self::AccountId>,
466		value: Self::Balance,
467	) -> DispatchResult {
468		Delegation::migrate_delegation(pool.into(), delegator.into(), value)
469	}
470
471	#[cfg(feature = "runtime-benchmarks")]
472	fn remove_as_agent(pool: Pool<Self::AccountId>) {
473		Delegation::force_kill_agent(pool.into())
474	}
475}