pallet_domains/
staking.rs

1//! Staking for domains
2
3#[cfg(not(feature = "std"))]
4extern crate alloc;
5
6use crate::bundle_storage_fund::{self, deposit_reserve_for_storage_fund};
7use crate::pallet::{
8    AllowedDefaultSharePriceEpoch, Deposits, DomainRegistry, DomainStakingSummary,
9    HeadDomainNumber, NextOperatorId, NominatorCount, OperatorIdOwner, Operators, PendingSlashes,
10    PendingStakingOperationCount, Withdrawals,
11};
12use crate::staking_epoch::{mint_funds, mint_into_treasury};
13use crate::{
14    BalanceOf, Config, DepositOnHold, DomainBlockNumberFor, Event, HoldIdentifier, NominatorId,
15    OperatorEpochSharePrice, Pallet, ReceiptHashFor, SlashedReason,
16};
17use frame_support::traits::fungible::{Inspect, MutateHold};
18use frame_support::traits::tokens::{Fortitude, Precision, Preservation};
19use frame_support::{ensure, PalletError};
20use frame_system::pallet_prelude::BlockNumberFor;
21use parity_scale_codec::{Decode, Encode};
22use scale_info::TypeInfo;
23use sp_core::{sr25519, Get};
24use sp_domains::{DomainId, EpochIndex, OperatorId, OperatorPublicKey, OperatorRewardSource};
25use sp_runtime::traits::{CheckedAdd, CheckedSub, Zero};
26use sp_runtime::{Perbill, Percent, Perquintill, Saturating};
27use sp_std::collections::btree_map::BTreeMap;
28use sp_std::collections::btree_set::BTreeSet;
29use sp_std::collections::vec_deque::VecDeque;
30use sp_std::vec::IntoIter;
31
32/// A nominators deposit.
33#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Default)]
34pub(crate) struct Deposit<Share: Copy, Balance: Copy> {
35    pub(crate) known: KnownDeposit<Share, Balance>,
36    pub(crate) pending: Option<PendingDeposit<Balance>>,
37}
38
39/// A share price is parts per billion of shares/ssc.
40/// Note: Shares must always be equal to or lower than ssc.
41#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Default)]
42pub struct SharePrice(Perbill);
43
44impl SharePrice {
45    /// Creates a new instance of share price from shares and stake.
46    pub(crate) fn new<T: Config>(shares: T::Share, stake: BalanceOf<T>) -> Self {
47        SharePrice(if shares.is_zero() || stake.is_zero() {
48            Perbill::one()
49        } else {
50            Perbill::from_rational(shares, stake.into())
51        })
52    }
53
54    /// Converts stake to shares based on the share price
55    pub(crate) fn stake_to_shares<T: Config>(&self, stake: BalanceOf<T>) -> T::Share {
56        if self.0.is_one() {
57            stake.into()
58        } else {
59            self.0.mul_floor(stake).into()
60        }
61    }
62
63    /// Converts shares to stake based on the share price
64    pub(crate) fn shares_to_stake<T: Config>(&self, shares: T::Share) -> BalanceOf<T> {
65        if self.0.is_one() {
66            shares.into()
67        } else {
68            self.0.saturating_reciprocal_mul_floor(shares.into())
69        }
70    }
71}
72
73/// Unique epoch identifier across all domains. A combination of Domain and its epoch.
74#[derive(TypeInfo, Debug, Encode, Decode, Copy, Clone, PartialEq, Eq)]
75pub struct DomainEpoch(DomainId, EpochIndex);
76
77impl DomainEpoch {
78    pub(crate) fn deconstruct(self) -> (DomainId, EpochIndex) {
79        (self.0, self.1)
80    }
81}
82
83impl From<(DomainId, EpochIndex)> for DomainEpoch {
84    fn from((domain_id, epoch_idx): (DomainId, EpochIndex)) -> Self {
85        Self(domain_id, epoch_idx)
86    }
87}
88
89pub struct NewDeposit<Balance> {
90    pub(crate) staking: Balance,
91    pub(crate) storage_fee_deposit: Balance,
92}
93
94/// A nominator's shares against their deposits to given operator pool.
95#[derive(TypeInfo, Debug, Encode, Decode, Copy, Clone, PartialEq, Eq, Default)]
96pub(crate) struct KnownDeposit<Share: Copy, Balance: Copy> {
97    pub(crate) shares: Share,
98    pub(crate) storage_fee_deposit: Balance,
99}
100
101/// A nominators pending deposit in SSC that needs to be converted to shares once domain epoch is complete.
102#[derive(TypeInfo, Debug, Encode, Decode, Copy, Clone, PartialEq, Eq)]
103pub(crate) struct PendingDeposit<Balance: Copy> {
104    pub(crate) effective_domain_epoch: DomainEpoch,
105    pub(crate) amount: Balance,
106    pub(crate) storage_fee_deposit: Balance,
107}
108
109impl<Balance: Copy + CheckedAdd> PendingDeposit<Balance> {
110    fn total(&self) -> Result<Balance, Error> {
111        self.amount
112            .checked_add(&self.storage_fee_deposit)
113            .ok_or(Error::BalanceOverflow)
114    }
115}
116
117/// A nominator's withdrawal from a given operator pool.
118#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Default)]
119pub(crate) struct Withdrawal<Balance, Share, DomainBlockNumber> {
120    /// Total withdrawal amount requested by the nominator that are in unlocking state excluding withdrawal
121    /// in shares and the storage fee
122    pub(crate) total_withdrawal_amount: Balance,
123    /// Total amount of storage fee on withdraw (including withdrawal in shares)
124    pub(crate) total_storage_fee_withdrawal: Balance,
125    /// Individual withdrawal amounts with their unlocking block for a given domain
126    pub(crate) withdrawals: VecDeque<WithdrawalInBalance<DomainBlockNumber, Balance>>,
127    /// Withdrawal that was initiated by nominator and not converted to balance due to
128    /// unfinished domain epoch.
129    pub(crate) withdrawal_in_shares: Option<WithdrawalInShares<DomainBlockNumber, Share, Balance>>,
130}
131
132#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
133pub(crate) struct WithdrawalInBalance<DomainBlockNumber, Balance> {
134    pub(crate) unlock_at_confirmed_domain_block_number: DomainBlockNumber,
135    pub(crate) amount_to_unlock: Balance,
136    pub(crate) storage_fee_refund: Balance,
137}
138
139#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
140pub(crate) struct WithdrawalInShares<DomainBlockNumber, Share, Balance> {
141    pub(crate) domain_epoch: DomainEpoch,
142    pub(crate) unlock_at_confirmed_domain_block_number: DomainBlockNumber,
143    pub(crate) shares: Share,
144    pub(crate) storage_fee_refund: Balance,
145}
146
147#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
148pub struct OperatorDeregisteredInfo<DomainBlockNumber> {
149    pub domain_epoch: DomainEpoch,
150    pub unlock_at_confirmed_domain_block_number: DomainBlockNumber,
151}
152
153impl<DomainBlockNumber> From<(DomainId, EpochIndex, DomainBlockNumber)>
154    for OperatorDeregisteredInfo<DomainBlockNumber>
155{
156    fn from(value: (DomainId, EpochIndex, DomainBlockNumber)) -> Self {
157        OperatorDeregisteredInfo {
158            domain_epoch: (value.0, value.1).into(),
159            unlock_at_confirmed_domain_block_number: value.2,
160        }
161    }
162}
163
164/// Type that represents an operator status.
165#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
166pub enum OperatorStatus<DomainBlockNumber> {
167    Registered,
168    /// De-registered at given domain epoch.
169    Deregistered(OperatorDeregisteredInfo<DomainBlockNumber>),
170    Slashed,
171    PendingSlash,
172}
173
174/// Type that represents an operator details.
175#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
176pub struct Operator<Balance, Share, DomainBlockNumber> {
177    pub signing_key: OperatorPublicKey,
178    pub current_domain_id: DomainId,
179    pub next_domain_id: DomainId,
180    pub minimum_nominator_stake: Balance,
181    pub nomination_tax: Percent,
182    /// Total active stake of combined nominators under this operator.
183    pub current_total_stake: Balance,
184    /// Total shares of all the nominators under this operator.
185    pub current_total_shares: Share,
186    /// The status of the operator, it may be stale due to the `OperatorStatus::PendingSlash` is
187    /// not assigned to this field directly, thus MUST use the `status()` method to query the status
188    /// instead.
189    partial_status: OperatorStatus<DomainBlockNumber>,
190    /// Total deposits during the previous epoch
191    pub deposits_in_epoch: Balance,
192    /// Total withdrew shares during the previous epoch
193    pub withdrawals_in_epoch: Share,
194    /// Total balance deposited to the bundle storage fund
195    pub total_storage_fee_deposit: Balance,
196}
197
198impl<Balance, Share, DomainBlockNumber> Operator<Balance, Share, DomainBlockNumber> {
199    pub fn status<T: Config>(&self, operator_id: OperatorId) -> &OperatorStatus<DomainBlockNumber> {
200        if matches!(self.partial_status, OperatorStatus::Slashed) {
201            &OperatorStatus::Slashed
202        } else if Pallet::<T>::is_operator_pending_to_slash(self.current_domain_id, operator_id) {
203            &OperatorStatus::PendingSlash
204        } else {
205            &self.partial_status
206        }
207    }
208
209    pub fn update_status(&mut self, new_status: OperatorStatus<DomainBlockNumber>) {
210        self.partial_status = new_status;
211    }
212}
213
214#[cfg(test)]
215impl<Balance: Zero, Share: Zero, DomainBlockNumber> Operator<Balance, Share, DomainBlockNumber> {
216    pub(crate) fn dummy(
217        domain_id: DomainId,
218        signing_key: OperatorPublicKey,
219        minimum_nominator_stake: Balance,
220    ) -> Self {
221        Operator {
222            signing_key,
223            current_domain_id: domain_id,
224            next_domain_id: domain_id,
225            minimum_nominator_stake,
226            nomination_tax: Default::default(),
227            current_total_stake: Zero::zero(),
228            current_total_shares: Zero::zero(),
229            partial_status: OperatorStatus::Registered,
230            deposits_in_epoch: Zero::zero(),
231            withdrawals_in_epoch: Zero::zero(),
232            total_storage_fee_deposit: Zero::zero(),
233        }
234    }
235}
236
237#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
238pub struct StakingSummary<OperatorId, Balance> {
239    /// Current epoch index for the domain.
240    pub current_epoch_index: EpochIndex,
241    /// Total active stake for the current epoch.
242    pub current_total_stake: Balance,
243    /// Current operators for this epoch
244    pub current_operators: BTreeMap<OperatorId, Balance>,
245    /// Operators for the next epoch.
246    pub next_operators: BTreeSet<OperatorId>,
247    /// Operator's current Epoch rewards
248    pub current_epoch_rewards: BTreeMap<OperatorId, Balance>,
249}
250
251#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
252pub struct OperatorConfig<Balance> {
253    pub signing_key: OperatorPublicKey,
254    pub minimum_nominator_stake: Balance,
255    pub nomination_tax: Percent,
256}
257
258#[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
259pub enum Error {
260    MaximumOperatorId,
261    DomainNotInitialized,
262    PendingOperatorSwitch,
263    InsufficientBalance,
264    InsufficientShares,
265    ZeroWithdraw,
266    BalanceFreeze,
267    MinimumOperatorStake,
268    UnknownOperator,
269    MinimumNominatorStake,
270    BalanceOverflow,
271    BalanceUnderflow,
272    NotOperatorOwner,
273    OperatorNotRegistered,
274    UnknownNominator,
275    MissingOperatorOwner,
276    MintBalance,
277    BlockNumberOverflow,
278    RemoveLock,
279    EpochOverflow,
280    ShareUnderflow,
281    ShareOverflow,
282    TooManyPendingStakingOperation,
283    OperatorNotAllowed,
284    InvalidOperatorSigningKey,
285    MissingOperatorEpochSharePrice,
286    MissingWithdrawal,
287    EpochNotComplete,
288    UnlockPeriodNotComplete,
289    OperatorNotDeregistered,
290    BundleStorageFund(bundle_storage_fund::Error),
291    UnconfirmedER,
292    TooManyWithdrawals,
293    ZeroDeposit,
294}
295
296// Increase `PendingStakingOperationCount` by one and check if the `MaxPendingStakingOperation`
297// limit is exceeded
298fn note_pending_staking_operation<T: Config>(domain_id: DomainId) -> Result<(), Error> {
299    let pending_op_count = PendingStakingOperationCount::<T>::get(domain_id);
300
301    ensure!(
302        pending_op_count < T::MaxPendingStakingOperation::get(),
303        Error::TooManyPendingStakingOperation
304    );
305
306    PendingStakingOperationCount::<T>::set(domain_id, pending_op_count.saturating_add(1));
307
308    Ok(())
309}
310
311pub fn do_register_operator<T: Config>(
312    operator_owner: T::AccountId,
313    domain_id: DomainId,
314    amount: BalanceOf<T>,
315    config: OperatorConfig<BalanceOf<T>>,
316) -> Result<(OperatorId, EpochIndex), Error> {
317    note_pending_staking_operation::<T>(domain_id)?;
318
319    DomainStakingSummary::<T>::try_mutate(domain_id, |maybe_domain_stake_summary| {
320        ensure!(
321            config.signing_key != OperatorPublicKey::from(sr25519::Public::default()),
322            Error::InvalidOperatorSigningKey
323        );
324
325        ensure!(
326            config.minimum_nominator_stake >= T::MinNominatorStake::get(),
327            Error::MinimumNominatorStake
328        );
329
330        let domain_obj = DomainRegistry::<T>::get(domain_id).ok_or(Error::DomainNotInitialized)?;
331        ensure!(
332            domain_obj
333                .domain_config
334                .operator_allow_list
335                .is_operator_allowed(&operator_owner),
336            Error::OperatorNotAllowed
337        );
338
339        let operator_id = NextOperatorId::<T>::get();
340        let next_operator_id = operator_id.checked_add(1).ok_or(Error::MaximumOperatorId)?;
341        NextOperatorId::<T>::set(next_operator_id);
342
343        OperatorIdOwner::<T>::insert(operator_id, operator_owner.clone());
344
345        // reserve stake balance
346        ensure!(
347            amount >= T::MinOperatorStake::get(),
348            Error::MinimumOperatorStake
349        );
350
351        let new_deposit =
352            deposit_reserve_for_storage_fund::<T>(operator_id, &operator_owner, amount)
353                .map_err(Error::BundleStorageFund)?;
354
355        hold_deposit::<T>(&operator_owner, operator_id, new_deposit.staking)?;
356
357        let domain_stake_summary = maybe_domain_stake_summary
358            .as_mut()
359            .ok_or(Error::DomainNotInitialized)?;
360
361        let OperatorConfig {
362            signing_key,
363            minimum_nominator_stake,
364            nomination_tax,
365        } = config;
366
367        let operator = Operator {
368            signing_key: signing_key.clone(),
369            current_domain_id: domain_id,
370            next_domain_id: domain_id,
371            minimum_nominator_stake,
372            nomination_tax,
373            current_total_stake: Zero::zero(),
374            current_total_shares: Zero::zero(),
375            partial_status: OperatorStatus::Registered,
376            // sum total deposits added during this epoch.
377            deposits_in_epoch: new_deposit.staking,
378            withdrawals_in_epoch: Zero::zero(),
379            total_storage_fee_deposit: new_deposit.storage_fee_deposit,
380        };
381        Operators::<T>::insert(operator_id, operator);
382        // update stake summary to include new operator for next epoch
383        domain_stake_summary.next_operators.insert(operator_id);
384        // update pending transfers
385        let current_domain_epoch = (domain_id, domain_stake_summary.current_epoch_index).into();
386        do_calculate_previous_epoch_deposit_shares_and_add_new_deposit::<T>(
387            operator_id,
388            operator_owner,
389            current_domain_epoch,
390            new_deposit,
391        )?;
392
393        Ok((operator_id, domain_stake_summary.current_epoch_index))
394    })
395}
396
397pub(crate) struct DepositInfo<Balance> {
398    /// If this nominator is currently nominating the operator.
399    /// If there are multiple deposits in same epoch, still returns true
400    nominating: bool,
401    /// Final current deposit in this epoch.
402    total_deposit: Balance,
403    /// If this is the first deposit in this epoch.
404    first_deposit_in_epoch: bool,
405}
406
407/// Calculates shares for any pending deposit for previous epoch using the the epoch share price and
408/// then create a new pending deposit in the current epoch.
409/// If there is a pending deposit for the current epoch, then simply increment the amount.
410/// Returns updated deposit info
411pub(crate) fn do_calculate_previous_epoch_deposit_shares_and_add_new_deposit<T: Config>(
412    operator_id: OperatorId,
413    nominator_id: NominatorId<T>,
414    current_domain_epoch: DomainEpoch,
415    new_deposit: NewDeposit<BalanceOf<T>>,
416) -> Result<DepositInfo<BalanceOf<T>>, Error> {
417    Deposits::<T>::try_mutate(operator_id, nominator_id, |maybe_deposit| {
418        let mut deposit = maybe_deposit.take().unwrap_or_default();
419        do_convert_previous_epoch_deposits::<T>(operator_id, &mut deposit, current_domain_epoch.1)?;
420
421        // add or create new pending deposit
422        let (pending_deposit, deposit_info) = match deposit.pending {
423            None => {
424                let pending_deposit = PendingDeposit {
425                    effective_domain_epoch: current_domain_epoch,
426                    amount: new_deposit.staking,
427                    storage_fee_deposit: new_deposit.storage_fee_deposit,
428                };
429
430                let deposit_info = DepositInfo {
431                    nominating: !deposit.known.shares.is_zero(),
432                    total_deposit: pending_deposit.total()?,
433                    first_deposit_in_epoch: true,
434                };
435
436                (pending_deposit, deposit_info)
437            }
438            Some(pending_deposit) => {
439                let pending_deposit = PendingDeposit {
440                    effective_domain_epoch: current_domain_epoch,
441                    amount: pending_deposit
442                        .amount
443                        .checked_add(&new_deposit.staking)
444                        .ok_or(Error::BalanceOverflow)?,
445                    storage_fee_deposit: pending_deposit
446                        .storage_fee_deposit
447                        .checked_add(&new_deposit.storage_fee_deposit)
448                        .ok_or(Error::BalanceOverflow)?,
449                };
450
451                let deposit_info = DepositInfo {
452                    nominating: !deposit.known.shares.is_zero(),
453                    total_deposit: pending_deposit.total()?,
454                    first_deposit_in_epoch: false,
455                };
456
457                (pending_deposit, deposit_info)
458            }
459        };
460
461        deposit.pending = Some(pending_deposit);
462        *maybe_deposit = Some(deposit);
463        Ok(deposit_info)
464    })
465}
466
467pub(crate) fn do_convert_previous_epoch_deposits<T: Config>(
468    operator_id: OperatorId,
469    deposit: &mut Deposit<T::Share, BalanceOf<T>>,
470    current_domain_epoch_index: EpochIndex,
471) -> Result<(), Error> {
472    // if it is one of the previous domain epoch, then calculate shares for the epoch and update known deposit
473    let epoch_share_price = match deposit.pending {
474        None => return Ok(()),
475        Some(pending_deposit) => {
476            // TODO: remove after all zero-amount deposit are processed on Taurus
477            // Due to https://github.com/autonomys/subspace/issues/3459 there may be zero-amount
478            // deposit being accepted, in this case, we simply remove it from the state.
479            if pending_deposit.amount.is_zero() && pending_deposit.storage_fee_deposit.is_zero() {
480                deposit.pending.take();
481                return Ok(());
482            }
483
484            match OperatorEpochSharePrice::<T>::get(
485                operator_id,
486                pending_deposit.effective_domain_epoch,
487            ) {
488                Some(p) => p,
489                None => {
490                    ensure!(
491                        pending_deposit.effective_domain_epoch.1 >= current_domain_epoch_index,
492                        Error::MissingOperatorEpochSharePrice
493                    );
494                    return Ok(());
495                }
496            }
497        }
498    };
499
500    if let Some(PendingDeposit {
501        amount,
502        storage_fee_deposit,
503        ..
504    }) = deposit.pending.take()
505    {
506        let new_shares = epoch_share_price.stake_to_shares::<T>(amount);
507        deposit.known.shares = deposit
508            .known
509            .shares
510            .checked_add(&new_shares)
511            .ok_or(Error::ShareOverflow)?;
512        deposit.known.storage_fee_deposit = deposit
513            .known
514            .storage_fee_deposit
515            .checked_add(&storage_fee_deposit)
516            .ok_or(Error::BalanceOverflow)?;
517    }
518
519    Ok(())
520}
521
522/// TODO: remove after migration are done on Taurus
523pub(crate) fn allowed_default_share_price<T: Config>(
524    operator_id: OperatorId,
525    domain_epoch_index: EpochIndex,
526) -> Option<SharePrice> {
527    let DomainEpoch(allowed_domain_id, allowed_epoch_index) =
528        AllowedDefaultSharePriceEpoch::<T>::get()?;
529    let operator = Operators::<T>::get(operator_id)?;
530
531    // The default share price is only allowed if the requested `domain_epoch_index` is happen
532    // before (or is the same as) the `AllowedDefaultSharePriceEpoch`
533    if allowed_domain_id == operator.current_domain_id && allowed_epoch_index >= domain_epoch_index
534    {
535        let domain_stake_summary = DomainStakingSummary::<T>::get(operator.current_domain_id)?;
536        return Some(current_share_price::<T>(
537            operator_id,
538            &operator,
539            &domain_stake_summary,
540        ));
541    }
542    None
543}
544
545/// Converts any epoch withdrawals into balance using the operator epoch share price.
546///
547/// If there is withdrawal happened in the current epoch (thus share price is unavailable),
548/// this will be no-op. If there is withdrawal happened in the previous epoch and the share
549/// price is unavailable, `MissingOperatorEpochSharePrice` error will be return.
550///
551/// NOTE: On Taurus, there may be share price missing unexpectly due to https://github.com/autonomys/subspace/issues/3459
552/// in order to convert these withdrawals and unlock them, we will use the current share price
553/// as default value.
554pub(crate) fn do_convert_previous_epoch_withdrawal<T: Config>(
555    operator_id: OperatorId,
556    withdrawal: &mut Withdrawal<BalanceOf<T>, T::Share, DomainBlockNumberFor<T>>,
557    current_domain_epoch_index: EpochIndex,
558) -> Result<(), Error> {
559    let epoch_share_price = match withdrawal.withdrawal_in_shares.as_ref() {
560        None => return Ok(()),
561        Some(withdraw) => {
562            // TODO: remove after all zero-amount withdrawal are processed on Taurus
563            // Due to https://github.com/autonomys/subspace/issues/3459 there may be zero-amount
564            // withdrawal being accepted, in this case, we simply remove it from the state.
565            if withdraw.shares.is_zero() && withdraw.storage_fee_refund.is_zero() {
566                withdrawal.withdrawal_in_shares.take();
567                return Ok(());
568            }
569
570            // `withdraw.domain_epoch` is not end yet so the share price won't be available
571            if withdraw.domain_epoch.1 >= current_domain_epoch_index {
572                return Ok(());
573            }
574
575            match OperatorEpochSharePrice::<T>::get(operator_id, withdraw.domain_epoch) {
576                Some(p) => p,
577                None => {
578                    match allowed_default_share_price::<T>(operator_id, withdraw.domain_epoch.1) {
579                        Some(p) => p,
580                        None => return Err(Error::MissingOperatorEpochSharePrice),
581                    }
582                }
583            }
584        }
585    };
586
587    if let Some(WithdrawalInShares {
588        unlock_at_confirmed_domain_block_number,
589        shares,
590        storage_fee_refund,
591        domain_epoch: _,
592    }) = withdrawal.withdrawal_in_shares.take()
593    {
594        let withdrawal_amount = epoch_share_price.shares_to_stake::<T>(shares);
595        withdrawal.total_withdrawal_amount = withdrawal
596            .total_withdrawal_amount
597            .checked_add(&withdrawal_amount)
598            .ok_or(Error::BalanceOverflow)?;
599
600        let withdraw_in_balance = WithdrawalInBalance {
601            unlock_at_confirmed_domain_block_number,
602            amount_to_unlock: withdrawal_amount,
603            storage_fee_refund,
604        };
605        withdrawal.withdrawals.push_back(withdraw_in_balance);
606    }
607
608    Ok(())
609}
610
611pub(crate) fn do_nominate_operator<T: Config>(
612    operator_id: OperatorId,
613    nominator_id: T::AccountId,
614    amount: BalanceOf<T>,
615) -> Result<(), Error> {
616    ensure!(!amount.is_zero(), Error::ZeroDeposit);
617
618    Operators::<T>::try_mutate(operator_id, |maybe_operator| {
619        let operator = maybe_operator.as_mut().ok_or(Error::UnknownOperator)?;
620
621        ensure!(
622            *operator.status::<T>(operator_id) == OperatorStatus::Registered,
623            Error::OperatorNotRegistered
624        );
625
626        // If the this is the first staking request of this operator `note_pending_staking_operation` for it
627        if operator.deposits_in_epoch.is_zero() && operator.withdrawals_in_epoch.is_zero() {
628            note_pending_staking_operation::<T>(operator.current_domain_id)?;
629        }
630
631        let domain_stake_summary = DomainStakingSummary::<T>::get(operator.current_domain_id)
632            .ok_or(Error::DomainNotInitialized)?;
633
634        // Reserve for the bundle storage fund
635        let new_deposit = deposit_reserve_for_storage_fund::<T>(operator_id, &nominator_id, amount)
636            .map_err(Error::BundleStorageFund)?;
637
638        hold_deposit::<T>(&nominator_id, operator_id, new_deposit.staking)?;
639        Pallet::<T>::deposit_event(Event::OperatorNominated {
640            operator_id,
641            nominator_id: nominator_id.clone(),
642            amount: new_deposit.staking,
643        });
644
645        // increment total deposit for operator pool within this epoch
646        operator.deposits_in_epoch = operator
647            .deposits_in_epoch
648            .checked_add(&new_deposit.staking)
649            .ok_or(Error::BalanceOverflow)?;
650
651        // Increase total storage fee deposit as there is new deposit to the storage fund
652        operator.total_storage_fee_deposit = operator
653            .total_storage_fee_deposit
654            .checked_add(&new_deposit.storage_fee_deposit)
655            .ok_or(Error::BalanceOverflow)?;
656
657        let current_domain_epoch = (
658            operator.current_domain_id,
659            domain_stake_summary.current_epoch_index,
660        )
661            .into();
662
663        let DepositInfo {
664            nominating,
665            total_deposit,
666            first_deposit_in_epoch,
667        } = do_calculate_previous_epoch_deposit_shares_and_add_new_deposit::<T>(
668            operator_id,
669            nominator_id,
670            current_domain_epoch,
671            new_deposit,
672        )?;
673
674        // if not a nominator, then ensure
675        // - amount >= operator's minimum nominator stake amount.
676        // - nominator count does not exceed max nominators.
677        // - if first nomination, then increment the nominator count.
678        if !nominating {
679            ensure!(
680                total_deposit >= operator.minimum_nominator_stake,
681                Error::MinimumNominatorStake
682            );
683
684            if first_deposit_in_epoch {
685                NominatorCount::<T>::try_mutate(operator_id, |count| {
686                    *count += 1;
687                    Ok(())
688                })?;
689            }
690        }
691
692        Ok(())
693    })
694}
695
696pub(crate) fn hold_deposit<T: Config>(
697    who: &T::AccountId,
698    operator_id: OperatorId,
699    amount: BalanceOf<T>,
700) -> Result<(), Error> {
701    // ensure there is enough free balance to lock
702    ensure!(
703        T::Currency::reducible_balance(who, Preservation::Preserve, Fortitude::Polite) >= amount,
704        Error::InsufficientBalance
705    );
706
707    DepositOnHold::<T>::try_mutate((operator_id, who), |deposit_on_hold| {
708        *deposit_on_hold = deposit_on_hold
709            .checked_add(&amount)
710            .ok_or(Error::BalanceOverflow)?;
711        Ok(())
712    })?;
713
714    let pending_deposit_hold_id = T::HoldIdentifier::staking_staked();
715    T::Currency::hold(&pending_deposit_hold_id, who, amount).map_err(|_| Error::BalanceFreeze)?;
716
717    Ok(())
718}
719
720pub(crate) fn do_deregister_operator<T: Config>(
721    operator_owner: T::AccountId,
722    operator_id: OperatorId,
723) -> Result<(), Error> {
724    ensure!(
725        OperatorIdOwner::<T>::get(operator_id) == Some(operator_owner),
726        Error::NotOperatorOwner
727    );
728
729    Operators::<T>::try_mutate(operator_id, |maybe_operator| {
730        let operator = maybe_operator.as_mut().ok_or(Error::UnknownOperator)?;
731
732        ensure!(
733            *operator.status::<T>(operator_id) == OperatorStatus::Registered,
734            Error::OperatorNotRegistered
735        );
736
737        DomainStakingSummary::<T>::try_mutate(
738            operator.current_domain_id,
739            |maybe_domain_stake_summary| {
740                let stake_summary = maybe_domain_stake_summary
741                    .as_mut()
742                    .ok_or(Error::DomainNotInitialized)?;
743
744                let head_domain_number = HeadDomainNumber::<T>::get(operator.current_domain_id);
745                let unlock_operator_at_domain_block_number = head_domain_number
746                    .checked_add(&T::StakeWithdrawalLockingPeriod::get())
747                    .ok_or(Error::BlockNumberOverflow)?;
748                let operator_deregister_info = (
749                    operator.current_domain_id,
750                    stake_summary.current_epoch_index,
751                    unlock_operator_at_domain_block_number,
752                )
753                    .into();
754
755                operator.update_status(OperatorStatus::Deregistered(operator_deregister_info));
756
757                stake_summary.next_operators.remove(&operator_id);
758                Ok(())
759            },
760        )
761    })
762}
763
764/// Different type of withdrawal
765///
766/// NOTE: if the deposit was made in the current epoch, the user may not be able to withdraw it
767/// until the current epoch ends
768#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
769pub enum WithdrawStake<Balance, Share> {
770    /// Withdraw all stake
771    All,
772    /// Withdraw a given percentage of the stake
773    Percent(Percent),
774    /// Withdraw a given amount of stake, calculated by the share price at
775    /// this instant, it may not be accurate and may withdraw a bit more
776    /// stake if a reward happens later in this epoch
777    Stake(Balance),
778    /// Withdraw a given amount of share
779    Share(Share),
780}
781
782impl<Balance: Zero, Share: Zero> WithdrawStake<Balance, Share> {
783    pub fn is_zero(&self) -> bool {
784        match self {
785            Self::All => false,
786            Self::Percent(p) => p.is_zero(),
787            Self::Stake(s) => s.is_zero(),
788            Self::Share(s) => s.is_zero(),
789        }
790    }
791}
792
793// A helper function used to calculate the share price at this instant
794fn current_share_price<T: Config>(
795    operator_id: OperatorId,
796    operator: &Operator<BalanceOf<T>, T::Share, DomainBlockNumberFor<T>>,
797    domain_stake_summary: &StakingSummary<OperatorId, BalanceOf<T>>,
798) -> SharePrice {
799    // Total stake including any reward within this epoch.
800    let total_stake = domain_stake_summary
801        .current_epoch_rewards
802        .get(&operator_id)
803        .and_then(|rewards| {
804            let operator_tax = operator.nomination_tax.mul_floor(*rewards);
805            operator
806                .current_total_stake
807                .checked_add(rewards)?
808                // deduct operator tax
809                .checked_sub(&operator_tax)
810        })
811        .unwrap_or(operator.current_total_stake);
812
813    SharePrice::new::<T>(operator.current_total_shares, total_stake)
814}
815
816pub(crate) fn do_withdraw_stake<T: Config>(
817    operator_id: OperatorId,
818    nominator_id: NominatorId<T>,
819    to_withdraw: WithdrawStake<BalanceOf<T>, T::Share>,
820) -> Result<(), Error> {
821    Operators::<T>::try_mutate(operator_id, |maybe_operator| {
822        let operator = maybe_operator.as_mut().ok_or(Error::UnknownOperator)?;
823        ensure!(
824            *operator.status::<T>(operator_id) == OperatorStatus::Registered,
825            Error::OperatorNotRegistered
826        );
827
828        // If the this is the first staking request of this operator `note_pending_staking_operation` for it
829        if operator.deposits_in_epoch.is_zero() && operator.withdrawals_in_epoch.is_zero() {
830            note_pending_staking_operation::<T>(operator.current_domain_id)?;
831        }
832
833        // calculate shares for any previous epoch
834        let domain_stake_summary = DomainStakingSummary::<T>::get(operator.current_domain_id)
835            .ok_or(Error::DomainNotInitialized)?;
836        let domain_current_epoch = (
837            operator.current_domain_id,
838            domain_stake_summary.current_epoch_index,
839        )
840            .into();
841
842        let known_share =
843            Deposits::<T>::try_mutate(operator_id, nominator_id.clone(), |maybe_deposit| {
844                let deposit = maybe_deposit.as_mut().ok_or(Error::UnknownNominator)?;
845                do_convert_previous_epoch_deposits::<T>(
846                    operator_id,
847                    deposit,
848                    domain_stake_summary.current_epoch_index,
849                )?;
850                Ok(deposit.known.shares)
851            })?;
852
853        Withdrawals::<T>::try_mutate(operator_id, nominator_id.clone(), |maybe_withdrawal| {
854            if let Some(withdrawal) = maybe_withdrawal {
855                do_convert_previous_epoch_withdrawal::<T>(
856                    operator_id,
857                    withdrawal,
858                    domain_stake_summary.current_epoch_index,
859                )?;
860                if withdrawal.withdrawals.len() as u32 >= T::WithdrawalLimit::get() {
861                    return Err(Error::TooManyWithdrawals);
862                }
863            }
864            Ok(())
865        })?;
866
867        let operator_owner =
868            OperatorIdOwner::<T>::get(operator_id).ok_or(Error::UnknownOperator)?;
869
870        let is_operator_owner = operator_owner == nominator_id;
871
872        let shares_withdrew = match to_withdraw {
873            WithdrawStake::All => known_share,
874            WithdrawStake::Percent(p) => p.mul_floor(known_share),
875            WithdrawStake::Stake(s) => {
876                let share_price =
877                    current_share_price::<T>(operator_id, operator, &domain_stake_summary);
878                share_price.stake_to_shares::<T>(s)
879            }
880            WithdrawStake::Share(s) => s,
881        };
882
883        ensure!(!shares_withdrew.is_zero(), Error::ZeroWithdraw);
884
885        Deposits::<T>::try_mutate(operator_id, nominator_id.clone(), |maybe_deposit| {
886            let deposit = maybe_deposit.as_mut().ok_or(Error::UnknownNominator)?;
887            let known_shares = deposit.known.shares;
888
889            let (remaining_shares, shares_withdrew) = {
890                let remaining_shares = known_shares
891                    .checked_sub(&shares_withdrew)
892                    .ok_or(Error::InsufficientShares)?;
893
894                // short circuit to check if remaining shares can be zero
895                if remaining_shares.is_zero() {
896                    if is_operator_owner {
897                        return Err(Error::MinimumOperatorStake);
898                    }
899
900                    (remaining_shares, shares_withdrew)
901                } else {
902                    let share_price =
903                        current_share_price::<T>(operator_id, operator, &domain_stake_summary);
904
905                    let remaining_storage_fee =
906                        Perbill::from_rational(remaining_shares, known_shares)
907                            .mul_floor(deposit.known.storage_fee_deposit);
908
909                    let remaining_stake = share_price
910                        .shares_to_stake::<T>(remaining_shares)
911                        .checked_add(&remaining_storage_fee)
912                        .ok_or(Error::BalanceOverflow)?;
913
914                    // ensure the remaining share value is atleast the defined minimum
915                    // MinOperatorStake if a nominator is operator pool owner
916                    if is_operator_owner && remaining_stake.lt(&T::MinOperatorStake::get()) {
917                        return Err(Error::MinimumOperatorStake);
918                    }
919
920                    // if not an owner, if remaining balance < MinNominatorStake, then withdraw all shares.
921                    if !is_operator_owner && remaining_stake.lt(&operator.minimum_nominator_stake) {
922                        (T::Share::zero(), known_shares)
923                    } else {
924                        (remaining_shares, shares_withdrew)
925                    }
926                }
927            };
928
929            // Withdraw storage fund, the `withdraw_storage_fee` amount of fund will be transfered
930            // and hold on the nominator account
931            let storage_fee_to_withdraw = Perbill::from_rational(shares_withdrew, known_shares)
932                .mul_floor(deposit.known.storage_fee_deposit);
933
934            let withdraw_storage_fee = {
935                let storage_fund_redeem_price = bundle_storage_fund::storage_fund_redeem_price::<T>(
936                    operator_id,
937                    operator.total_storage_fee_deposit,
938                );
939                bundle_storage_fund::withdraw_and_hold::<T>(
940                    operator_id,
941                    &nominator_id,
942                    storage_fund_redeem_price.redeem(storage_fee_to_withdraw),
943                )
944                .map_err(Error::BundleStorageFund)?
945            };
946
947            deposit.known.storage_fee_deposit = deposit
948                .known
949                .storage_fee_deposit
950                .checked_sub(&storage_fee_to_withdraw)
951                .ok_or(Error::BalanceOverflow)?;
952
953            operator.total_storage_fee_deposit = operator
954                .total_storage_fee_deposit
955                .checked_sub(&storage_fee_to_withdraw)
956                .ok_or(Error::BalanceOverflow)?;
957
958            // update operator pool to note withdrew shares in the epoch
959            operator.withdrawals_in_epoch = operator
960                .withdrawals_in_epoch
961                .checked_add(&shares_withdrew)
962                .ok_or(Error::ShareOverflow)?;
963
964            deposit.known.shares = remaining_shares;
965            if remaining_shares.is_zero() {
966                if let Some(pending_deposit) = deposit.pending {
967                    // if there is a pending deposit, then ensure
968                    // the new deposit is atleast minimum nominator stake
969                    ensure!(
970                        pending_deposit.total()? >= operator.minimum_nominator_stake,
971                        Error::MinimumNominatorStake
972                    );
973                } else {
974                    // reduce nominator count if withdraw all and there are no pending deposits
975                    NominatorCount::<T>::mutate(operator_id, |count| {
976                        *count -= 1;
977                    });
978                }
979            }
980
981            let head_domain_number = HeadDomainNumber::<T>::get(operator.current_domain_id);
982            let unlock_at_confirmed_domain_block_number = head_domain_number
983                .checked_add(&T::StakeWithdrawalLockingPeriod::get())
984                .ok_or(Error::BlockNumberOverflow)?;
985
986            Withdrawals::<T>::try_mutate(operator_id, nominator_id, |maybe_withdrawal| {
987                let mut withdrawal = maybe_withdrawal.take().unwrap_or_default();
988                // if this is some, then the withdrawal was initiated in this current epoch due to conversion
989                // of previous epoch withdrawals from shares to balances above. So just update it instead
990                let new_withdrawal_in_shares = match withdrawal.withdrawal_in_shares.take() {
991                    Some(WithdrawalInShares {
992                        shares,
993                        storage_fee_refund,
994                        ..
995                    }) => WithdrawalInShares {
996                        domain_epoch: domain_current_epoch,
997                        shares: shares
998                            .checked_add(&shares_withdrew)
999                            .ok_or(Error::ShareOverflow)?,
1000                        unlock_at_confirmed_domain_block_number,
1001                        storage_fee_refund: storage_fee_refund
1002                            .checked_add(&withdraw_storage_fee)
1003                            .ok_or(Error::BalanceOverflow)?,
1004                    },
1005                    None => WithdrawalInShares {
1006                        domain_epoch: domain_current_epoch,
1007                        unlock_at_confirmed_domain_block_number,
1008                        shares: shares_withdrew,
1009                        storage_fee_refund: withdraw_storage_fee,
1010                    },
1011                };
1012                withdrawal.withdrawal_in_shares = Some(new_withdrawal_in_shares);
1013                withdrawal.total_storage_fee_withdrawal = withdrawal
1014                    .total_storage_fee_withdrawal
1015                    .checked_add(&withdraw_storage_fee)
1016                    .ok_or(Error::BalanceOverflow)?;
1017
1018                *maybe_withdrawal = Some(withdrawal);
1019                Ok(())
1020            })
1021        })
1022    })
1023}
1024
1025/// Unlocks any withdraws that are ready to be unlocked.
1026pub(crate) fn do_unlock_funds<T: Config>(
1027    operator_id: OperatorId,
1028    nominator_id: NominatorId<T>,
1029) -> Result<(), Error> {
1030    let operator = Operators::<T>::get(operator_id).ok_or(Error::UnknownOperator)?;
1031    ensure!(
1032        *operator.status::<T>(operator_id) == OperatorStatus::Registered,
1033        Error::OperatorNotRegistered
1034    );
1035
1036    let current_domain_epoch_index = DomainStakingSummary::<T>::get(operator.current_domain_id)
1037        .ok_or(Error::DomainNotInitialized)?
1038        .current_epoch_index;
1039
1040    Withdrawals::<T>::try_mutate_exists(operator_id, nominator_id.clone(), |maybe_withdrawal| {
1041        let withdrawal = maybe_withdrawal.as_mut().ok_or(Error::MissingWithdrawal)?;
1042        do_convert_previous_epoch_withdrawal::<T>(
1043            operator_id,
1044            withdrawal,
1045            current_domain_epoch_index,
1046        )?;
1047
1048        ensure!(!withdrawal.withdrawals.is_empty(), Error::MissingWithdrawal);
1049
1050        let head_domain_number = HeadDomainNumber::<T>::get(operator.current_domain_id);
1051
1052        let mut total_unlocked_amount = BalanceOf::<T>::zero();
1053        let mut total_storage_fee_refund = BalanceOf::<T>::zero();
1054        loop {
1055            if withdrawal
1056                .withdrawals
1057                .front()
1058                .map(|w| w.unlock_at_confirmed_domain_block_number > head_domain_number)
1059                .unwrap_or(true)
1060            {
1061                break;
1062            }
1063
1064            let WithdrawalInBalance {
1065                amount_to_unlock,
1066                storage_fee_refund,
1067                ..
1068            } = withdrawal
1069                .withdrawals
1070                .pop_front()
1071                .expect("Must not empty as checked above; qed");
1072
1073            total_unlocked_amount = total_unlocked_amount
1074                .checked_add(&amount_to_unlock)
1075                .ok_or(Error::BalanceOverflow)?;
1076
1077            total_storage_fee_refund = total_storage_fee_refund
1078                .checked_add(&storage_fee_refund)
1079                .ok_or(Error::BalanceOverflow)?;
1080        }
1081
1082        // There is withdrawal but none being processed meaning the first withdrawal's unlock period has
1083        // not completed yet
1084        ensure!(
1085            !total_unlocked_amount.is_zero() || !total_storage_fee_refund.is_zero(),
1086            Error::UnlockPeriodNotComplete
1087        );
1088
1089        // deduct the amount unlocked from total
1090        withdrawal.total_withdrawal_amount = withdrawal
1091            .total_withdrawal_amount
1092            .checked_sub(&total_unlocked_amount)
1093            .ok_or(Error::BalanceUnderflow)?;
1094
1095        withdrawal.total_storage_fee_withdrawal = withdrawal
1096            .total_storage_fee_withdrawal
1097            .checked_sub(&total_storage_fee_refund)
1098            .ok_or(Error::BalanceUnderflow)?;
1099
1100        // If the amount to release is more than currently locked,
1101        // mint the diff and release the rest
1102        let (amount_to_mint, amount_to_release) = DepositOnHold::<T>::try_mutate(
1103            (operator_id, nominator_id.clone()),
1104            |deposit_on_hold| {
1105                let amount_to_release = total_unlocked_amount.min(*deposit_on_hold);
1106                let amount_to_mint = total_unlocked_amount.saturating_sub(*deposit_on_hold);
1107
1108                *deposit_on_hold = deposit_on_hold.saturating_sub(amount_to_release);
1109
1110                Ok((amount_to_mint, amount_to_release))
1111            },
1112        )?;
1113
1114        // Mint any gains
1115        if !amount_to_mint.is_zero() {
1116            mint_funds::<T>(&nominator_id, amount_to_mint)?;
1117        }
1118        // Release staking fund
1119        if !amount_to_release.is_zero() {
1120            let staked_hold_id = T::HoldIdentifier::staking_staked();
1121            T::Currency::release(
1122                &staked_hold_id,
1123                &nominator_id,
1124                amount_to_release,
1125                Precision::Exact,
1126            )
1127            .map_err(|_| Error::RemoveLock)?;
1128        }
1129
1130        Pallet::<T>::deposit_event(Event::NominatedStakedUnlocked {
1131            operator_id,
1132            nominator_id: nominator_id.clone(),
1133            unlocked_amount: total_unlocked_amount,
1134        });
1135
1136        // Release storage fund
1137        let storage_fund_hold_id = T::HoldIdentifier::storage_fund_withdrawal();
1138        T::Currency::release(
1139            &storage_fund_hold_id,
1140            &nominator_id,
1141            total_storage_fee_refund,
1142            Precision::Exact,
1143        )
1144        .map_err(|_| Error::RemoveLock)?;
1145
1146        Pallet::<T>::deposit_event(Event::StorageFeeUnlocked {
1147            operator_id,
1148            nominator_id: nominator_id.clone(),
1149            storage_fee: total_storage_fee_refund,
1150        });
1151
1152        // if there are no withdrawals, then delete the storage as well
1153        if withdrawal.withdrawals.is_empty() && withdrawal.withdrawal_in_shares.is_none() {
1154            *maybe_withdrawal = None;
1155            // if there is no deposit or pending deposits, then clean up the deposit state as well
1156            Deposits::<T>::mutate_exists(operator_id, nominator_id, |maybe_deposit| {
1157                if let Some(deposit) = maybe_deposit
1158                    && deposit.known.shares.is_zero()
1159                    && deposit.pending.is_none()
1160                {
1161                    *maybe_deposit = None
1162                }
1163            });
1164        }
1165
1166        Ok(())
1167    })
1168}
1169
1170/// Unlocks an already de-registered operator's nominator given unlock wait period is complete.
1171pub(crate) fn do_unlock_nominator<T: Config>(
1172    operator_id: OperatorId,
1173    nominator_id: NominatorId<T>,
1174) -> Result<(), Error> {
1175    Operators::<T>::try_mutate_exists(operator_id, |maybe_operator| {
1176        // take the operator so this operator info is removed once we unlock the operator.
1177        let mut operator = maybe_operator.take().ok_or(Error::UnknownOperator)?;
1178        let OperatorDeregisteredInfo {
1179            domain_epoch,
1180            unlock_at_confirmed_domain_block_number,
1181        } = match operator.status::<T>(operator_id) {
1182            OperatorStatus::Deregistered(operator_deregistered_info) => operator_deregistered_info,
1183            _ => return Err(Error::OperatorNotDeregistered),
1184        };
1185
1186        let (domain_id, _) = domain_epoch.deconstruct();
1187        let head_domain_number = HeadDomainNumber::<T>::get(domain_id);
1188        ensure!(
1189            *unlock_at_confirmed_domain_block_number <= head_domain_number,
1190            Error::UnlockPeriodNotComplete
1191        );
1192
1193        let current_domain_epoch_index = DomainStakingSummary::<T>::get(operator.current_domain_id)
1194            .ok_or(Error::DomainNotInitialized)?
1195            .current_epoch_index;
1196
1197        let mut total_shares = operator.current_total_shares;
1198        let mut total_stake = operator.current_total_stake;
1199        let share_price = SharePrice::new::<T>(total_shares, total_stake);
1200
1201        let mut total_storage_fee_deposit = operator.total_storage_fee_deposit;
1202        let storage_fund_redeem_price = bundle_storage_fund::storage_fund_redeem_price::<T>(
1203            operator_id,
1204            total_storage_fee_deposit,
1205        );
1206        let mut deposit = Deposits::<T>::take(operator_id, nominator_id.clone())
1207            .ok_or(Error::UnknownNominator)?;
1208
1209        // convert any deposits from the previous epoch to shares
1210        match do_convert_previous_epoch_deposits::<T>(
1211            operator_id,
1212            &mut deposit,
1213            current_domain_epoch_index,
1214        ) {
1215            // Share price may be missing if there is deposit happen in the same epoch as de-register
1216            Ok(()) | Err(Error::MissingOperatorEpochSharePrice) => {}
1217            Err(err) => return Err(err),
1218        }
1219
1220        // if there are any withdrawals from this operator, account for them
1221        // if the withdrawals has share price noted, then convert them to SSC
1222        // if no share price, then it must be intitated in the epoch before operator de-registered,
1223        // so get the shares as is and include them in the total staked shares.
1224        let (
1225            amount_ready_to_withdraw,
1226            total_storage_fee_withdrawal,
1227            shares_withdrew_in_current_epoch,
1228        ) = Withdrawals::<T>::take(operator_id, nominator_id.clone())
1229            .map(|mut withdrawal| {
1230                match do_convert_previous_epoch_withdrawal::<T>(
1231                    operator_id,
1232                    &mut withdrawal,
1233                    current_domain_epoch_index,
1234                ) {
1235                    // Share price may be missing if there is withdrawal happen in the same epoch as de-register
1236                    Ok(()) | Err(Error::MissingOperatorEpochSharePrice) => {}
1237                    Err(err) => return Err(err),
1238                }
1239                Ok((
1240                    withdrawal.total_withdrawal_amount,
1241                    withdrawal.total_storage_fee_withdrawal,
1242                    withdrawal
1243                        .withdrawal_in_shares
1244                        .map(|WithdrawalInShares { shares, .. }| shares)
1245                        .unwrap_or_default(),
1246                ))
1247            })
1248            .unwrap_or(Ok((Zero::zero(), Zero::zero(), Zero::zero())))?;
1249
1250        // include all the known shares and shares that were withdrawn in the current epoch
1251        let nominator_shares = deposit
1252            .known
1253            .shares
1254            .checked_add(&shares_withdrew_in_current_epoch)
1255            .ok_or(Error::ShareOverflow)?;
1256
1257        // current staked amount
1258        let nominator_staked_amount = share_price.shares_to_stake::<T>(nominator_shares);
1259
1260        // amount deposited by this nominator before operator de-registered.
1261        let amount_deposited_in_epoch = deposit
1262            .pending
1263            .map(|pending_deposit| pending_deposit.amount)
1264            .unwrap_or_default();
1265
1266        let total_amount_to_unlock = nominator_staked_amount
1267            .checked_add(&amount_ready_to_withdraw)
1268            .and_then(|amount| amount.checked_add(&amount_deposited_in_epoch))
1269            .ok_or(Error::BalanceOverflow)?;
1270
1271        // Remove the lock and mint any gains
1272        let current_locked_amount = DepositOnHold::<T>::take((operator_id, nominator_id.clone()));
1273        if let Some(amount_to_mint) = total_amount_to_unlock.checked_sub(&current_locked_amount) {
1274            mint_funds::<T>(&nominator_id, amount_to_mint)?;
1275        }
1276        if !current_locked_amount.is_zero() {
1277            let staked_hold_id = T::HoldIdentifier::staking_staked();
1278            T::Currency::release(
1279                &staked_hold_id,
1280                &nominator_id,
1281                current_locked_amount,
1282                Precision::Exact,
1283            )
1284            .map_err(|_| Error::RemoveLock)?;
1285        }
1286
1287        Pallet::<T>::deposit_event(Event::NominatedStakedUnlocked {
1288            operator_id,
1289            nominator_id: nominator_id.clone(),
1290            unlocked_amount: total_amount_to_unlock,
1291        });
1292
1293        total_stake = total_stake.saturating_sub(nominator_staked_amount);
1294        total_shares = total_shares.saturating_sub(nominator_shares);
1295
1296        // Withdraw all storage fee for the nominator
1297        let nominator_total_storage_fee_deposit = deposit
1298            .pending
1299            .map(|pending_deposit| pending_deposit.storage_fee_deposit)
1300            .unwrap_or(Zero::zero())
1301            .checked_add(&deposit.known.storage_fee_deposit)
1302            .ok_or(Error::BalanceOverflow)?;
1303
1304        bundle_storage_fund::withdraw_to::<T>(
1305            operator_id,
1306            &nominator_id,
1307            storage_fund_redeem_price.redeem(nominator_total_storage_fee_deposit),
1308        )
1309        .map_err(Error::BundleStorageFund)?;
1310
1311        // Release all storage fee on withdraw of the nominator
1312        let storage_fund_hold_id = T::HoldIdentifier::storage_fund_withdrawal();
1313        T::Currency::release(
1314            &storage_fund_hold_id,
1315            &nominator_id,
1316            total_storage_fee_withdrawal,
1317            Precision::Exact,
1318        )
1319        .map_err(|_| Error::RemoveLock)?;
1320
1321        Pallet::<T>::deposit_event(Event::StorageFeeUnlocked {
1322            operator_id,
1323            nominator_id: nominator_id.clone(),
1324            storage_fee: total_storage_fee_withdrawal,
1325        });
1326
1327        // reduce total storage fee deposit with nominator total fee deposit
1328        total_storage_fee_deposit =
1329            total_storage_fee_deposit.saturating_sub(nominator_total_storage_fee_deposit);
1330
1331        let current_nominator_count = NominatorCount::<T>::get(operator_id);
1332        let operator_owner =
1333            OperatorIdOwner::<T>::get(operator_id).ok_or(Error::UnknownOperator)?;
1334
1335        // reduce the nominator count for operator if the nominator is not operator owner
1336        // since operator own nominator is not counted into nominator count for operator.
1337        let current_nominator_count =
1338            if operator_owner != nominator_id && current_nominator_count > 0 {
1339                let new_nominator_count = current_nominator_count - 1;
1340                NominatorCount::<T>::set(operator_id, new_nominator_count);
1341                new_nominator_count
1342            } else {
1343                current_nominator_count
1344            };
1345
1346        // operator state can be cleaned if all the nominators have unlocked their stake and operator
1347        // themself unlocked their stake.
1348        let cleanup_operator = current_nominator_count == 0
1349            && !Deposits::<T>::contains_key(operator_id, operator_owner);
1350
1351        if cleanup_operator {
1352            do_cleanup_operator::<T>(operator_id, total_stake)?
1353        } else {
1354            // set update total shares, total stake and total storage fee deposit for operator
1355            operator.current_total_shares = total_shares;
1356            operator.current_total_stake = total_stake;
1357            operator.total_storage_fee_deposit = total_storage_fee_deposit;
1358
1359            *maybe_operator = Some(operator);
1360        }
1361
1362        Ok(())
1363    })
1364}
1365
1366/// Removes all operator storages and mints the total stake back to treasury.
1367pub(crate) fn do_cleanup_operator<T: Config>(
1368    operator_id: OperatorId,
1369    total_stake: BalanceOf<T>,
1370) -> Result<(), Error> {
1371    // transfer any remaining storage fund to treasury
1372    bundle_storage_fund::transfer_all_to_treasury::<T>(operator_id)
1373        .map_err(Error::BundleStorageFund)?;
1374
1375    // transfer any remaining amount to treasury
1376    mint_into_treasury::<T>(total_stake)?;
1377
1378    // remove OperatorOwner Details
1379    OperatorIdOwner::<T>::remove(operator_id);
1380
1381    // remove operator epoch share prices
1382    let _ = OperatorEpochSharePrice::<T>::clear_prefix(operator_id, u32::MAX, None);
1383
1384    // remove nominator count for this operator.
1385    NominatorCount::<T>::remove(operator_id);
1386
1387    Ok(())
1388}
1389
1390/// Distribute the reward to the operators equally and drop any dust to treasury.
1391pub(crate) fn do_reward_operators<T: Config>(
1392    domain_id: DomainId,
1393    source: OperatorRewardSource<BlockNumberFor<T>>,
1394    operators: IntoIter<OperatorId>,
1395    rewards: BalanceOf<T>,
1396) -> Result<(), Error> {
1397    if rewards.is_zero() {
1398        return Ok(());
1399    }
1400    DomainStakingSummary::<T>::mutate(domain_id, |maybe_stake_summary| {
1401        let stake_summary = maybe_stake_summary
1402            .as_mut()
1403            .ok_or(Error::DomainNotInitialized)?;
1404
1405        let total_count = operators.len() as u64;
1406        // calculate the operator weights based on the number of times they are repeated in the original list.
1407        let operator_weights = operators.into_iter().fold(
1408            BTreeMap::<OperatorId, u64>::new(),
1409            |mut acc, operator_id| {
1410                acc.entry(operator_id)
1411                    .and_modify(|weight| *weight += 1)
1412                    .or_insert(1);
1413                acc
1414            },
1415        );
1416
1417        let mut allocated_rewards = BalanceOf::<T>::zero();
1418        for (operator_id, weight) in operator_weights {
1419            let operator_reward = {
1420                let distribution = Perquintill::from_rational(weight, total_count);
1421                distribution.mul_floor(rewards)
1422            };
1423
1424            stake_summary
1425                .current_epoch_rewards
1426                .entry(operator_id)
1427                .and_modify(|rewards| *rewards = rewards.saturating_add(operator_reward))
1428                .or_insert(operator_reward);
1429
1430            Pallet::<T>::deposit_event(Event::OperatorRewarded {
1431                source: source.clone(),
1432                operator_id,
1433                reward: operator_reward,
1434            });
1435
1436            allocated_rewards = allocated_rewards
1437                .checked_add(&operator_reward)
1438                .ok_or(Error::BalanceOverflow)?;
1439        }
1440
1441        // mint remaining funds to treasury
1442        mint_into_treasury::<T>(
1443            rewards
1444                .checked_sub(&allocated_rewards)
1445                .ok_or(Error::BalanceUnderflow)?,
1446        )
1447    })
1448}
1449
1450/// Freezes the slashed operators and moves the operator to be removed once the domain they are
1451/// operating finishes the epoch.
1452pub(crate) fn do_mark_operators_as_slashed<T: Config>(
1453    operator_ids: impl AsRef<[OperatorId]>,
1454    slash_reason: SlashedReason<DomainBlockNumberFor<T>, ReceiptHashFor<T>>,
1455) -> Result<(), Error> {
1456    for operator_id in operator_ids.as_ref() {
1457        Operators::<T>::try_mutate(operator_id, |maybe_operator| {
1458            let operator = match maybe_operator.as_mut() {
1459                // If the operator is already slashed and removed due to fraud proof, when the operator
1460                // is slash again due to invalid bundle, which happen after the ER is confirmed, we can
1461                // not find the operator here thus just return.
1462                None => return Ok(()),
1463                Some(operator) => operator,
1464            };
1465            let mut pending_slashes =
1466                PendingSlashes::<T>::get(operator.current_domain_id).unwrap_or_default();
1467
1468            if pending_slashes.contains(operator_id) {
1469                return Ok(());
1470            }
1471
1472            DomainStakingSummary::<T>::try_mutate(
1473                operator.current_domain_id,
1474                |maybe_domain_stake_summary| {
1475                    let stake_summary = maybe_domain_stake_summary
1476                        .as_mut()
1477                        .ok_or(Error::DomainNotInitialized)?;
1478
1479                    // slash and remove operator from next and current epoch set
1480                    operator.update_status(OperatorStatus::Slashed);
1481                    stake_summary.current_operators.remove(operator_id);
1482                    stake_summary.next_operators.remove(operator_id);
1483                    stake_summary.current_total_stake = stake_summary
1484                        .current_total_stake
1485                        .checked_sub(&operator.current_total_stake)
1486                        .ok_or(Error::BalanceUnderflow)?;
1487
1488                    pending_slashes.insert(*operator_id);
1489                    PendingSlashes::<T>::insert(operator.current_domain_id, pending_slashes);
1490                    Pallet::<T>::deposit_event(Event::OperatorSlashed {
1491                        operator_id: *operator_id,
1492                        reason: slash_reason.clone(),
1493                    });
1494                    Ok(())
1495                },
1496            )
1497        })?
1498    }
1499
1500    Ok(())
1501}
1502
1503#[cfg(test)]
1504pub(crate) mod tests {
1505    use crate::domain_registry::{DomainConfig, DomainObject};
1506    use crate::pallet::{
1507        Config, Deposits, DomainRegistry, DomainStakingSummary, HeadDomainNumber, NextOperatorId,
1508        NominatorCount, OperatorIdOwner, Operators, PendingSlashes, Withdrawals,
1509    };
1510    use crate::staking::{
1511        do_convert_previous_epoch_withdrawal, do_mark_operators_as_slashed, do_nominate_operator,
1512        do_reward_operators, do_unlock_funds, do_withdraw_stake, DomainEpoch,
1513        Error as StakingError, Operator, OperatorConfig, OperatorStatus, StakingSummary,
1514        WithdrawStake,
1515    };
1516    use crate::staking_epoch::{do_finalize_domain_current_epoch, do_slash_operator};
1517    use crate::tests::{new_test_ext, ExistentialDeposit, RuntimeOrigin, Test};
1518    use crate::{
1519        bundle_storage_fund, AllowedDefaultSharePriceEpoch, BalanceOf, Error, NominatorId,
1520        OperatorEpochSharePrice, SlashedReason, MAX_NOMINATORS_TO_SLASH,
1521    };
1522    use frame_support::traits::fungible::Mutate;
1523    use frame_support::traits::Currency;
1524    use frame_support::weights::Weight;
1525    use frame_support::{assert_err, assert_ok};
1526    use sp_core::{sr25519, Pair};
1527    use sp_domains::{
1528        DomainId, OperatorAllowList, OperatorId, OperatorPair, OperatorPublicKey,
1529        OperatorRewardSource,
1530    };
1531    use sp_runtime::traits::Zero;
1532    use sp_runtime::{PerThing, Perbill, Percent};
1533    use std::collections::{BTreeMap, BTreeSet};
1534    use std::vec;
1535    use subspace_runtime_primitives::SSC;
1536
1537    type Balances = pallet_balances::Pallet<Test>;
1538    type Domains = crate::Pallet<Test>;
1539
1540    const STORAGE_FEE_RESERVE: Perbill = Perbill::from_percent(20);
1541
1542    #[allow(clippy::too_many_arguments)]
1543    pub(crate) fn register_operator(
1544        domain_id: DomainId,
1545        operator_account: <Test as frame_system::Config>::AccountId,
1546        operator_free_balance: BalanceOf<Test>,
1547        operator_stake: BalanceOf<Test>,
1548        minimum_nominator_stake: BalanceOf<Test>,
1549        signing_key: OperatorPublicKey,
1550        mut nominators: BTreeMap<NominatorId<Test>, (BalanceOf<Test>, BalanceOf<Test>)>,
1551    ) -> (OperatorId, OperatorConfig<BalanceOf<Test>>) {
1552        nominators.insert(operator_account, (operator_free_balance, operator_stake));
1553        for nominator in &nominators {
1554            Balances::set_balance(nominator.0, nominator.1 .0);
1555            assert_eq!(Balances::usable_balance(nominator.0), nominator.1 .0);
1556        }
1557        nominators.remove(&operator_account);
1558
1559        if !DomainRegistry::<Test>::contains_key(domain_id) {
1560            let domain_config = DomainConfig {
1561                domain_name: String::from_utf8(vec![0; 1024]).unwrap(),
1562                runtime_id: 0,
1563                max_bundle_size: u32::MAX,
1564                max_bundle_weight: Weight::MAX,
1565                bundle_slot_probability: (0, 0),
1566                operator_allow_list: OperatorAllowList::Anyone,
1567                initial_balances: Default::default(),
1568            };
1569
1570            let domain_obj = DomainObject {
1571                owner_account_id: 0,
1572                created_at: 0,
1573                genesis_receipt_hash: Default::default(),
1574                domain_config,
1575                domain_runtime_info: Default::default(),
1576                domain_instantiation_deposit: Default::default(),
1577            };
1578
1579            DomainRegistry::<Test>::insert(domain_id, domain_obj);
1580        }
1581
1582        if !DomainStakingSummary::<Test>::contains_key(domain_id) {
1583            DomainStakingSummary::<Test>::insert(
1584                domain_id,
1585                StakingSummary {
1586                    current_epoch_index: 0,
1587                    current_total_stake: 0,
1588                    current_operators: BTreeMap::new(),
1589                    next_operators: BTreeSet::new(),
1590                    current_epoch_rewards: BTreeMap::new(),
1591                },
1592            );
1593        }
1594
1595        let operator_config = OperatorConfig {
1596            signing_key,
1597            minimum_nominator_stake,
1598            nomination_tax: Default::default(),
1599        };
1600
1601        let res = Domains::register_operator(
1602            RuntimeOrigin::signed(operator_account),
1603            domain_id,
1604            operator_stake,
1605            operator_config.clone(),
1606        );
1607        assert_ok!(res);
1608
1609        let operator_id = NextOperatorId::<Test>::get() - 1;
1610        let mut expected_nominator_count = 0;
1611        for nominator in nominators {
1612            if nominator.1 .1.is_zero() {
1613                continue;
1614            }
1615
1616            expected_nominator_count += 1;
1617            let res = Domains::nominate_operator(
1618                RuntimeOrigin::signed(nominator.0),
1619                operator_id,
1620                nominator.1 .1,
1621            );
1622            assert_ok!(res);
1623        }
1624
1625        let nominator_count = NominatorCount::<Test>::get(operator_id) as usize;
1626        assert_eq!(nominator_count, expected_nominator_count);
1627
1628        (operator_id, operator_config)
1629    }
1630
1631    #[test]
1632    fn test_register_operator_invalid_signing_key() {
1633        let domain_id = DomainId::new(0);
1634        let operator_account = 1;
1635
1636        let mut ext = new_test_ext();
1637        ext.execute_with(|| {
1638            let operator_config = OperatorConfig {
1639                signing_key: OperatorPublicKey::from(sr25519::Public::default()),
1640                minimum_nominator_stake: Default::default(),
1641                nomination_tax: Default::default(),
1642            };
1643
1644            let res = Domains::register_operator(
1645                RuntimeOrigin::signed(operator_account),
1646                domain_id,
1647                Default::default(),
1648                operator_config,
1649            );
1650            assert_err!(
1651                res,
1652                Error::<Test>::Staking(StakingError::InvalidOperatorSigningKey)
1653            );
1654        });
1655    }
1656
1657    #[test]
1658    fn test_register_operator_minimum_nominator_stake() {
1659        let domain_id = DomainId::new(0);
1660        let operator_account = 1;
1661        let pair = OperatorPair::from_seed(&[0; 32]);
1662
1663        let mut ext = new_test_ext();
1664        ext.execute_with(|| {
1665            let operator_config = OperatorConfig {
1666                signing_key: pair.public(),
1667                minimum_nominator_stake: Default::default(),
1668                nomination_tax: Default::default(),
1669            };
1670
1671            let res = Domains::register_operator(
1672                RuntimeOrigin::signed(operator_account),
1673                domain_id,
1674                Default::default(),
1675                operator_config,
1676            );
1677            assert_err!(
1678                res,
1679                Error::<Test>::Staking(StakingError::MinimumNominatorStake)
1680            );
1681        });
1682    }
1683
1684    #[test]
1685    fn test_register_operator() {
1686        let domain_id = DomainId::new(0);
1687        let operator_account = 1;
1688        let operator_free_balance = 2500 * SSC;
1689        let operator_total_stake = 1000 * SSC;
1690        let operator_stake = 800 * SSC;
1691        let operator_storage_fee_deposit = 200 * SSC;
1692        let pair = OperatorPair::from_seed(&[0; 32]);
1693
1694        let mut ext = new_test_ext();
1695        ext.execute_with(|| {
1696            let (operator_id, mut operator_config) = register_operator(
1697                domain_id,
1698                operator_account,
1699                operator_free_balance,
1700                operator_total_stake,
1701                SSC,
1702                pair.public(),
1703                BTreeMap::new(),
1704            );
1705
1706            assert_eq!(NextOperatorId::<Test>::get(), 1);
1707            // operator_id should be 0 and be registered
1708            assert_eq!(
1709                OperatorIdOwner::<Test>::get(operator_id).unwrap(),
1710                operator_account
1711            );
1712            assert_eq!(
1713                Operators::<Test>::get(operator_id).unwrap(),
1714                Operator {
1715                    signing_key: pair.public(),
1716                    current_domain_id: domain_id,
1717                    next_domain_id: domain_id,
1718                    minimum_nominator_stake: SSC,
1719                    nomination_tax: Default::default(),
1720                    current_total_stake: operator_stake,
1721                    current_total_shares: operator_stake,
1722                    partial_status: OperatorStatus::Registered,
1723                    deposits_in_epoch: 0,
1724                    withdrawals_in_epoch: 0,
1725                    total_storage_fee_deposit: operator_storage_fee_deposit,
1726                }
1727            );
1728
1729            let stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
1730            assert!(stake_summary.next_operators.contains(&operator_id));
1731            assert_eq!(stake_summary.current_total_stake, operator_stake);
1732
1733            assert_eq!(
1734                Balances::usable_balance(operator_account),
1735                operator_free_balance - operator_total_stake - ExistentialDeposit::get()
1736            );
1737
1738            // registering with same operator key is allowed
1739            let res = Domains::register_operator(
1740                RuntimeOrigin::signed(operator_account),
1741                domain_id,
1742                operator_stake,
1743                operator_config.clone(),
1744            );
1745            assert_ok!(res);
1746
1747            // cannot use the locked funds to register a new operator
1748            let new_pair = OperatorPair::from_seed(&[1; 32]);
1749            operator_config.signing_key = new_pair.public();
1750            let res = Domains::register_operator(
1751                RuntimeOrigin::signed(operator_account),
1752                domain_id,
1753                operator_stake,
1754                operator_config,
1755            );
1756            assert_err!(
1757                res,
1758                Error::<Test>::Staking(crate::staking::Error::InsufficientBalance)
1759            );
1760
1761            let nominator_count = NominatorCount::<Test>::get(operator_id);
1762            assert_eq!(nominator_count, 0);
1763        });
1764    }
1765
1766    #[test]
1767    fn nominate_operator() {
1768        let domain_id = DomainId::new(0);
1769        let operator_account = 1;
1770        let operator_free_balance = 1500 * SSC;
1771        let operator_total_stake = 1000 * SSC;
1772        let operator_stake = 800 * SSC;
1773        let operator_storage_fee_deposit = 200 * SSC;
1774        let pair = OperatorPair::from_seed(&[0; 32]);
1775
1776        let nominator_account = 2;
1777        let nominator_free_balance = 150 * SSC;
1778        let nominator_total_stake = 100 * SSC;
1779        let nominator_stake = 80 * SSC;
1780        let nominator_storage_fee_deposit = 20 * SSC;
1781
1782        let mut ext = new_test_ext();
1783        ext.execute_with(|| {
1784            let (operator_id, _) = register_operator(
1785                domain_id,
1786                operator_account,
1787                operator_free_balance,
1788                operator_total_stake,
1789                10 * SSC,
1790                pair.public(),
1791                BTreeMap::from_iter(vec![(
1792                    nominator_account,
1793                    (nominator_free_balance, nominator_total_stake),
1794                )]),
1795            );
1796
1797            let domain_staking_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
1798            assert_eq!(domain_staking_summary.current_total_stake, operator_stake);
1799
1800            let operator = Operators::<Test>::get(operator_id).unwrap();
1801            assert_eq!(operator.current_total_stake, operator_stake);
1802            assert_eq!(operator.current_total_shares, operator_stake);
1803            assert_eq!(
1804                operator.total_storage_fee_deposit,
1805                operator_storage_fee_deposit + nominator_storage_fee_deposit
1806            );
1807            assert_eq!(operator.deposits_in_epoch, nominator_stake);
1808
1809            let pending_deposit = Deposits::<Test>::get(0, nominator_account)
1810                .unwrap()
1811                .pending
1812                .unwrap();
1813            assert_eq!(pending_deposit.amount, nominator_stake);
1814            assert_eq!(
1815                pending_deposit.storage_fee_deposit,
1816                nominator_storage_fee_deposit
1817            );
1818            assert_eq!(pending_deposit.total().unwrap(), nominator_total_stake);
1819
1820            assert_eq!(
1821                Balances::usable_balance(nominator_account),
1822                nominator_free_balance - nominator_total_stake - ExistentialDeposit::get()
1823            );
1824
1825            // another transfer with an existing transfer in place should lead to single
1826            let addtional_nomination_total_stake = 40 * SSC;
1827            let addtional_nomination_stake = 32 * SSC;
1828            let addtional_nomination_storage_fee_deposit = 8 * SSC;
1829            let res = Domains::nominate_operator(
1830                RuntimeOrigin::signed(nominator_account),
1831                operator_id,
1832                addtional_nomination_total_stake,
1833            );
1834            assert_ok!(res);
1835            let pending_deposit = Deposits::<Test>::get(0, nominator_account)
1836                .unwrap()
1837                .pending
1838                .unwrap();
1839            assert_eq!(
1840                pending_deposit.amount,
1841                nominator_stake + addtional_nomination_stake
1842            );
1843            assert_eq!(
1844                pending_deposit.storage_fee_deposit,
1845                nominator_storage_fee_deposit + addtional_nomination_storage_fee_deposit
1846            );
1847
1848            let operator = Operators::<Test>::get(operator_id).unwrap();
1849            assert_eq!(operator.current_total_stake, operator_stake);
1850            assert_eq!(
1851                operator.deposits_in_epoch,
1852                nominator_stake + addtional_nomination_stake
1853            );
1854            assert_eq!(
1855                operator.total_storage_fee_deposit,
1856                operator_storage_fee_deposit
1857                    + nominator_storage_fee_deposit
1858                    + addtional_nomination_storage_fee_deposit
1859            );
1860
1861            let nominator_count = NominatorCount::<Test>::get(operator_id);
1862            assert_eq!(nominator_count, 1);
1863
1864            // do epoch transition
1865            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
1866
1867            let operator = Operators::<Test>::get(operator_id).unwrap();
1868            assert_eq!(
1869                operator.current_total_stake,
1870                operator_stake + nominator_stake + addtional_nomination_stake
1871            );
1872
1873            let domain_staking_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
1874            assert_eq!(
1875                domain_staking_summary.current_total_stake,
1876                operator_stake + nominator_stake + addtional_nomination_stake
1877            );
1878        });
1879    }
1880
1881    #[test]
1882    fn operator_deregistration() {
1883        let domain_id = DomainId::new(0);
1884        let operator_account = 1;
1885        let operator_stake = 200 * SSC;
1886        let operator_free_balance = 250 * SSC;
1887        let pair = OperatorPair::from_seed(&[0; 32]);
1888        let mut ext = new_test_ext();
1889        ext.execute_with(|| {
1890            let (operator_id, _) = register_operator(
1891                domain_id,
1892                operator_account,
1893                operator_free_balance,
1894                operator_stake,
1895                SSC,
1896                pair.public(),
1897                BTreeMap::new(),
1898            );
1899
1900            let res =
1901                Domains::deregister_operator(RuntimeOrigin::signed(operator_account), operator_id);
1902            assert_ok!(res);
1903
1904            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
1905            assert!(!domain_stake_summary.next_operators.contains(&operator_id));
1906
1907            let operator = Operators::<Test>::get(operator_id).unwrap();
1908            assert_eq!(
1909                *operator.status::<Test>(operator_id),
1910                OperatorStatus::Deregistered(
1911                    (
1912                        domain_id,
1913                        domain_stake_summary.current_epoch_index,
1914                        // since the Withdrawals locking period is 5 and confirmed domain block is 0
1915                        5
1916                    )
1917                        .into()
1918                )
1919            );
1920
1921            // operator nomination will not work since the operator is already de-registered
1922            let new_domain_id = DomainId::new(1);
1923            let domain_config = DomainConfig {
1924                domain_name: String::from_utf8(vec![0; 1024]).unwrap(),
1925                runtime_id: 0,
1926                max_bundle_size: u32::MAX,
1927                max_bundle_weight: Weight::MAX,
1928                bundle_slot_probability: (0, 0),
1929                operator_allow_list: OperatorAllowList::Anyone,
1930                initial_balances: Default::default(),
1931            };
1932
1933            let domain_obj = DomainObject {
1934                owner_account_id: 0,
1935                created_at: 0,
1936                genesis_receipt_hash: Default::default(),
1937                domain_config,
1938                domain_runtime_info: Default::default(),
1939                domain_instantiation_deposit: Default::default(),
1940            };
1941
1942            DomainRegistry::<Test>::insert(new_domain_id, domain_obj);
1943            DomainStakingSummary::<Test>::insert(
1944                new_domain_id,
1945                StakingSummary {
1946                    current_epoch_index: 0,
1947                    current_total_stake: 0,
1948                    current_operators: BTreeMap::new(),
1949                    next_operators: BTreeSet::new(),
1950                    current_epoch_rewards: BTreeMap::new(),
1951                },
1952            );
1953
1954            // nominations will not work since the is frozen
1955            let nominator_account = 100;
1956            let nominator_stake = 100 * SSC;
1957            let res = Domains::nominate_operator(
1958                RuntimeOrigin::signed(nominator_account),
1959                operator_id,
1960                nominator_stake,
1961            );
1962            assert_err!(
1963                res,
1964                Error::<Test>::Staking(crate::staking::Error::OperatorNotRegistered)
1965            );
1966        });
1967    }
1968
1969    type WithdrawWithResult = Vec<(Share, Result<(), StakingError>)>;
1970
1971    /// Expected withdrawal amount.
1972    /// Bool indicates to include exisitential deposit while asserting the final balance
1973    /// since ED is not holded back from usable balance when there are no holds on the account.
1974    type ExpectedWithdrawAmount = Option<(BalanceOf<Test>, bool)>;
1975
1976    /// The storage fund change in SSC, `true` means increase of the storage fund, `false` means decrease.
1977    type StorageFundChange = (bool, u32);
1978
1979    pub(crate) type Share = <Test as Config>::Share;
1980
1981    struct WithdrawParams {
1982        minimum_nominator_stake: BalanceOf<Test>,
1983        nominators: Vec<(NominatorId<Test>, BalanceOf<Test>)>,
1984        operator_reward: BalanceOf<Test>,
1985        nominator_id: NominatorId<Test>,
1986        withdraws: WithdrawWithResult,
1987        maybe_deposit: Option<BalanceOf<Test>>,
1988        expected_withdraw: ExpectedWithdrawAmount,
1989        expected_nominator_count_reduced_by: u32,
1990        storage_fund_change: StorageFundChange,
1991    }
1992
1993    fn withdraw_stake(params: WithdrawParams) {
1994        let WithdrawParams {
1995            minimum_nominator_stake,
1996            nominators,
1997            operator_reward,
1998            nominator_id,
1999            withdraws,
2000            maybe_deposit,
2001            expected_withdraw,
2002            expected_nominator_count_reduced_by,
2003            storage_fund_change,
2004        } = params;
2005        let domain_id = DomainId::new(0);
2006        let operator_account = 0;
2007        let pair = OperatorPair::from_seed(&[0; 32]);
2008        let mut total_balance = nominators.iter().map(|n| n.1).sum::<BalanceOf<Test>>()
2009            + operator_reward
2010            + maybe_deposit.unwrap_or(0);
2011
2012        let mut nominators = BTreeMap::from_iter(
2013            nominators
2014                .into_iter()
2015                .map(|(id, bal)| (id, (bal + ExistentialDeposit::get(), bal)))
2016                .collect::<Vec<(NominatorId<Test>, (BalanceOf<Test>, BalanceOf<Test>))>>(),
2017        );
2018
2019        let mut ext = new_test_ext();
2020        ext.execute_with(|| {
2021            let (operator_free_balance, operator_stake) =
2022                nominators.remove(&operator_account).unwrap();
2023            let (operator_id, _) = register_operator(
2024                domain_id,
2025                operator_account,
2026                operator_free_balance,
2027                operator_stake,
2028                minimum_nominator_stake,
2029                pair.public(),
2030                nominators,
2031            );
2032
2033            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
2034
2035            if !operator_reward.is_zero() {
2036                do_reward_operators::<Test>(
2037                    domain_id,
2038                    OperatorRewardSource::Dummy,
2039                    vec![operator_id].into_iter(),
2040                    operator_reward,
2041                )
2042                .unwrap();
2043            }
2044
2045            let head_domain_number = HeadDomainNumber::<Test>::get(domain_id);
2046            let nominator_count = NominatorCount::<Test>::get(operator_id);
2047
2048            if let Some(deposit_amount) = maybe_deposit {
2049                Balances::mint_into(&nominator_id, deposit_amount).unwrap();
2050                let res = Domains::nominate_operator(
2051                    RuntimeOrigin::signed(nominator_id),
2052                    operator_id,
2053                    deposit_amount,
2054                );
2055                assert_ok!(res);
2056            }
2057
2058            let operator = Operators::<Test>::get(operator_id).unwrap();
2059            let (is_storage_fund_increased, storage_fund_change_amount) = storage_fund_change;
2060            if is_storage_fund_increased {
2061                bundle_storage_fund::refund_storage_fee::<Test>(
2062                    storage_fund_change_amount as u128 * SSC,
2063                    BTreeMap::from_iter([(operator_id, 1)]),
2064                )
2065                .unwrap();
2066                assert_eq!(
2067                    operator.total_storage_fee_deposit + storage_fund_change_amount as u128 * SSC,
2068                    bundle_storage_fund::total_balance::<Test>(operator_id)
2069                );
2070                total_balance += storage_fund_change_amount as u128 * SSC;
2071            } else {
2072                bundle_storage_fund::charge_bundle_storage_fee::<Test>(
2073                    operator_id,
2074                    storage_fund_change_amount,
2075                )
2076                .unwrap();
2077                assert_eq!(
2078                    operator.total_storage_fee_deposit - storage_fund_change_amount as u128 * SSC,
2079                    bundle_storage_fund::total_balance::<Test>(operator_id)
2080                );
2081                total_balance -= storage_fund_change_amount as u128 * SSC;
2082            }
2083
2084            for (withdraw, expected_result) in withdraws {
2085                let withdraw_share_amount = STORAGE_FEE_RESERVE.left_from_one().mul_ceil(withdraw);
2086                let res = Domains::withdraw_stake(
2087                    RuntimeOrigin::signed(nominator_id),
2088                    operator_id,
2089                    WithdrawStake::Share(withdraw_share_amount),
2090                );
2091                assert_eq!(
2092                    res,
2093                    expected_result.map_err(|err| Error::<Test>::Staking(err).into())
2094                );
2095            }
2096
2097            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
2098
2099            if let Some((withdraw, include_ed)) = expected_withdraw {
2100                let previous_usable_balance = Balances::usable_balance(nominator_id);
2101
2102                // Update `HeadDomainNumber` to ensure unlock success
2103                HeadDomainNumber::<Test>::set(
2104                    domain_id,
2105                    head_domain_number
2106                        + <Test as crate::Config>::StakeWithdrawalLockingPeriod::get(),
2107                );
2108                assert_ok!(do_unlock_funds::<Test>(operator_id, nominator_id));
2109
2110                let expected_balance = if include_ed {
2111                    total_balance += crate::tests::ExistentialDeposit::get();
2112                    previous_usable_balance + withdraw + crate::tests::ExistentialDeposit::get()
2113                } else {
2114                    previous_usable_balance + withdraw
2115                };
2116
2117                assert_eq!(Balances::usable_balance(nominator_id), expected_balance);
2118
2119                // ensure there are no withdrawals left
2120                assert!(Withdrawals::<Test>::get(operator_id, nominator_id).is_none());
2121            }
2122
2123            let new_nominator_count = NominatorCount::<Test>::get(operator_id);
2124            assert_eq!(
2125                nominator_count - expected_nominator_count_reduced_by,
2126                new_nominator_count
2127            );
2128
2129            // if the nominator count reduced, then there should be no storage for deposits as well
2130            if new_nominator_count < nominator_count {
2131                assert!(Deposits::<Test>::get(operator_id, nominator_id).is_none())
2132            }
2133
2134            // The total balance is distributed in different places but never changed
2135            let operator = Operators::<Test>::get(operator_id).unwrap();
2136            assert_eq!(
2137                total_balance,
2138                Balances::usable_balance(nominator_id)
2139                    + operator.current_total_stake
2140                    + bundle_storage_fund::total_balance::<Test>(operator_id)
2141            );
2142        });
2143    }
2144
2145    #[test]
2146    fn withdraw_stake_operator_all() {
2147        withdraw_stake(WithdrawParams {
2148            minimum_nominator_stake: 10 * SSC,
2149            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2150            operator_reward: 20 * SSC,
2151            nominator_id: 0,
2152            withdraws: vec![(150 * SSC, Err(StakingError::MinimumOperatorStake))],
2153            maybe_deposit: None,
2154            expected_withdraw: None,
2155            expected_nominator_count_reduced_by: 0,
2156            storage_fund_change: (true, 0),
2157        })
2158    }
2159
2160    #[test]
2161    fn withdraw_stake_operator_below_minimum() {
2162        withdraw_stake(WithdrawParams {
2163            minimum_nominator_stake: 10 * SSC,
2164            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2165            operator_reward: 20 * SSC,
2166            nominator_id: 0,
2167            withdraws: vec![(65 * SSC, Err(StakingError::MinimumOperatorStake))],
2168            maybe_deposit: None,
2169            expected_withdraw: None,
2170            expected_nominator_count_reduced_by: 0,
2171            storage_fund_change: (true, 0),
2172        })
2173    }
2174
2175    #[test]
2176    fn withdraw_stake_operator_below_minimum_no_rewards() {
2177        withdraw_stake(WithdrawParams {
2178            minimum_nominator_stake: 10 * SSC,
2179            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2180            operator_reward: Zero::zero(),
2181            nominator_id: 0,
2182            withdraws: vec![(51 * SSC, Err(StakingError::MinimumOperatorStake))],
2183            maybe_deposit: None,
2184            expected_withdraw: None,
2185            expected_nominator_count_reduced_by: 0,
2186            storage_fund_change: (true, 0),
2187        })
2188    }
2189
2190    #[test]
2191    fn withdraw_stake_operator_above_minimum() {
2192        withdraw_stake(WithdrawParams {
2193            minimum_nominator_stake: 10 * SSC,
2194            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2195            operator_reward: 20 * SSC,
2196            nominator_id: 0,
2197            withdraws: vec![(58 * SSC, Ok(()))],
2198            // given the reward, operator will get 164.28 SSC
2199            // taking 58 shares will give this following approximate amount.
2200            maybe_deposit: None,
2201            expected_withdraw: Some((63523809519881179143, false)),
2202            expected_nominator_count_reduced_by: 0,
2203            storage_fund_change: (true, 0),
2204        })
2205    }
2206
2207    #[test]
2208    fn withdraw_stake_operator_above_minimum_multiple_withdraws_error() {
2209        withdraw_stake(WithdrawParams {
2210            minimum_nominator_stake: 10 * SSC,
2211            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2212            operator_reward: 20 * SSC,
2213            nominator_id: 0,
2214            withdraws: vec![
2215                (58 * SSC, Ok(())),
2216                (5 * SSC, Err(StakingError::MinimumOperatorStake)),
2217            ],
2218            maybe_deposit: None,
2219            expected_withdraw: Some((63523809519881179143, false)),
2220            expected_nominator_count_reduced_by: 0,
2221            storage_fund_change: (true, 0),
2222        })
2223    }
2224
2225    #[test]
2226    fn withdraw_stake_operator_above_minimum_multiple_withdraws() {
2227        withdraw_stake(WithdrawParams {
2228            minimum_nominator_stake: 10 * SSC,
2229            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2230            operator_reward: 20 * SSC,
2231            nominator_id: 0,
2232            withdraws: vec![(53 * SSC, Ok(())), (5 * SSC, Ok(()))],
2233            maybe_deposit: None,
2234            expected_withdraw: Some((63523809515796643053, false)),
2235            expected_nominator_count_reduced_by: 0,
2236            storage_fund_change: (true, 0),
2237        })
2238    }
2239
2240    #[test]
2241    fn withdraw_stake_operator_above_minimum_no_rewards() {
2242        withdraw_stake(WithdrawParams {
2243            minimum_nominator_stake: 10 * SSC,
2244            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2245            operator_reward: Zero::zero(),
2246            nominator_id: 0,
2247            withdraws: vec![(49 * SSC, Ok(()))],
2248            maybe_deposit: None,
2249            expected_withdraw: Some((48999999980000000000, false)),
2250            expected_nominator_count_reduced_by: 0,
2251            storage_fund_change: (true, 0),
2252        })
2253    }
2254
2255    #[test]
2256    fn withdraw_stake_operator_above_minimum_multiple_withdraws_no_rewards() {
2257        withdraw_stake(WithdrawParams {
2258            minimum_nominator_stake: 10 * SSC,
2259            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2260            operator_reward: Zero::zero(),
2261            nominator_id: 0,
2262            withdraws: vec![(29 * SSC, Ok(())), (20 * SSC, Ok(()))],
2263            maybe_deposit: None,
2264            expected_withdraw: Some((48999999986852892560, false)),
2265            expected_nominator_count_reduced_by: 0,
2266            storage_fund_change: (true, 0),
2267        })
2268    }
2269
2270    #[test]
2271    fn withdraw_stake_operator_above_minimum_multiple_withdraws_no_rewards_with_errors() {
2272        withdraw_stake(WithdrawParams {
2273            minimum_nominator_stake: 10 * SSC,
2274            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2275            operator_reward: Zero::zero(),
2276            nominator_id: 0,
2277            withdraws: vec![
2278                (29 * SSC, Ok(())),
2279                (20 * SSC, Ok(())),
2280                (20 * SSC, Err(StakingError::MinimumOperatorStake)),
2281            ],
2282            maybe_deposit: None,
2283            expected_withdraw: Some((48999999986852892560, false)),
2284            expected_nominator_count_reduced_by: 0,
2285            storage_fund_change: (true, 0),
2286        })
2287    }
2288
2289    #[test]
2290    fn withdraw_stake_nominator_below_minimum_with_rewards() {
2291        withdraw_stake(WithdrawParams {
2292            minimum_nominator_stake: 10 * SSC,
2293            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2294            operator_reward: 20 * SSC,
2295            nominator_id: 1,
2296            withdraws: vec![(45 * SSC, Ok(()))],
2297            // given nominator remaining stake goes below minimum
2298            // we withdraw everything, so for their 50 shares with reward,
2299            // price would be following
2300            maybe_deposit: None,
2301            expected_withdraw: Some((54761904775759637192, true)),
2302            expected_nominator_count_reduced_by: 1,
2303            storage_fund_change: (true, 0),
2304        })
2305    }
2306
2307    #[test]
2308    fn withdraw_stake_nominator_below_minimum_with_rewards_multiple_withdraws() {
2309        withdraw_stake(WithdrawParams {
2310            minimum_nominator_stake: 10 * SSC,
2311            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2312            operator_reward: 20 * SSC,
2313            nominator_id: 1,
2314            withdraws: vec![(25 * SSC, Ok(())), (20 * SSC, Ok(()))],
2315            // given nominator remaining stake goes below minimum
2316            // we withdraw everything, so for their 50 shares with reward,
2317            // price would be following
2318            maybe_deposit: None,
2319            expected_withdraw: Some((54761904775759637192, true)),
2320            expected_nominator_count_reduced_by: 1,
2321            storage_fund_change: (true, 0),
2322        })
2323    }
2324
2325    #[test]
2326    fn withdraw_stake_nominator_below_minimum_with_rewards_multiple_withdraws_with_errors() {
2327        withdraw_stake(WithdrawParams {
2328            minimum_nominator_stake: 10 * SSC,
2329            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2330            operator_reward: 20 * SSC,
2331            nominator_id: 1,
2332            withdraws: vec![
2333                (25 * SSC, Ok(())),
2334                (20 * SSC, Ok(())),
2335                (20 * SSC, Err(StakingError::InsufficientShares)),
2336            ],
2337            // given nominator remaining stake goes below minimum
2338            // we withdraw everything, so for their 50 shares with reward,
2339            // price would be following
2340            maybe_deposit: None,
2341            expected_withdraw: Some((54761904775759637192, true)),
2342            expected_nominator_count_reduced_by: 1,
2343            storage_fund_change: (true, 0),
2344        })
2345    }
2346
2347    #[test]
2348    fn withdraw_stake_nominator_below_minimum_no_reward() {
2349        withdraw_stake(WithdrawParams {
2350            minimum_nominator_stake: 10 * SSC,
2351            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2352            operator_reward: Zero::zero(),
2353            nominator_id: 1,
2354            withdraws: vec![(45 * SSC, Ok(()))],
2355            maybe_deposit: None,
2356            expected_withdraw: Some((50 * SSC, true)),
2357            expected_nominator_count_reduced_by: 1,
2358            storage_fund_change: (true, 0),
2359        })
2360    }
2361
2362    #[test]
2363    fn withdraw_stake_nominator_below_minimum_no_reward_multiple_rewards() {
2364        withdraw_stake(WithdrawParams {
2365            minimum_nominator_stake: 10 * SSC,
2366            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2367            operator_reward: Zero::zero(),
2368            nominator_id: 1,
2369            withdraws: vec![(25 * SSC, Ok(())), (20 * SSC, Ok(()))],
2370            maybe_deposit: None,
2371            expected_withdraw: Some((50 * SSC, true)),
2372            expected_nominator_count_reduced_by: 1,
2373            storage_fund_change: (true, 0),
2374        })
2375    }
2376
2377    #[test]
2378    fn withdraw_stake_nominator_below_minimum_no_reward_multiple_rewards_with_errors() {
2379        withdraw_stake(WithdrawParams {
2380            minimum_nominator_stake: 10 * SSC,
2381            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2382            operator_reward: Zero::zero(),
2383            nominator_id: 1,
2384            withdraws: vec![
2385                (25 * SSC, Ok(())),
2386                (20 * SSC, Ok(())),
2387                (20 * SSC, Err(StakingError::InsufficientShares)),
2388            ],
2389            maybe_deposit: None,
2390            expected_withdraw: Some((50 * SSC, true)),
2391            expected_nominator_count_reduced_by: 1,
2392            storage_fund_change: (true, 0),
2393        })
2394    }
2395
2396    #[test]
2397    fn withdraw_stake_nominator_above_minimum() {
2398        withdraw_stake(WithdrawParams {
2399            minimum_nominator_stake: 10 * SSC,
2400            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2401            operator_reward: 20 * SSC,
2402            nominator_id: 1,
2403            withdraws: vec![(40 * SSC, Ok(()))],
2404            maybe_deposit: None,
2405            expected_withdraw: Some((43809523820607709753, false)),
2406            expected_nominator_count_reduced_by: 0,
2407            storage_fund_change: (true, 0),
2408        })
2409    }
2410
2411    #[test]
2412    fn withdraw_stake_nominator_above_minimum_multiple_withdraws() {
2413        withdraw_stake(WithdrawParams {
2414            minimum_nominator_stake: 10 * SSC,
2415            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2416            operator_reward: 20 * SSC,
2417            nominator_id: 1,
2418            withdraws: vec![(35 * SSC, Ok(())), (5 * SSC, Ok(()))],
2419            maybe_deposit: None,
2420            expected_withdraw: Some((43809523819607709753, false)),
2421            expected_nominator_count_reduced_by: 0,
2422            storage_fund_change: (true, 0),
2423        })
2424    }
2425
2426    #[test]
2427    fn withdraw_stake_nominator_above_minimum_withdraw_all_multiple_withdraws_error() {
2428        withdraw_stake(WithdrawParams {
2429            minimum_nominator_stake: 10 * SSC,
2430            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2431            operator_reward: 20 * SSC,
2432            nominator_id: 1,
2433            withdraws: vec![
2434                (35 * SSC, Ok(())),
2435                (5 * SSC, Ok(())),
2436                (15 * SSC, Err(StakingError::InsufficientShares)),
2437            ],
2438            maybe_deposit: None,
2439            expected_withdraw: Some((43809523819607709753, false)),
2440            expected_nominator_count_reduced_by: 0,
2441            storage_fund_change: (true, 0),
2442        })
2443    }
2444
2445    #[test]
2446    fn withdraw_stake_nominator_above_minimum_no_rewards() {
2447        withdraw_stake(WithdrawParams {
2448            minimum_nominator_stake: 10 * SSC,
2449            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2450            operator_reward: Zero::zero(),
2451            nominator_id: 1,
2452            withdraws: vec![(39 * SSC, Ok(()))],
2453            maybe_deposit: None,
2454            expected_withdraw: Some((39 * SSC, false)),
2455            expected_nominator_count_reduced_by: 0,
2456            storage_fund_change: (true, 0),
2457        })
2458    }
2459
2460    #[test]
2461    fn withdraw_stake_nominator_above_minimum_no_rewards_multiple_withdraws() {
2462        withdraw_stake(WithdrawParams {
2463            minimum_nominator_stake: 10 * SSC,
2464            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2465            operator_reward: Zero::zero(),
2466            nominator_id: 1,
2467            withdraws: vec![(35 * SSC, Ok(())), (5 * SSC - 100000000000, Ok(()))],
2468            maybe_deposit: None,
2469            expected_withdraw: Some((39999999898000000000, false)),
2470            expected_nominator_count_reduced_by: 0,
2471            storage_fund_change: (true, 0),
2472        })
2473    }
2474
2475    #[test]
2476    fn withdraw_stake_nominator_above_minimum_no_rewards_multiple_withdraws_with_errors() {
2477        withdraw_stake(WithdrawParams {
2478            minimum_nominator_stake: 10 * SSC,
2479            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2480            operator_reward: Zero::zero(),
2481            nominator_id: 1,
2482            withdraws: vec![
2483                (35 * SSC, Ok(())),
2484                (5 * SSC - 100000000000, Ok(())),
2485                (15 * SSC, Err(StakingError::InsufficientShares)),
2486            ],
2487            maybe_deposit: None,
2488            expected_withdraw: Some((39999999898000000000, false)),
2489            expected_nominator_count_reduced_by: 0,
2490            storage_fund_change: (true, 0),
2491        })
2492    }
2493
2494    #[test]
2495    fn withdraw_stake_nominator_no_rewards_multiple_withdraws_with_error_min_nominator_stake() {
2496        withdraw_stake(WithdrawParams {
2497            minimum_nominator_stake: 10 * SSC,
2498            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2499            operator_reward: Zero::zero(),
2500            nominator_id: 1,
2501            withdraws: vec![
2502                (35 * SSC, Ok(())),
2503                (5 * SSC - 100000000000, Ok(())),
2504                (10 * SSC, Err(StakingError::MinimumNominatorStake)),
2505            ],
2506            maybe_deposit: Some(2 * SSC),
2507            expected_withdraw: Some((39999999898000000000, false)),
2508            expected_nominator_count_reduced_by: 0,
2509            storage_fund_change: (true, 0),
2510        })
2511    }
2512
2513    #[test]
2514    fn withdraw_stake_nominator_with_rewards_multiple_withdraws_with_error_min_nominator_stake() {
2515        withdraw_stake(WithdrawParams {
2516            minimum_nominator_stake: 10 * SSC,
2517            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2518            operator_reward: 20 * SSC,
2519            nominator_id: 1,
2520            withdraws: vec![
2521                (35 * SSC, Ok(())),
2522                (5 * SSC, Ok(())),
2523                (10 * SSC, Err(StakingError::MinimumNominatorStake)),
2524            ],
2525            // given nominator remaining stake goes below minimum
2526            // we withdraw everything, so for their 50 shares with reward,
2527            // price would be following
2528            maybe_deposit: Some(2 * SSC),
2529            expected_withdraw: Some((43809523819607709753, false)),
2530            expected_nominator_count_reduced_by: 0,
2531            storage_fund_change: (true, 0),
2532        })
2533    }
2534
2535    #[test]
2536    fn withdraw_stake_nominator_zero_amount() {
2537        withdraw_stake(WithdrawParams {
2538            minimum_nominator_stake: 10 * SSC,
2539            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2540            operator_reward: Zero::zero(),
2541            nominator_id: 1,
2542            withdraws: vec![(0, Err(StakingError::ZeroWithdraw))],
2543            maybe_deposit: None,
2544            expected_withdraw: None,
2545            expected_nominator_count_reduced_by: 0,
2546            storage_fund_change: (true, 0),
2547        })
2548    }
2549
2550    #[test]
2551    fn withdraw_stake_nominator_all_with_storage_fee_profit() {
2552        withdraw_stake(WithdrawParams {
2553            minimum_nominator_stake: 10 * SSC,
2554            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2555            operator_reward: Zero::zero(),
2556            nominator_id: 1,
2557            withdraws: vec![(50 * SSC, Ok(()))],
2558            maybe_deposit: None,
2559            // The storage fund increased 50% (i.e. 21 * SSC) thus the nominator make 50%
2560            // storage fee profit i.e. 5 * SSC with rounding dust deducted
2561            storage_fund_change: (true, 21),
2562            expected_withdraw: Some((54999999994000000000, true)),
2563            expected_nominator_count_reduced_by: 1,
2564        })
2565    }
2566
2567    #[test]
2568    fn withdraw_stake_nominator_all_with_storage_fee_loss() {
2569        withdraw_stake(WithdrawParams {
2570            minimum_nominator_stake: 10 * SSC,
2571            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2572            operator_reward: Zero::zero(),
2573            nominator_id: 1,
2574            withdraws: vec![(50 * SSC, Ok(()))],
2575            maybe_deposit: None,
2576            // The storage fund decreased 50% (i.e. 21 * SSC) thus the nominator loss 50%
2577            // storage fee deposit i.e. 5 * SSC with rounding dust deducted
2578            storage_fund_change: (false, 21),
2579            expected_withdraw: Some((44999999998000000000, true)),
2580            expected_nominator_count_reduced_by: 1,
2581        })
2582    }
2583
2584    #[test]
2585    fn withdraw_stake_nominator_all_with_storage_fee_loss_all() {
2586        withdraw_stake(WithdrawParams {
2587            minimum_nominator_stake: 10 * SSC,
2588            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2589            operator_reward: Zero::zero(),
2590            nominator_id: 1,
2591            withdraws: vec![(50 * SSC, Ok(()))],
2592            maybe_deposit: None,
2593            // The storage fund decreased 100% (i.e. 42 * SSC) thus the nominator loss 100%
2594            // storage fee deposit i.e. 10 * SSC
2595            storage_fund_change: (false, 42),
2596            expected_withdraw: Some((40 * SSC, true)),
2597            expected_nominator_count_reduced_by: 1,
2598        })
2599    }
2600
2601    #[test]
2602    fn withdraw_stake_nominator_multiple_withdraws_with_storage_fee_profit() {
2603        withdraw_stake(WithdrawParams {
2604            minimum_nominator_stake: 10 * SSC,
2605            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2606            operator_reward: Zero::zero(),
2607            nominator_id: 1,
2608            withdraws: vec![(5 * SSC, Ok(())), (10 * SSC, Ok(())), (15 * SSC, Ok(()))],
2609            maybe_deposit: None,
2610            // The storage fund increased 50% (i.e. 21 * SSC) thus the nominator make 50%
2611            // storage fee profit i.e. 5 * SSC with rounding dust deducted, withdraw 60% of
2612            // the stake and the storage fee profit
2613            storage_fund_change: (true, 21),
2614            expected_withdraw: Some((30 * SSC + 2999999855527204374, false)),
2615            expected_nominator_count_reduced_by: 0,
2616        })
2617    }
2618
2619    #[test]
2620    fn withdraw_stake_nominator_multiple_withdraws_with_storage_fee_loss() {
2621        withdraw_stake(WithdrawParams {
2622            minimum_nominator_stake: 10 * SSC,
2623            nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)],
2624            operator_reward: Zero::zero(),
2625            nominator_id: 1,
2626            withdraws: vec![(5 * SSC, Ok(())), (5 * SSC, Ok(())), (10 * SSC, Ok(()))],
2627            maybe_deposit: None,
2628            // The storage fund increased 50% (i.e. 21 * SSC) thus the nominator loss 50%
2629            // storage fee i.e. 5 * SSC with rounding dust deducted, withdraw 40% of
2630            // the stake and 40% of the storage fee loss are deducted
2631            storage_fund_change: (false, 21),
2632            expected_withdraw: Some((20 * SSC - 2 * SSC - 33331097576, false)),
2633            expected_nominator_count_reduced_by: 0,
2634        })
2635    }
2636
2637    #[test]
2638    fn unlock_multiple_withdrawals() {
2639        let domain_id = DomainId::new(0);
2640        let operator_account = 1;
2641        let operator_free_balance = 250 * SSC;
2642        let operator_stake = 200 * SSC;
2643        let pair = OperatorPair::from_seed(&[0; 32]);
2644        let nominator_account = 2;
2645        let nominator_free_balance = 150 * SSC;
2646        let nominator_stake = 100 * SSC;
2647
2648        let nominators = vec![
2649            (operator_account, (operator_free_balance, operator_stake)),
2650            (nominator_account, (nominator_free_balance, nominator_stake)),
2651        ];
2652
2653        let total_deposit = 300 * SSC;
2654        let init_total_stake = STORAGE_FEE_RESERVE.left_from_one() * total_deposit;
2655        let init_total_storage_fund = STORAGE_FEE_RESERVE * total_deposit;
2656
2657        let mut ext = new_test_ext();
2658        ext.execute_with(|| {
2659            let (operator_id, _) = register_operator(
2660                domain_id,
2661                operator_account,
2662                operator_free_balance,
2663                operator_stake,
2664                10 * SSC,
2665                pair.public(),
2666                BTreeMap::from_iter(nominators),
2667            );
2668
2669            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
2670            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2671            assert_eq!(domain_stake_summary.current_total_stake, init_total_stake);
2672
2673            let operator = Operators::<Test>::get(operator_id).unwrap();
2674            assert_eq!(operator.current_total_stake, init_total_stake);
2675            assert_eq!(operator.total_storage_fee_deposit, init_total_storage_fund);
2676            assert_eq!(
2677                operator.total_storage_fee_deposit,
2678                bundle_storage_fund::total_balance::<Test>(operator_id)
2679            );
2680
2681            let amount_per_withdraw = init_total_stake / 100;
2682            let head_domain_number = HeadDomainNumber::<Test>::get(domain_id);
2683
2684            // Request `WithdrawalLimit - 1` number of withdrawal
2685            for _ in 1..<Test as crate::Config>::WithdrawalLimit::get() {
2686                do_withdraw_stake::<Test>(
2687                    operator_id,
2688                    nominator_account,
2689                    WithdrawStake::Stake(amount_per_withdraw),
2690                )
2691                .unwrap();
2692                do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
2693            }
2694            // Increase the head domain number by 1
2695            HeadDomainNumber::<Test>::set(domain_id, head_domain_number + 1);
2696
2697            // All withdrawals of a given nominator submitted in the same epoch will merge into one,
2698            // so we submit can submit as many as we want even though the withdrawal limit is met
2699            for _ in 0..5 {
2700                do_withdraw_stake::<Test>(
2701                    operator_id,
2702                    nominator_account,
2703                    WithdrawStake::Stake(amount_per_withdraw),
2704                )
2705                .unwrap();
2706            }
2707            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
2708
2709            // After the withdrawal limit is met, any new withdraw will be rejected in the next epoch
2710            assert_err!(
2711                do_withdraw_stake::<Test>(
2712                    operator_id,
2713                    nominator_account,
2714                    WithdrawStake::Stake(amount_per_withdraw),
2715                ),
2716                StakingError::TooManyWithdrawals
2717            );
2718            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2719            Withdrawals::<Test>::try_mutate(operator_id, nominator_account, |maybe_withdrawal| {
2720                let withdrawal = maybe_withdrawal.as_mut().unwrap();
2721                do_convert_previous_epoch_withdrawal::<Test>(
2722                    operator_id,
2723                    withdrawal,
2724                    domain_stake_summary.current_epoch_index,
2725                )
2726                .unwrap();
2727                assert_eq!(
2728                    withdrawal.withdrawals.len() as u32,
2729                    <Test as crate::Config>::WithdrawalLimit::get()
2730                );
2731                Ok::<(), StakingError>(())
2732            })
2733            .unwrap();
2734
2735            // Make the first set of withdrawals pass the unlock period then unlock fund
2736            HeadDomainNumber::<Test>::set(
2737                domain_id,
2738                head_domain_number + <Test as crate::Config>::StakeWithdrawalLockingPeriod::get(),
2739            );
2740            let total_balance = Balances::usable_balance(nominator_account);
2741            assert_ok!(do_unlock_funds::<Test>(operator_id, nominator_account));
2742            assert_eq!(
2743                Balances::usable_balance(nominator_account) + 60246126106, // `60246126106` is a minior rounding dust
2744                total_balance
2745                    + (<Test as crate::Config>::WithdrawalLimit::get() as u128 - 1) * total_deposit
2746                        / 100
2747            );
2748            let withdrawal = Withdrawals::<Test>::get(operator_id, nominator_account).unwrap();
2749            assert_eq!(withdrawal.withdrawals.len(), 1);
2750
2751            // Make the second set of withdrawals pass the unlock period then unlock funds
2752            HeadDomainNumber::<Test>::set(
2753                domain_id,
2754                head_domain_number
2755                    + <Test as crate::Config>::StakeWithdrawalLockingPeriod::get()
2756                    + 1,
2757            );
2758            let total_balance = Balances::usable_balance(nominator_account);
2759            assert_ok!(do_unlock_funds::<Test>(operator_id, nominator_account));
2760            assert_eq!(
2761                Balances::usable_balance(nominator_account) + 18473897451, // `18473897451` is a minor rounding dust
2762                total_balance + 5 * total_deposit / 100
2763            );
2764            assert!(Withdrawals::<Test>::get(operator_id, nominator_account).is_none());
2765        });
2766    }
2767
2768    #[test]
2769    fn slash_operator() {
2770        let domain_id = DomainId::new(0);
2771        let operator_account = 1;
2772        let operator_free_balance = 250 * SSC;
2773        let operator_stake = 200 * SSC;
2774        let operator_extra_deposit = 40 * SSC;
2775        let pair = OperatorPair::from_seed(&[0; 32]);
2776        let nominator_account = 2;
2777        let nominator_free_balance = 150 * SSC;
2778        let nominator_stake = 100 * SSC;
2779        let nominator_extra_deposit = 40 * SSC;
2780
2781        let nominators = vec![
2782            (operator_account, (operator_free_balance, operator_stake)),
2783            (nominator_account, (nominator_free_balance, nominator_stake)),
2784        ];
2785
2786        let unlocking = vec![(operator_account, 10 * SSC), (nominator_account, 10 * SSC)];
2787
2788        let deposits = vec![
2789            (operator_account, operator_extra_deposit),
2790            (nominator_account, nominator_extra_deposit),
2791        ];
2792
2793        let init_total_stake = STORAGE_FEE_RESERVE.left_from_one() * 300 * SSC;
2794        let init_total_storage_fund = STORAGE_FEE_RESERVE * 300 * SSC;
2795
2796        let mut ext = new_test_ext();
2797        ext.execute_with(|| {
2798            let (operator_id, _) = register_operator(
2799                domain_id,
2800                operator_account,
2801                operator_free_balance,
2802                operator_stake,
2803                10 * SSC,
2804                pair.public(),
2805                BTreeMap::from_iter(nominators),
2806            );
2807
2808            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
2809            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2810            assert_eq!(domain_stake_summary.current_total_stake, init_total_stake);
2811
2812            let operator = Operators::<Test>::get(operator_id).unwrap();
2813            assert_eq!(operator.current_total_stake, init_total_stake);
2814            assert_eq!(operator.total_storage_fee_deposit, init_total_storage_fund);
2815            assert_eq!(
2816                operator.total_storage_fee_deposit,
2817                bundle_storage_fund::total_balance::<Test>(operator_id)
2818            );
2819
2820            for unlock in &unlocking {
2821                do_withdraw_stake::<Test>(operator_id, unlock.0, WithdrawStake::Share(unlock.1))
2822                    .unwrap();
2823            }
2824
2825            do_reward_operators::<Test>(
2826                domain_id,
2827                OperatorRewardSource::Dummy,
2828                vec![operator_id].into_iter(),
2829                20 * SSC,
2830            )
2831            .unwrap();
2832            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
2833
2834            // Manually convert previous withdrawal in share to balance
2835            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2836            for id in [operator_account, nominator_account] {
2837                Withdrawals::<Test>::try_mutate(operator_id, id, |maybe_withdrawal| {
2838                    do_convert_previous_epoch_withdrawal::<Test>(
2839                        operator_id,
2840                        maybe_withdrawal.as_mut().unwrap(),
2841                        domain_stake_summary.current_epoch_index,
2842                    )
2843                })
2844                .unwrap();
2845            }
2846
2847            // post epoch transition, domain stake has 21.666 amount reduced and storage fund has 5 amount reduced
2848            // due to withdrawal of 20 shares
2849            let operator = Operators::<Test>::get(operator_id).unwrap();
2850            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2851            let operator_withdrawal =
2852                Withdrawals::<Test>::get(operator_id, operator_account).unwrap();
2853            let nominator_withdrawal =
2854                Withdrawals::<Test>::get(operator_id, nominator_account).unwrap();
2855
2856            let total_deposit =
2857                domain_stake_summary.current_total_stake + operator.total_storage_fee_deposit;
2858            let total_stake_withdrawal = operator_withdrawal.total_withdrawal_amount
2859                + nominator_withdrawal.total_withdrawal_amount;
2860            let total_storage_fee_withdrawal = operator_withdrawal.withdrawals[0]
2861                .storage_fee_refund
2862                + nominator_withdrawal.withdrawals[0].storage_fee_refund;
2863            assert_eq!(293333333331527777778, total_deposit,);
2864            assert_eq!(21666666668472222222, total_stake_withdrawal);
2865            assert_eq!(5000000000000000000, total_storage_fee_withdrawal);
2866            assert_eq!(
2867                320 * SSC,
2868                total_deposit + total_stake_withdrawal + total_storage_fee_withdrawal
2869            );
2870            assert_eq!(
2871                operator.total_storage_fee_deposit,
2872                bundle_storage_fund::total_balance::<Test>(operator_id)
2873            );
2874
2875            for deposit in deposits {
2876                do_nominate_operator::<Test>(operator_id, deposit.0, deposit.1).unwrap();
2877            }
2878
2879            do_mark_operators_as_slashed::<Test>(
2880                vec![operator_id],
2881                SlashedReason::InvalidBundle(1),
2882            )
2883            .unwrap();
2884
2885            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2886            assert!(!domain_stake_summary.next_operators.contains(&operator_id));
2887
2888            let operator = Operators::<Test>::get(operator_id).unwrap();
2889            assert_eq!(
2890                *operator.status::<Test>(operator_id),
2891                OperatorStatus::Slashed
2892            );
2893
2894            let pending_slashes = PendingSlashes::<Test>::get(domain_id).unwrap();
2895            assert!(pending_slashes.contains(&operator_id));
2896
2897            assert_eq!(
2898                Balances::total_balance(&crate::tests::TreasuryAccount::get()),
2899                0
2900            );
2901
2902            do_slash_operator::<Test>(domain_id, MAX_NOMINATORS_TO_SLASH).unwrap();
2903            assert_eq!(PendingSlashes::<Test>::get(domain_id), None);
2904            assert_eq!(Operators::<Test>::get(operator_id), None);
2905            assert_eq!(OperatorIdOwner::<Test>::get(operator_id), None);
2906
2907            assert_eq!(
2908                Balances::total_balance(&operator_account),
2909                operator_free_balance - operator_stake
2910            );
2911            assert_eq!(
2912                Balances::total_balance(&nominator_account),
2913                nominator_free_balance - nominator_stake
2914            );
2915
2916            assert!(Balances::total_balance(&crate::tests::TreasuryAccount::get()) >= 320 * SSC);
2917            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), 0);
2918        });
2919    }
2920
2921    #[test]
2922    fn slash_operator_with_more_than_max_nominators_to_slash() {
2923        let domain_id = DomainId::new(0);
2924        let operator_account = 1;
2925        let operator_free_balance = 250 * SSC;
2926        let operator_stake = 200 * SSC;
2927        let operator_extra_deposit = 40 * SSC;
2928        let operator_extra_withdraw = 5 * SSC;
2929        let pair = OperatorPair::from_seed(&[0; 32]);
2930
2931        let nominator_accounts: Vec<crate::tests::AccountId> = (2..22).collect();
2932        let nominator_free_balance = 150 * SSC;
2933        let nominator_stake = 100 * SSC;
2934        let nominator_extra_deposit = 40 * SSC;
2935        let nominator_extra_withdraw = 5 * SSC;
2936
2937        let mut nominators = vec![(operator_account, (operator_free_balance, operator_stake))];
2938        for nominator_account in nominator_accounts.clone() {
2939            nominators.push((nominator_account, (nominator_free_balance, nominator_stake)))
2940        }
2941
2942        let last_nominator_account = nominator_accounts.last().cloned().unwrap();
2943        let unlocking = vec![
2944            (operator_account, 10 * SSC),
2945            (last_nominator_account, 10 * SSC),
2946        ];
2947
2948        let deposits = vec![
2949            (operator_account, operator_extra_deposit),
2950            (last_nominator_account, nominator_extra_deposit),
2951        ];
2952        let withdrawals = vec![
2953            (operator_account, operator_extra_withdraw),
2954            (last_nominator_account, nominator_extra_withdraw),
2955        ];
2956
2957        let init_total_stake = STORAGE_FEE_RESERVE.left_from_one()
2958            * (200 + (100 * nominator_accounts.len() as u128))
2959            * SSC;
2960        let init_total_storage_fund =
2961            STORAGE_FEE_RESERVE * (200 + (100 * nominator_accounts.len() as u128)) * SSC;
2962
2963        let mut ext = new_test_ext();
2964        ext.execute_with(|| {
2965            let (operator_id, _) = register_operator(
2966                domain_id,
2967                operator_account,
2968                operator_free_balance,
2969                operator_stake,
2970                10 * SSC,
2971                pair.public(),
2972                BTreeMap::from_iter(nominators),
2973            );
2974
2975            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
2976            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2977            assert_eq!(domain_stake_summary.current_total_stake, init_total_stake);
2978
2979            let operator = Operators::<Test>::get(operator_id).unwrap();
2980            assert_eq!(operator.current_total_stake, init_total_stake);
2981            assert_eq!(operator.total_storage_fee_deposit, init_total_storage_fund);
2982            assert_eq!(
2983                operator.total_storage_fee_deposit,
2984                bundle_storage_fund::total_balance::<Test>(operator_id)
2985            );
2986
2987            for unlock in &unlocking {
2988                do_withdraw_stake::<Test>(operator_id, unlock.0, WithdrawStake::Share(unlock.1))
2989                    .unwrap();
2990            }
2991
2992            do_reward_operators::<Test>(
2993                domain_id,
2994                OperatorRewardSource::Dummy,
2995                vec![operator_id].into_iter(),
2996                20 * SSC,
2997            )
2998            .unwrap();
2999            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
3000
3001            // Manually convert previous withdrawal in share to balance
3002            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
3003            for id in [operator_account, last_nominator_account] {
3004                Withdrawals::<Test>::try_mutate(operator_id, id, |maybe_withdrawal| {
3005                    do_convert_previous_epoch_withdrawal::<Test>(
3006                        operator_id,
3007                        maybe_withdrawal.as_mut().unwrap(),
3008                        domain_stake_summary.current_epoch_index,
3009                    )
3010                })
3011                .unwrap();
3012            }
3013
3014            // post epoch transition, domain stake has 21.666 amount reduced and storage fund has 5 amount reduced
3015            // due to withdrawal of 20 shares
3016            let operator = Operators::<Test>::get(operator_id).unwrap();
3017            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
3018            let operator_withdrawal =
3019                Withdrawals::<Test>::get(operator_id, operator_account).unwrap();
3020            let nominator_withdrawal =
3021                Withdrawals::<Test>::get(operator_id, last_nominator_account).unwrap();
3022
3023            let total_deposit =
3024                domain_stake_summary.current_total_stake + operator.total_storage_fee_deposit;
3025            let total_stake_withdrawal = operator_withdrawal.total_withdrawal_amount
3026                + nominator_withdrawal.total_withdrawal_amount;
3027            let total_storage_fee_withdrawal = operator_withdrawal.withdrawals[0]
3028                .storage_fee_refund
3029                + nominator_withdrawal.withdrawals[0].storage_fee_refund;
3030            assert_eq!(2194772727253419421470, total_deposit,);
3031            assert_eq!(20227272746580578530, total_stake_withdrawal);
3032            assert_eq!(5000000000000000000, total_storage_fee_withdrawal);
3033            assert_eq!(
3034                2220 * SSC,
3035                total_deposit + total_stake_withdrawal + total_storage_fee_withdrawal
3036            );
3037
3038            assert_eq!(
3039                operator.total_storage_fee_deposit,
3040                bundle_storage_fund::total_balance::<Test>(operator_id)
3041            );
3042
3043            for deposit in deposits {
3044                do_nominate_operator::<Test>(operator_id, deposit.0, deposit.1).unwrap();
3045            }
3046            for withdrawal in withdrawals {
3047                do_withdraw_stake::<Test>(
3048                    operator_id,
3049                    withdrawal.0,
3050                    WithdrawStake::Stake(withdrawal.1),
3051                )
3052                .unwrap();
3053            }
3054
3055            do_mark_operators_as_slashed::<Test>(
3056                vec![operator_id],
3057                SlashedReason::InvalidBundle(1),
3058            )
3059            .unwrap();
3060
3061            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
3062
3063            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
3064            assert!(!domain_stake_summary.next_operators.contains(&operator_id));
3065
3066            let operator = Operators::<Test>::get(operator_id).unwrap();
3067            assert_eq!(
3068                *operator.status::<Test>(operator_id),
3069                OperatorStatus::Slashed
3070            );
3071
3072            let pending_slashes = PendingSlashes::<Test>::get(domain_id).unwrap();
3073            assert!(pending_slashes.contains(&operator_id));
3074
3075            assert_eq!(
3076                Balances::total_balance(&crate::tests::TreasuryAccount::get()),
3077                0
3078            );
3079
3080            // since we only slash 10 nominators a time but we have a total of 21 nominators,
3081            // do 3 iterations
3082            do_slash_operator::<Test>(domain_id, MAX_NOMINATORS_TO_SLASH).unwrap();
3083            do_slash_operator::<Test>(domain_id, MAX_NOMINATORS_TO_SLASH).unwrap();
3084            do_slash_operator::<Test>(domain_id, MAX_NOMINATORS_TO_SLASH).unwrap();
3085
3086            assert_eq!(PendingSlashes::<Test>::get(domain_id), None);
3087            assert_eq!(Operators::<Test>::get(operator_id), None);
3088            assert_eq!(OperatorIdOwner::<Test>::get(operator_id), None);
3089
3090            assert_eq!(
3091                Balances::total_balance(&operator_account),
3092                operator_free_balance - operator_stake
3093            );
3094            for nominator_account in nominator_accounts {
3095                assert_eq!(
3096                    Balances::total_balance(&nominator_account),
3097                    nominator_free_balance - nominator_stake
3098                );
3099            }
3100
3101            assert!(Balances::total_balance(&crate::tests::TreasuryAccount::get()) >= 2220 * SSC);
3102            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), 0);
3103        });
3104    }
3105
3106    #[test]
3107    fn slash_operators() {
3108        let domain_id = DomainId::new(0);
3109        let operator_free_balance = 250 * SSC;
3110        let operator_stake = 200 * SSC;
3111
3112        let operator_account_1 = 1;
3113        let operator_account_2 = 2;
3114        let operator_account_3 = 3;
3115
3116        let pair_1 = OperatorPair::from_seed(&[0; 32]);
3117        let pair_2 = OperatorPair::from_seed(&[1; 32]);
3118        let pair_3 = OperatorPair::from_seed(&[2; 32]);
3119
3120        let mut ext = new_test_ext();
3121        ext.execute_with(|| {
3122            let (operator_id_1, _) = register_operator(
3123                domain_id,
3124                operator_account_1,
3125                operator_free_balance,
3126                operator_stake,
3127                10 * SSC,
3128                pair_1.public(),
3129                Default::default(),
3130            );
3131
3132            let (operator_id_2, _) = register_operator(
3133                domain_id,
3134                operator_account_2,
3135                operator_free_balance,
3136                operator_stake,
3137                10 * SSC,
3138                pair_2.public(),
3139                Default::default(),
3140            );
3141
3142            let (operator_id_3, _) = register_operator(
3143                domain_id,
3144                operator_account_3,
3145                operator_free_balance,
3146                operator_stake,
3147                10 * SSC,
3148                pair_3.public(),
3149                Default::default(),
3150            );
3151
3152            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
3153            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
3154            assert!(domain_stake_summary.next_operators.contains(&operator_id_1));
3155            assert!(domain_stake_summary.next_operators.contains(&operator_id_2));
3156            assert!(domain_stake_summary.next_operators.contains(&operator_id_3));
3157            assert_eq!(
3158                domain_stake_summary.current_total_stake,
3159                STORAGE_FEE_RESERVE.left_from_one() * 600 * SSC
3160            );
3161            for operator_id in [operator_id_1, operator_id_2, operator_id_3] {
3162                let operator = Operators::<Test>::get(operator_id).unwrap();
3163                assert_eq!(
3164                    operator.total_storage_fee_deposit,
3165                    STORAGE_FEE_RESERVE * operator_stake
3166                );
3167                assert_eq!(
3168                    operator.total_storage_fee_deposit,
3169                    bundle_storage_fund::total_balance::<Test>(operator_id)
3170                );
3171            }
3172
3173            do_mark_operators_as_slashed::<Test>(
3174                vec![operator_id_1],
3175                SlashedReason::InvalidBundle(1),
3176            )
3177            .unwrap();
3178            do_mark_operators_as_slashed::<Test>(
3179                vec![operator_id_2],
3180                SlashedReason::InvalidBundle(2),
3181            )
3182            .unwrap();
3183            do_mark_operators_as_slashed::<Test>(
3184                vec![operator_id_3],
3185                SlashedReason::InvalidBundle(3),
3186            )
3187            .unwrap();
3188
3189            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
3190            assert!(!domain_stake_summary.next_operators.contains(&operator_id_1));
3191            assert!(!domain_stake_summary.next_operators.contains(&operator_id_2));
3192            assert!(!domain_stake_summary.next_operators.contains(&operator_id_3));
3193
3194            let operator = Operators::<Test>::get(operator_id_1).unwrap();
3195            assert_eq!(
3196                *operator.status::<Test>(operator_id_1),
3197                OperatorStatus::Slashed
3198            );
3199
3200            let operator = Operators::<Test>::get(operator_id_2).unwrap();
3201            assert_eq!(
3202                *operator.status::<Test>(operator_id_2),
3203                OperatorStatus::Slashed
3204            );
3205
3206            let operator = Operators::<Test>::get(operator_id_3).unwrap();
3207            assert_eq!(
3208                *operator.status::<Test>(operator_id_3),
3209                OperatorStatus::Slashed
3210            );
3211
3212            assert_eq!(
3213                Balances::total_balance(&crate::tests::TreasuryAccount::get()),
3214                0
3215            );
3216
3217            let slashed_operators = PendingSlashes::<Test>::get(domain_id).unwrap();
3218            slashed_operators.into_iter().for_each(|_| {
3219                do_slash_operator::<Test>(domain_id, MAX_NOMINATORS_TO_SLASH).unwrap();
3220            });
3221
3222            assert_eq!(PendingSlashes::<Test>::get(domain_id), None);
3223            assert_eq!(Operators::<Test>::get(operator_id_1), None);
3224            assert_eq!(OperatorIdOwner::<Test>::get(operator_id_1), None);
3225            assert_eq!(Operators::<Test>::get(operator_id_2), None);
3226            assert_eq!(OperatorIdOwner::<Test>::get(operator_id_2), None);
3227            assert_eq!(Operators::<Test>::get(operator_id_3), None);
3228            assert_eq!(OperatorIdOwner::<Test>::get(operator_id_3), None);
3229
3230            assert_eq!(
3231                Balances::total_balance(&crate::tests::TreasuryAccount::get()),
3232                600 * SSC
3233            );
3234            for operator_id in [operator_id_1, operator_id_2, operator_id_3] {
3235                assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), 0);
3236            }
3237        });
3238    }
3239
3240    #[test]
3241    fn bundle_storage_fund_charged_and_refund_storege_fee() {
3242        let domain_id = DomainId::new(0);
3243        let operator_account = 1;
3244        let operator_free_balance = 150 * SSC;
3245        let operator_total_stake = 100 * SSC;
3246        let operator_stake = 80 * SSC;
3247        let operator_storage_fee_deposit = 20 * SSC;
3248        let pair = OperatorPair::from_seed(&[0; 32]);
3249        let nominator_account = 2;
3250
3251        let mut ext = new_test_ext();
3252        ext.execute_with(|| {
3253            let (operator_id, _) = register_operator(
3254                domain_id,
3255                operator_account,
3256                operator_free_balance,
3257                operator_total_stake,
3258                SSC,
3259                pair.public(),
3260                BTreeMap::default(),
3261            );
3262
3263            let domain_staking_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
3264            assert_eq!(domain_staking_summary.current_total_stake, operator_stake);
3265
3266            let operator = Operators::<Test>::get(operator_id).unwrap();
3267            assert_eq!(operator.current_total_stake, operator_stake);
3268            assert_eq!(operator.current_total_shares, operator_stake);
3269            assert_eq!(
3270                operator.total_storage_fee_deposit,
3271                operator_storage_fee_deposit
3272            );
3273
3274            // Drain the bundle storage fund
3275            bundle_storage_fund::charge_bundle_storage_fee::<Test>(
3276                operator_id,
3277                // the transaction fee is one SSC per byte thus div SSC here
3278                (operator_storage_fee_deposit / SSC) as u32,
3279            )
3280            .unwrap();
3281            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), 0);
3282            assert_err!(
3283                bundle_storage_fund::charge_bundle_storage_fee::<Test>(operator_id, 1,),
3284                bundle_storage_fund::Error::BundleStorageFeePayment
3285            );
3286
3287            // The operator add more stake thus add deposit to the bundle storage fund
3288            do_nominate_operator::<Test>(operator_id, operator_account, 5 * SSC).unwrap();
3289            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), SSC);
3290
3291            bundle_storage_fund::charge_bundle_storage_fee::<Test>(operator_id, 1).unwrap();
3292            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), 0);
3293
3294            // New nominator add deposit to the bundle storage fund
3295            Balances::set_balance(&nominator_account, 100 * SSC);
3296            do_nominate_operator::<Test>(operator_id, nominator_account, 5 * SSC).unwrap();
3297            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), SSC);
3298
3299            bundle_storage_fund::charge_bundle_storage_fee::<Test>(operator_id, 1).unwrap();
3300            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), 0);
3301
3302            // Refund of the storage fee add deposit to the bundle storage fund
3303            bundle_storage_fund::refund_storage_fee::<Test>(
3304                10 * SSC,
3305                BTreeMap::from_iter([(operator_id, 1), (operator_id + 1, 9)]),
3306            )
3307            .unwrap();
3308            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), SSC);
3309
3310            // The operator `operator_id + 1` not exist thus the refund storage fee added to treasury
3311            assert_eq!(
3312                Balances::total_balance(&crate::tests::TreasuryAccount::get()),
3313                9 * SSC
3314            );
3315
3316            bundle_storage_fund::charge_bundle_storage_fee::<Test>(operator_id, 1).unwrap();
3317            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), 0);
3318        });
3319    }
3320
3321    #[test]
3322    fn zero_amount_deposit_and_withdraw() {
3323        let domain_id = DomainId::new(0);
3324        let operator_account = 1;
3325        let operator_free_balance = 250 * SSC;
3326        let operator_stake = 200 * SSC;
3327        let pair = OperatorPair::from_seed(&[0; 32]);
3328        let nominator_account = 2;
3329        let nominator_free_balance = 150 * SSC;
3330        let nominator_stake = 100 * SSC;
3331
3332        let nominators = vec![
3333            (operator_account, (operator_free_balance, operator_stake)),
3334            (nominator_account, (nominator_free_balance, nominator_stake)),
3335        ];
3336
3337        let total_deposit = 300 * SSC;
3338        let init_total_stake = STORAGE_FEE_RESERVE.left_from_one() * total_deposit;
3339
3340        let mut ext = new_test_ext();
3341        ext.execute_with(|| {
3342            let (operator_id, _) = register_operator(
3343                domain_id,
3344                operator_account,
3345                operator_free_balance,
3346                operator_stake,
3347                10 * SSC,
3348                pair.public(),
3349                BTreeMap::from_iter(nominators),
3350            );
3351
3352            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
3353            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
3354            assert_eq!(domain_stake_summary.current_total_stake, init_total_stake);
3355
3356            // Zero deposit should be reject
3357            assert_err!(
3358                do_nominate_operator::<Test>(operator_id, nominator_account, 0),
3359                StakingError::ZeroDeposit
3360            );
3361
3362            // Zero withdraw should be reject
3363            assert_err!(
3364                do_withdraw_stake::<Test>(operator_id, nominator_account, WithdrawStake::Stake(0)),
3365                StakingError::ZeroWithdraw
3366            );
3367            assert_err!(
3368                do_withdraw_stake::<Test>(operator_id, nominator_account, WithdrawStake::Share(0)),
3369                StakingError::ZeroWithdraw
3370            );
3371            assert_err!(
3372                do_withdraw_stake::<Test>(
3373                    operator_id,
3374                    nominator_account,
3375                    WithdrawStake::Percent(Percent::from_percent(0)),
3376                ),
3377                StakingError::ZeroWithdraw
3378            );
3379
3380            // Withdraw all
3381            do_withdraw_stake::<Test>(operator_id, nominator_account, WithdrawStake::All).unwrap();
3382            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
3383
3384            // `WithdrawStake::Percent` and `WithdrawStake::All` should convert to zero and should be rejected
3385            assert_err!(
3386                do_withdraw_stake::<Test>(
3387                    operator_id,
3388                    nominator_account,
3389                    WithdrawStake::Percent(Percent::from_percent(1)),
3390                ),
3391                StakingError::ZeroWithdraw
3392            );
3393            assert_err!(
3394                do_withdraw_stake::<Test>(operator_id, nominator_account, WithdrawStake::All,),
3395                StakingError::ZeroWithdraw
3396            );
3397        });
3398    }
3399
3400    #[test]
3401    fn deposit_and_withdraw_should_be_rejected_due_to_missing_share_price() {
3402        let domain_id = DomainId::new(0);
3403        let operator_account = 1;
3404        let operator_free_balance = 250 * SSC;
3405        let operator_stake = 200 * SSC;
3406        let pair = OperatorPair::from_seed(&[0; 32]);
3407        let nominator_account = 2;
3408        let nominator_free_balance = 150 * SSC;
3409        let nominator_stake = 100 * SSC;
3410
3411        let nominators = vec![
3412            (operator_account, (operator_free_balance, operator_stake)),
3413            (nominator_account, (nominator_free_balance, nominator_stake)),
3414        ];
3415
3416        let total_deposit = 300 * SSC;
3417        let init_total_stake = STORAGE_FEE_RESERVE.left_from_one() * total_deposit;
3418
3419        let mut ext = new_test_ext();
3420        ext.execute_with(|| {
3421            let (operator_id, _) = register_operator(
3422                domain_id,
3423                operator_account,
3424                operator_free_balance,
3425                operator_stake,
3426                10 * SSC,
3427                pair.public(),
3428                BTreeMap::from_iter(nominators),
3429            );
3430
3431            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
3432            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
3433            assert_eq!(domain_stake_summary.current_total_stake, init_total_stake);
3434
3435            do_nominate_operator::<Test>(operator_id, nominator_account, 5 * SSC).unwrap();
3436            do_withdraw_stake::<Test>(
3437                operator_id,
3438                nominator_account,
3439                WithdrawStake::Stake(3 * SSC),
3440            )
3441            .unwrap();
3442
3443            // Completed current epoch
3444            let previous_epoch = do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
3445            // Remove the epoch share price intentionally
3446            OperatorEpochSharePrice::<Test>::remove(
3447                operator_id,
3448                DomainEpoch::from((domain_id, previous_epoch.completed_epoch_index)),
3449            );
3450
3451            // Both deposit and withdraw should fail due to the share price is missing unexpectly
3452            assert_err!(
3453                do_nominate_operator::<Test>(operator_id, nominator_account, SSC),
3454                StakingError::MissingOperatorEpochSharePrice
3455            );
3456            assert_err!(
3457                do_withdraw_stake::<Test>(
3458                    operator_id,
3459                    nominator_account,
3460                    WithdrawStake::Percent(Percent::from_percent(10))
3461                ),
3462                StakingError::MissingOperatorEpochSharePrice
3463            );
3464        });
3465    }
3466
3467    #[test]
3468    fn allowed_use_default_share_price_if_missing() {
3469        let domain_id = DomainId::new(0);
3470        let operator_account = 1;
3471        let operator_free_balance = 250 * SSC;
3472        let operator_stake = 200 * SSC;
3473        let pair = OperatorPair::from_seed(&[0; 32]);
3474        let nominator_account = 2;
3475        let nominator_free_balance = 150 * SSC;
3476        let nominator_stake = 100 * SSC;
3477
3478        let nominators = vec![
3479            (operator_account, (operator_free_balance, operator_stake)),
3480            (nominator_account, (nominator_free_balance, nominator_stake)),
3481        ];
3482
3483        let total_deposit = 300 * SSC;
3484        let init_total_stake = STORAGE_FEE_RESERVE.left_from_one() * total_deposit;
3485
3486        let mut ext = new_test_ext();
3487        ext.execute_with(|| {
3488            let (operator_id, _) = register_operator(
3489                domain_id,
3490                operator_account,
3491                operator_free_balance,
3492                operator_stake,
3493                10 * SSC,
3494                pair.public(),
3495                BTreeMap::from_iter(nominators),
3496            );
3497
3498            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
3499            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
3500            assert_eq!(domain_stake_summary.current_total_stake, init_total_stake);
3501            let head_domain_number = HeadDomainNumber::<Test>::get(domain_id);
3502
3503            do_withdraw_stake::<Test>(
3504                operator_id,
3505                nominator_account,
3506                WithdrawStake::Stake(3 * SSC),
3507            )
3508            .unwrap();
3509
3510            // Increase the head domain number by 1
3511            HeadDomainNumber::<Test>::set(domain_id, head_domain_number + 1);
3512
3513            // Completed current epoch and remove the epoch share price intentionally
3514            let allowed_epoch = do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
3515            OperatorEpochSharePrice::<Test>::remove(
3516                operator_id,
3517                DomainEpoch::from((domain_id, allowed_epoch.completed_epoch_index)),
3518            );
3519            // Allow using the current share price as default for epoch <= `allowed_epoch`
3520            AllowedDefaultSharePriceEpoch::<Test>::set(Some(DomainEpoch::from((
3521                domain_id,
3522                allowed_epoch.completed_epoch_index,
3523            ))));
3524
3525            // Start another withdraw which should convert the previous withdrawal successfully
3526            do_withdraw_stake::<Test>(
3527                operator_id,
3528                nominator_account,
3529                WithdrawStake::Percent(Percent::from_percent(10)),
3530            )
3531            .unwrap();
3532            // Unlock this withdrawal should be success
3533            HeadDomainNumber::<Test>::set(
3534                domain_id,
3535                head_domain_number
3536                    + <Test as crate::Config>::StakeWithdrawalLockingPeriod::get()
3537                    + 1,
3538            );
3539            assert_ok!(do_unlock_funds::<Test>(operator_id, nominator_account));
3540
3541            // Completed one more epoch and remove the epoch share price intentionally
3542            let previous_epoch = do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
3543            OperatorEpochSharePrice::<Test>::remove(
3544                operator_id,
3545                DomainEpoch::from((domain_id, previous_epoch.completed_epoch_index)),
3546            );
3547            // This withdraw should fail due to the share price is missing unexpectly and it
3548            // is started after `allowed_epoch`
3549            assert_err!(
3550                do_withdraw_stake::<Test>(
3551                    operator_id,
3552                    nominator_account,
3553                    WithdrawStake::Percent(Percent::from_percent(10))
3554                ),
3555                StakingError::MissingOperatorEpochSharePrice
3556            );
3557        });
3558    }
3559}