lib.rs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. //! Staking handler module.
  2. //! Contains StakingHandler trait and its implementation - StakingManager.
  3. //! StakingHandler is responsible for staking logic in the Joystream runtime:
  4. //! https://joystream.gitbook.io/joystream-handbook/key-concepts/stakingmock.rs
  5. // Ensure we're `no_std` when compiling for Wasm.
  6. #![cfg_attr(not(feature = "std"), no_std)]
  7. use frame_support::dispatch::{DispatchError, DispatchResult};
  8. use frame_support::traits::{Currency, Get, LockIdentifier, LockableCurrency, WithdrawReasons};
  9. use sp_arithmetic::traits::Zero;
  10. use sp_std::marker::PhantomData;
  11. #[cfg(test)]
  12. mod mock;
  13. #[cfg(test)]
  14. mod test;
  15. /// Type alias for member id.
  16. pub type MemberId<T> = <T as membership::Trait>::MemberId;
  17. /// Balance alias for `balances` module.
  18. pub type BalanceOf<T> = <T as pallet_balances::Trait>::Balance;
  19. /// Defines abstract staking handler to manage user stakes for different activities
  20. /// like adding a proposal. Implementation should use built-in LockableCurrency
  21. /// and LockIdentifier to lock balance consistently with pallet_staking.
  22. pub trait StakingHandler<T: frame_system::Trait + membership::Trait + pallet_balances::Trait> {
  23. /// Locks the specified balance on the account using specific lock identifier.
  24. fn lock(account_id: &T::AccountId, amount: BalanceOf<T>);
  25. /// Removes the specified lock on the account.
  26. fn unlock(account_id: &T::AccountId);
  27. /// Slash the specified balance on the account using specific lock identifier.
  28. /// No limits, no actions on zero stake.
  29. /// If slashing balance greater than the existing stake - stake is slashed to zero.
  30. /// Returns actually slashed balance.
  31. fn slash(account_id: &T::AccountId, amount: Option<BalanceOf<T>>) -> BalanceOf<T>;
  32. /// Sets the new stake to a given amount.
  33. fn set_stake(account_id: &T::AccountId, new_stake: BalanceOf<T>) -> DispatchResult;
  34. /// Verifies that staking account bound to the member.
  35. fn is_member_staking_account(member_id: &MemberId<T>, account_id: &T::AccountId) -> bool;
  36. /// Verifies that there no conflicting stakes on the staking account.
  37. fn is_account_free_of_conflicting_stakes(account_id: &T::AccountId) -> bool;
  38. /// Verifies that staking account balance is sufficient for staking.
  39. /// During the balance check we should consider already locked stake. Effective balance to check
  40. /// is 'already locked funds' + 'usable funds'.
  41. fn is_enough_balance_for_stake(account_id: &T::AccountId, amount: BalanceOf<T>) -> bool;
  42. /// Returns the current stake on the account.
  43. fn current_stake(account_id: &T::AccountId) -> BalanceOf<T>;
  44. }
  45. /// Implementation of the StakingHandler.
  46. pub struct StakingManager<
  47. T: frame_system::Trait + membership::Trait + pallet_balances::Trait,
  48. LockId: Get<LockIdentifier>,
  49. > {
  50. trait_marker: PhantomData<T>,
  51. lock_id_marker: PhantomData<LockId>,
  52. }
  53. impl<
  54. T: frame_system::Trait + membership::Trait + pallet_balances::Trait,
  55. LockId: Get<LockIdentifier>,
  56. > StakingHandler<T> for StakingManager<T, LockId>
  57. {
  58. fn lock(account_id: &T::AccountId, amount: BalanceOf<T>) {
  59. <pallet_balances::Module<T>>::set_lock(
  60. LockId::get(),
  61. &account_id,
  62. amount,
  63. WithdrawReasons::all(),
  64. )
  65. }
  66. fn unlock(account_id: &T::AccountId) {
  67. T::Currency::remove_lock(LockId::get(), &account_id);
  68. }
  69. fn slash(account_id: &T::AccountId, amount: Option<BalanceOf<T>>) -> BalanceOf<T> {
  70. let locks = <pallet_balances::Module<T>>::locks(&account_id);
  71. let existing_lock = locks.iter().find(|lock| lock.id == LockId::get());
  72. let mut actually_slashed_balance = Default::default();
  73. if let Some(existing_lock) = existing_lock {
  74. Self::unlock(&account_id);
  75. let mut slashable_amount = existing_lock.amount;
  76. if let Some(amount) = amount {
  77. if existing_lock.amount > amount {
  78. let new_amount = existing_lock.amount - amount;
  79. Self::lock(&account_id, new_amount);
  80. slashable_amount = amount;
  81. }
  82. }
  83. let _ = <pallet_balances::Module<T>>::slash(&account_id, slashable_amount);
  84. actually_slashed_balance = slashable_amount
  85. }
  86. actually_slashed_balance
  87. }
  88. fn set_stake(account_id: &T::AccountId, new_stake: BalanceOf<T>) -> DispatchResult {
  89. let current_stake = Self::current_stake(account_id);
  90. //Unlock previous stake if its not zero.
  91. if current_stake > Zero::zero() {
  92. Self::unlock(account_id);
  93. }
  94. if !Self::is_enough_balance_for_stake(account_id, new_stake) {
  95. //Restore previous stake if its not zero.
  96. if current_stake > Zero::zero() {
  97. Self::lock(account_id, current_stake);
  98. }
  99. return Err(DispatchError::Other("Not enough balance for a new stake."));
  100. }
  101. Self::lock(account_id, new_stake);
  102. Ok(())
  103. }
  104. // Membership support for staking accounts required.
  105. fn is_member_staking_account(_member_id: &MemberId<T>, _account_id: &T::AccountId) -> bool {
  106. true
  107. }
  108. fn is_account_free_of_conflicting_stakes(account_id: &T::AccountId) -> bool {
  109. let locks = <pallet_balances::Module<T>>::locks(&account_id);
  110. let existing_lock = locks.iter().find(|lock| lock.id == LockId::get());
  111. existing_lock.is_none()
  112. }
  113. fn is_enough_balance_for_stake(account_id: &T::AccountId, amount: BalanceOf<T>) -> bool {
  114. <pallet_balances::Module<T>>::usable_balance(account_id) >= amount
  115. }
  116. fn current_stake(account_id: &T::AccountId) -> BalanceOf<T> {
  117. let locks = <pallet_balances::Module<T>>::locks(&account_id);
  118. let existing_lock = locks.iter().find(|lock| lock.id == LockId::get());
  119. existing_lock.map_or(Zero::zero(), |lock| lock.amount)
  120. }
  121. }