1use crate::*;
19use frame_support::traits::tokens::{Fortitude::Polite, Preservation::Expendable};
20use sp_staking::{Agent, DelegationInterface, DelegationMigrator, Delegator};
21
22#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq)]
26pub enum StakeStrategyType {
27 Transfer,
32 Delegate,
34}
35
36#[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#[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
80pub 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 fn strategy_type() -> StakeStrategyType;
92
93 fn bonding_duration() -> EraIndex {
95 Self::CoreStaking::bonding_duration()
96 }
97
98 fn current_era() -> EraIndex {
100 Self::CoreStaking::current_era()
101 }
102
103 fn minimum_nominator_bond() -> Self::Balance {
105 Self::CoreStaking::minimum_nominator_bond()
106 }
107
108 fn transferable_balance(
112 pool_account: Pool<Self::AccountId>,
113 member_account: Member<Self::AccountId>,
114 ) -> Self::Balance;
115
116 fn total_balance(pool_account: Pool<Self::AccountId>) -> Option<Self::Balance>;
118
119 fn member_delegation_balance(member_account: Member<Self::AccountId>) -> Option<Self::Balance>;
121
122 fn active_stake(pool_account: Pool<Self::AccountId>) -> Self::Balance {
124 Self::CoreStaking::active_stake(&pool_account.0).unwrap_or_default()
125 }
126
127 fn total_stake(pool_account: Pool<Self::AccountId>) -> Self::Balance {
129 Self::CoreStaking::total_stake(&pool_account.0).unwrap_or_default()
130 }
131
132 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 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 fn chill(pool_account: Pool<Self::AccountId>) -> DispatchResult {
153 Self::CoreStaking::chill(&pool_account.0)
154 }
155
156 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 fn unbond(pool_account: Pool<Self::AccountId>, amount: Self::Balance) -> DispatchResult {
168 Self::CoreStaking::unbond(&pool_account.0, amount)
169 }
170
171 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 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 fn dissolve(pool_account: Pool<Self::AccountId>) -> DispatchResult;
189
190 fn pending_slash(pool_account: Pool<Self::AccountId>) -> Self::Balance;
192
193 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 fn migrate_nominator_to_agent(
206 pool_account: Pool<Self::AccountId>,
207 reward_account: &Self::AccountId,
208 ) -> DispatchResult;
209
210 fn migrate_delegation(
219 pool: Pool<Self::AccountId>,
220 delegator: Member<Self::AccountId>,
221 value: Self::Balance,
222 ) -> DispatchResult;
223
224 #[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 #[cfg(feature = "runtime-benchmarks")]
235 fn remove_as_agent(_pool: Pool<Self::AccountId>) {
236 }
238}
239
240#[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 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 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 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 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 T::Currency::set_balance(&pool_account.get(), Zero::zero());
323 Ok(())
324 }
325
326 fn pending_slash(_: Pool<Self::AccountId>) -> Self::Balance {
327 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
356pub 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 .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 Delegation::register_agent(pool_account.clone().into(), reward_account)?;
415 Delegation::delegate(who.into(), pool_account.into(), amount)
416 },
417 BondType::Extra => {
418 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}