Browse Source

staking module: add slash_immediate function

Mokhtar Naamani 5 years ago
parent
commit
75fa0415de

+ 6 - 0
runtime-modules/stake/src/errors.rs

@@ -44,6 +44,12 @@ pub enum DecreasingStakeError {
     CannotDecreaseStakeWhileUnstaking,
 }
 
+#[derive(Debug, Eq, PartialEq)]
+pub enum ImmediateSlashingError {
+    NotStaked,
+    SlashAmountShouldBeGreaterThanZero,
+}
+
 #[derive(Debug, Eq, PartialEq)]
 pub enum InitiateSlashingError {
     NotStaked,

+ 99 - 2
runtime-modules/stake/src/lib.rs

@@ -307,7 +307,7 @@ pub struct Stake<BlockNumber, Balance, SlashId: Ord> {
 
 impl<BlockNumber, Balance, SlashId> Stake<BlockNumber, Balance, SlashId>
 where
-    BlockNumber: Copy + SimpleArithmetic,
+    BlockNumber: Copy + SimpleArithmetic + Zero,
     Balance: Copy + SimpleArithmetic,
     SlashId: Copy + Ord + Zero + One,
 {
@@ -408,6 +408,38 @@ where
         }
     }
 
+    fn slash_immediate(
+        &mut self,
+        slash_amount: Balance,
+        minimum_balance: Balance,
+    ) -> Result<Balance, ImmediateSlashingError> {
+        ensure!(
+            slash_amount > Zero::zero(),
+            ImmediateSlashingError::SlashAmountShouldBeGreaterThanZero
+        );
+
+        match self.staking_status {
+            StakingStatus::Staked(ref mut staked_state) => {
+                // irrespective of wether we are unstaking or not, slash!
+
+                let actually_slashed = staked_state.apply_slash(
+                    Slash {
+                        slash_amount,
+                        // below values are irrelevant
+                        is_active: true,
+                        blocks_remaining_in_active_period_for_slashing: BlockNumber::zero(),
+                        started_at_block: BlockNumber::zero(),
+                    },
+                    minimum_balance,
+                );
+
+                Ok(actually_slashed)
+            }
+            // can't slash if not staked
+            _ => Err(ImmediateSlashingError::NotStaked),
+        }
+    }
+
     fn initiate_slashing(
         &mut self,
         slash_amount: Balance,
@@ -680,6 +712,12 @@ where
     }
 }
 
+pub struct SlashImmediateOutcome<Balance, Imbalance> {
+    caused_unstake: bool,
+    slashed_amount: Imbalance,
+    remaining_stake: Balance,
+}
+
 decl_storage! {
     trait Store for Module<T: Trait> as StakePool {
         /// Maps identifiers to a stake.
@@ -938,7 +976,66 @@ impl<T: Trait> Module<T> {
         Ok(staked_amount)
     }
 
-    /// Initiate a new slashing of a staked stake.
+    /// Slashes a stake with immediate effect, returns actual slashed amount as an imbalance.
+    /// If attempt to slash more than staked amount, actual slashed amount may be less than requested amount to slash.
+    /// Slashing adheres to system minimum balance, so if slashing results in amount at staked going below the
+    /// minimum balance, the entire stake will be slashed, and the state of the stake will change to Unstaked.
+    pub fn slash_immediate(
+        stake_id: &T::StakeId,
+        slash_amount: BalanceOf<T>,
+    ) -> Result<
+        SlashImmediateOutcome<BalanceOf<T>, NegativeImbalance<T>>,
+        StakeActionError<ImmediateSlashingError>,
+    > {
+        let mut stake = ensure_stake_exists!(T, stake_id, StakeActionError::StakeNotFound)?;
+
+        // Get amount at stake before slashing to be used in unstaked event trigger
+        let staked_amount_before_slash =
+            ensure_staked_amount!(stake, ImmediateSlashingError::NotStaked);
+
+        let actually_slashed_amount =
+            stake.slash_immediate(slash_amount, T::Currency::minimum_balance())?;
+
+        // Remove the slashed amount from the pool
+        let slashed_imbalance = Self::withdraw_funds_from_stake_pool(actually_slashed_amount);
+
+        // What remains at stake?
+        let staked_amount_after_slash =
+            ensure_staked_amount!(stake, ImmediateSlashingError::NotStaked);
+
+        if staked_amount_after_slash == BalanceOf::<T>::zero() {
+            // Set NotStaked status
+            stake.staking_status = StakingStatus::NotStaked;
+
+            <Stakes<T>>::insert(stake_id, stake);
+
+            // Trigger the event handler but since we are returning the imbalance in the result
+            // we pass a zero negative imbalance to the event handler to indicate someone else
+            // has already handled the unstaked amount.
+            let _ = T::StakingEventsHandler::unstaked(
+                &stake_id,
+                staked_amount_before_slash,
+                NegativeImbalance::<T>::zero(),
+            );
+
+            Ok(SlashImmediateOutcome {
+                caused_unstake: true,
+                slashed_amount: slashed_imbalance,
+                remaining_stake: BalanceOf::<T>::zero(),
+            })
+        } else {
+            // Update the state of the stake
+            <Stakes<T>>::insert(stake_id, stake);
+
+            Ok(SlashImmediateOutcome {
+                caused_unstake: false,
+                slashed_amount: slashed_imbalance,
+                remaining_stake: staked_amount_after_slash,
+            })
+        }
+    }
+
+    /// Initiate a new slashing of a staked stake. Slashing begins at next block.
     pub fn initiate_slashing(
         stake_id: &T::StakeId,
         slash_amount: BalanceOf<T>,

+ 12 - 0
runtime-modules/stake/src/macroes.rs

@@ -17,3 +17,15 @@ macro_rules! ensure_stake_exists {
         ensure_map_has_mapping_with_key!(Stakes, $runtime_trait, $stake_id, $error)
     }};
 }
+
+#[macro_export]
+macro_rules! ensure_staked_amount {
+    ($stake:expr, $error:expr) => {{
+        match $stake.staking_status {
+            StakingStatus::Staked(ref staked_state) => staked_state.staked_amount,
+            _ => {
+                return Err(StakeActionError::Error(ImmediateSlashingError::NotStaked));
+            }
+        }
+    }};
+}