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