1#[cfg(not(feature = "std"))]
4extern crate alloc;
5
6use crate::block_tree::invalid_bundle_authors_for_receipt;
7use crate::bundle_storage_fund::{self, deposit_reserve_for_storage_fund};
8use crate::pallet::{
9    Deposits, DomainRegistry, DomainStakingSummary, HeadDomainNumber, NextOperatorId,
10    OperatorIdOwner, Operators, PendingSlashes, PendingStakingOperationCount, Withdrawals,
11};
12use crate::staking_epoch::{mint_funds, mint_into_treasury};
13use crate::{
14    BalanceOf, Config, DeactivatedOperators, DepositOnHold, DeregisteredOperators,
15    DomainBlockNumberFor, DomainHashingFor, Event, ExecutionReceiptOf, HoldIdentifier,
16    InvalidBundleAuthors, NominatorId, OperatorEpochSharePrice, OperatorHighestSlot, Pallet,
17    ReceiptHashFor, SlashedReason, WeightInfo,
18};
19use frame_support::traits::fungible::{Inspect, MutateHold};
20use frame_support::traits::tokens::{Fortitude, Precision, Preservation};
21use frame_support::{PalletError, StorageDoubleMap, ensure};
22use frame_system::pallet_prelude::BlockNumberFor;
23use parity_scale_codec::{Decode, Encode};
24use scale_info::TypeInfo;
25use sp_core::{Get, sr25519};
26use sp_domains::{DomainId, EpochIndex, OperatorId, OperatorPublicKey, OperatorRewardSource};
27use sp_runtime::traits::{CheckedAdd, CheckedSub, Zero};
28use sp_runtime::{PerThing, Percent, Perquintill, Saturating, Weight};
29use sp_std::collections::btree_map::BTreeMap;
30use sp_std::collections::btree_set::BTreeSet;
31use sp_std::collections::vec_deque::VecDeque;
32use sp_std::vec::IntoIter;
33
34#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Default)]
36pub(crate) struct Deposit<Share: Copy, Balance: Copy> {
37    pub(crate) known: KnownDeposit<Share, Balance>,
38    pub(crate) pending: Option<PendingDeposit<Balance>>,
39}
40
41#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Default)]
44pub struct SharePrice(pub Perquintill);
45
46impl SharePrice {
47    pub(crate) fn new<T: Config>(
50        total_shares: T::Share,
51        total_stake: BalanceOf<T>,
52    ) -> Result<Self, Error> {
53        if total_shares > total_stake.into() {
54            Err(Error::ShareOverflow)
56        } else if total_stake.is_zero() || total_shares.is_zero() {
57            Err(Error::ZeroSharePrice)
59        } else {
60            Ok(SharePrice(Perquintill::from_rational(
61                total_shares.into(),
62                total_stake,
63            )))
64        }
65    }
66
67    pub(crate) fn stake_to_shares<T: Config>(&self, stake: BalanceOf<T>) -> T::Share {
70        self.0.mul_floor(stake).into()
71    }
72
73    pub(crate) fn shares_to_stake<T: Config>(&self, shares: T::Share) -> BalanceOf<T> {
76        self.0
81            .plus_epsilon()
84            .saturating_reciprocal_mul_floor(shares.into())
85    }
86
87    pub(crate) fn one() -> Self {
89        Self(Perquintill::one())
90    }
91}
92
93#[derive(TypeInfo, Debug, Encode, Decode, Copy, Clone, PartialEq, Eq)]
95pub struct DomainEpoch(DomainId, EpochIndex);
96
97impl DomainEpoch {
98    pub(crate) fn deconstruct(self) -> (DomainId, EpochIndex) {
99        (self.0, self.1)
100    }
101}
102
103impl From<(DomainId, EpochIndex)> for DomainEpoch {
104    fn from((domain_id, epoch_idx): (DomainId, EpochIndex)) -> Self {
105        Self(domain_id, epoch_idx)
106    }
107}
108
109pub struct NewDeposit<Balance> {
110    pub(crate) staking: Balance,
111    pub(crate) storage_fee_deposit: Balance,
112}
113
114#[derive(TypeInfo, Debug, Encode, Decode, Copy, Clone, PartialEq, Eq, Default)]
116pub(crate) struct KnownDeposit<Share: Copy, Balance: Copy> {
117    pub(crate) shares: Share,
118    pub(crate) storage_fee_deposit: Balance,
119}
120
121#[derive(TypeInfo, Debug, Encode, Decode, Copy, Clone, PartialEq, Eq)]
123pub(crate) struct PendingDeposit<Balance: Copy> {
124    pub(crate) effective_domain_epoch: DomainEpoch,
125    pub(crate) amount: Balance,
126    pub(crate) storage_fee_deposit: Balance,
127}
128
129impl<Balance: Copy + CheckedAdd> PendingDeposit<Balance> {
130    fn total(&self) -> Result<Balance, Error> {
131        self.amount
132            .checked_add(&self.storage_fee_deposit)
133            .ok_or(Error::BalanceOverflow)
134    }
135}
136
137#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Default)]
139pub(crate) struct Withdrawal<Balance, Share, DomainBlockNumber> {
140    pub(crate) total_withdrawal_amount: Balance,
143    pub(crate) total_storage_fee_withdrawal: Balance,
145    pub(crate) withdrawals: VecDeque<WithdrawalInBalance<DomainBlockNumber, Balance>>,
147    pub(crate) withdrawal_in_shares: Option<WithdrawalInShares<DomainBlockNumber, Share, Balance>>,
150}
151
152#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
153pub(crate) struct WithdrawalInBalance<DomainBlockNumber, Balance> {
154    pub(crate) unlock_at_confirmed_domain_block_number: DomainBlockNumber,
155    pub(crate) amount_to_unlock: Balance,
156    pub(crate) storage_fee_refund: Balance,
157}
158
159#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
160pub(crate) struct WithdrawalInShares<DomainBlockNumber, Share, Balance> {
161    pub(crate) domain_epoch: DomainEpoch,
162    pub(crate) unlock_at_confirmed_domain_block_number: DomainBlockNumber,
163    pub(crate) shares: Share,
164    pub(crate) storage_fee_refund: Balance,
165}
166
167#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
168pub struct OperatorDeregisteredInfo<DomainBlockNumber> {
169    pub domain_epoch: DomainEpoch,
170    pub unlock_at_confirmed_domain_block_number: DomainBlockNumber,
171}
172
173impl<DomainBlockNumber> From<(DomainId, EpochIndex, DomainBlockNumber)>
174    for OperatorDeregisteredInfo<DomainBlockNumber>
175{
176    fn from(value: (DomainId, EpochIndex, DomainBlockNumber)) -> Self {
177        OperatorDeregisteredInfo {
178            domain_epoch: (value.0, value.1).into(),
179            unlock_at_confirmed_domain_block_number: value.2,
180        }
181    }
182}
183
184#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
186pub enum OperatorStatus<DomainBlockNumber, ReceiptHash> {
187    #[codec(index = 0)]
188    Registered,
189    #[codec(index = 1)]
191    Deregistered(OperatorDeregisteredInfo<DomainBlockNumber>),
192    #[codec(index = 2)]
193    Slashed,
194    #[codec(index = 3)]
195    PendingSlash,
196    #[codec(index = 4)]
197    InvalidBundle(ReceiptHash),
198    #[codec(index = 5)]
201    Deactivated(EpochIndex),
202}
203
204#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
206pub struct Operator<Balance, Share, DomainBlockNumber, ReceiptHash> {
207    pub signing_key: OperatorPublicKey,
208    pub current_domain_id: DomainId,
209    pub next_domain_id: DomainId,
210    pub minimum_nominator_stake: Balance,
211    pub nomination_tax: Percent,
212    pub current_total_stake: Balance,
214    pub current_total_shares: Share,
216    partial_status: OperatorStatus<DomainBlockNumber, ReceiptHash>,
220    pub deposits_in_epoch: Balance,
222    pub withdrawals_in_epoch: Share,
224    pub total_storage_fee_deposit: Balance,
226}
227
228impl<Balance, Share, DomainBlockNumber, ReceiptHash>
229    Operator<Balance, Share, DomainBlockNumber, ReceiptHash>
230{
231    pub fn status<T: Config>(
232        &self,
233        operator_id: OperatorId,
234    ) -> &OperatorStatus<DomainBlockNumber, ReceiptHash> {
235        if matches!(self.partial_status, OperatorStatus::Slashed) {
236            &OperatorStatus::Slashed
237        } else if Pallet::<T>::is_operator_pending_to_slash(self.current_domain_id, operator_id) {
238            &OperatorStatus::PendingSlash
239        } else {
240            &self.partial_status
241        }
242    }
243
244    pub fn update_status(&mut self, new_status: OperatorStatus<DomainBlockNumber, ReceiptHash>) {
245        self.partial_status = new_status;
246    }
247
248    pub fn is_operator_registered_or_deactivated<T: Config>(
250        &self,
251        operator_id: OperatorId,
252    ) -> bool {
253        let status = self.status::<T>(operator_id);
254        matches!(
255            status,
256            OperatorStatus::Registered | OperatorStatus::Deactivated(_)
257        )
258    }
259
260    pub fn can_operator_submit_bundle<T: Config>(&self, operator_id: OperatorId) -> bool {
262        let status = self.status::<T>(operator_id);
263        !matches!(
265            status,
266            OperatorStatus::Slashed
267                | OperatorStatus::PendingSlash
268                | OperatorStatus::InvalidBundle(_)
269                | OperatorStatus::Deactivated(_)
270        )
271    }
272}
273
274#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
275pub struct StakingSummary<OperatorId, Balance> {
276    pub current_epoch_index: EpochIndex,
278    pub current_total_stake: Balance,
280    pub current_operators: BTreeMap<OperatorId, Balance>,
282    pub next_operators: BTreeSet<OperatorId>,
284    pub current_epoch_rewards: BTreeMap<OperatorId, Balance>,
286}
287
288#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
289pub struct OperatorConfig<Balance> {
290    pub signing_key: OperatorPublicKey,
291    pub minimum_nominator_stake: Balance,
292    pub nomination_tax: Percent,
293}
294
295#[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
296pub enum Error {
297    MaximumOperatorId,
298    DomainNotInitialized,
299    PendingOperatorSwitch,
300    InsufficientBalance,
301    InsufficientShares,
302    ZeroWithdraw,
303    BalanceFreeze,
304    MinimumOperatorStake,
305    UnknownOperator,
306    MinimumNominatorStake,
307    BalanceOverflow,
308    BalanceUnderflow,
309    NotOperatorOwner,
310    OperatorNotRegistered,
311    OperatorNotDeactivated,
312    UnknownNominator,
313    MissingOperatorOwner,
314    MintBalance,
315    BlockNumberOverflow,
316    RemoveLock,
317    EpochOverflow,
318    ShareUnderflow,
319    ShareOverflow,
320    TooManyPendingStakingOperation,
321    OperatorNotAllowed,
322    InvalidOperatorSigningKey,
323    MissingOperatorEpochSharePrice,
324    MissingWithdrawal,
325    EpochNotComplete,
326    UnlockPeriodNotComplete,
327    OperatorNotDeregistered,
328    BundleStorageFund(bundle_storage_fund::Error),
329    UnconfirmedER,
330    TooManyWithdrawals,
331    ZeroDeposit,
332    ZeroSharePrice,
333    ReactivationDelayPeriodIncomplete,
334    OperatorNotRegisterdOrDeactivated,
335}
336
337fn note_pending_staking_operation<T: Config>(domain_id: DomainId) -> Result<(), Error> {
340    let pending_op_count = PendingStakingOperationCount::<T>::get(domain_id);
341
342    ensure!(
343        pending_op_count < T::MaxPendingStakingOperation::get(),
344        Error::TooManyPendingStakingOperation
345    );
346
347    PendingStakingOperationCount::<T>::set(domain_id, pending_op_count.saturating_add(1));
348
349    Ok(())
350}
351
352pub fn do_register_operator<T: Config>(
353    operator_owner: T::AccountId,
354    domain_id: DomainId,
355    amount: BalanceOf<T>,
356    config: OperatorConfig<BalanceOf<T>>,
357) -> Result<(OperatorId, EpochIndex), Error> {
358    note_pending_staking_operation::<T>(domain_id)?;
359
360    DomainStakingSummary::<T>::try_mutate(domain_id, |maybe_domain_stake_summary| {
361        ensure!(
362            config.signing_key != OperatorPublicKey::from(sr25519::Public::default()),
363            Error::InvalidOperatorSigningKey
364        );
365
366        ensure!(
367            config.minimum_nominator_stake >= T::MinNominatorStake::get(),
368            Error::MinimumNominatorStake
369        );
370
371        let domain_obj = DomainRegistry::<T>::get(domain_id).ok_or(Error::DomainNotInitialized)?;
372        ensure!(
373            domain_obj
374                .domain_config
375                .operator_allow_list
376                .is_operator_allowed(&operator_owner),
377            Error::OperatorNotAllowed
378        );
379
380        let operator_id = NextOperatorId::<T>::get();
381        let next_operator_id = operator_id.checked_add(1).ok_or(Error::MaximumOperatorId)?;
382        NextOperatorId::<T>::set(next_operator_id);
383
384        OperatorIdOwner::<T>::insert(operator_id, operator_owner.clone());
385
386        ensure!(
388            amount >= T::MinOperatorStake::get(),
389            Error::MinimumOperatorStake
390        );
391
392        let new_deposit =
393            deposit_reserve_for_storage_fund::<T>(operator_id, &operator_owner, amount)
394                .map_err(Error::BundleStorageFund)?;
395
396        hold_deposit::<T>(&operator_owner, operator_id, new_deposit.staking)?;
397
398        let domain_stake_summary = maybe_domain_stake_summary
399            .as_mut()
400            .ok_or(Error::DomainNotInitialized)?;
401
402        let OperatorConfig {
403            signing_key,
404            minimum_nominator_stake,
405            nomination_tax,
406        } = config;
407
408        let first_share_price = SharePrice::one();
417        let operator = Operator {
418            signing_key: signing_key.clone(),
419            current_domain_id: domain_id,
420            next_domain_id: domain_id,
421            minimum_nominator_stake,
422            nomination_tax,
423            current_total_stake: new_deposit.staking,
424            current_total_shares: first_share_price.stake_to_shares::<T>(new_deposit.staking),
425            partial_status: OperatorStatus::Registered,
426            deposits_in_epoch: Zero::zero(),
428            withdrawals_in_epoch: Zero::zero(),
429            total_storage_fee_deposit: new_deposit.storage_fee_deposit,
430        };
431        Operators::<T>::insert(operator_id, operator);
432        OperatorEpochSharePrice::<T>::insert(
433            operator_id,
434            DomainEpoch::from((domain_id, domain_stake_summary.current_epoch_index)),
435            first_share_price,
436        );
437
438        domain_stake_summary.next_operators.insert(operator_id);
440        let current_domain_epoch = (domain_id, domain_stake_summary.current_epoch_index).into();
442        do_calculate_previous_epoch_deposit_shares_and_add_new_deposit::<T>(
443            operator_id,
444            operator_owner,
445            current_domain_epoch,
446            new_deposit,
447            None,
448        )?;
449
450        Ok((operator_id, domain_stake_summary.current_epoch_index))
451    })
452}
453
454pub(crate) fn do_calculate_previous_epoch_deposit_shares_and_add_new_deposit<T: Config>(
459    operator_id: OperatorId,
460    nominator_id: NominatorId<T>,
461    current_domain_epoch: DomainEpoch,
462    new_deposit: NewDeposit<BalanceOf<T>>,
463    required_minimum_nominator_stake: Option<BalanceOf<T>>,
464) -> Result<(), Error> {
465    Deposits::<T>::try_mutate(operator_id, nominator_id, |maybe_deposit| {
466        let mut deposit = maybe_deposit.take().unwrap_or_default();
467        do_convert_previous_epoch_deposits::<T>(operator_id, &mut deposit, current_domain_epoch.1)?;
468
469        let pending_deposit = match deposit.pending {
471            None => PendingDeposit {
472                effective_domain_epoch: current_domain_epoch,
473                amount: new_deposit.staking,
474                storage_fee_deposit: new_deposit.storage_fee_deposit,
475            },
476            Some(pending_deposit) => PendingDeposit {
477                effective_domain_epoch: current_domain_epoch,
478                amount: pending_deposit
479                    .amount
480                    .checked_add(&new_deposit.staking)
481                    .ok_or(Error::BalanceOverflow)?,
482                storage_fee_deposit: pending_deposit
483                    .storage_fee_deposit
484                    .checked_add(&new_deposit.storage_fee_deposit)
485                    .ok_or(Error::BalanceOverflow)?,
486            },
487        };
488
489        if deposit.known.shares.is_zero()
490            && let Some(minimum_nominator_stake) = required_minimum_nominator_stake
491        {
492            ensure!(
493                pending_deposit.total()? >= minimum_nominator_stake,
494                Error::MinimumNominatorStake
495            );
496        }
497
498        deposit.pending = Some(pending_deposit);
499        *maybe_deposit = Some(deposit);
500        Ok(())
501    })
502}
503
504pub(crate) fn do_convert_previous_epoch_deposits<T: Config>(
505    operator_id: OperatorId,
506    deposit: &mut Deposit<T::Share, BalanceOf<T>>,
507    current_domain_epoch_index: EpochIndex,
508) -> Result<(), Error> {
509    let epoch_share_price = match deposit.pending {
511        None => return Ok(()),
512        Some(pending_deposit) => {
513            match OperatorEpochSharePrice::<T>::get(
514                operator_id,
515                pending_deposit.effective_domain_epoch,
516            ) {
517                Some(p) => p,
518                None => {
519                    ensure!(
520                        pending_deposit.effective_domain_epoch.1 >= current_domain_epoch_index,
521                        Error::MissingOperatorEpochSharePrice
522                    );
523                    return Ok(());
524                }
525            }
526        }
527    };
528
529    if let Some(PendingDeposit {
530        amount,
531        storage_fee_deposit,
532        ..
533    }) = deposit.pending.take()
534    {
535        let new_shares = epoch_share_price.stake_to_shares::<T>(amount);
536        deposit.known.shares = deposit
537            .known
538            .shares
539            .checked_add(&new_shares)
540            .ok_or(Error::ShareOverflow)?;
541        deposit.known.storage_fee_deposit = deposit
542            .known
543            .storage_fee_deposit
544            .checked_add(&storage_fee_deposit)
545            .ok_or(Error::BalanceOverflow)?;
546    }
547
548    Ok(())
549}
550
551pub(crate) fn do_convert_previous_epoch_withdrawal<T: Config>(
557    operator_id: OperatorId,
558    withdrawal: &mut Withdrawal<BalanceOf<T>, T::Share, DomainBlockNumberFor<T>>,
559    current_domain_epoch_index: EpochIndex,
560) -> Result<(), Error> {
561    let epoch_share_price = match withdrawal.withdrawal_in_shares.as_ref() {
562        None => return Ok(()),
563        Some(withdraw) => {
564            if withdraw.domain_epoch.1 >= current_domain_epoch_index {
566                return Ok(());
567            }
568
569            match OperatorEpochSharePrice::<T>::get(operator_id, withdraw.domain_epoch) {
570                Some(p) => p,
571                None => return Err(Error::MissingOperatorEpochSharePrice),
572            }
573        }
574    };
575
576    if let Some(WithdrawalInShares {
577        unlock_at_confirmed_domain_block_number,
578        shares,
579        storage_fee_refund,
580        domain_epoch: _,
581    }) = withdrawal.withdrawal_in_shares.take()
582    {
583        let withdrawal_amount = epoch_share_price.shares_to_stake::<T>(shares);
584        withdrawal.total_withdrawal_amount = withdrawal
585            .total_withdrawal_amount
586            .checked_add(&withdrawal_amount)
587            .ok_or(Error::BalanceOverflow)?;
588
589        let withdraw_in_balance = WithdrawalInBalance {
590            unlock_at_confirmed_domain_block_number,
591            amount_to_unlock: withdrawal_amount,
592            storage_fee_refund,
593        };
594        withdrawal.withdrawals.push_back(withdraw_in_balance);
595    }
596
597    Ok(())
598}
599
600pub(crate) fn do_nominate_operator<T: Config>(
601    operator_id: OperatorId,
602    nominator_id: T::AccountId,
603    amount: BalanceOf<T>,
604) -> Result<(), Error> {
605    ensure!(!amount.is_zero(), Error::ZeroDeposit);
606
607    Operators::<T>::try_mutate(operator_id, |maybe_operator| {
608        let operator = maybe_operator.as_mut().ok_or(Error::UnknownOperator)?;
609
610        ensure!(
611            *operator.status::<T>(operator_id) == OperatorStatus::Registered,
612            Error::OperatorNotRegistered
613        );
614
615        if operator.deposits_in_epoch.is_zero() && operator.withdrawals_in_epoch.is_zero() {
617            note_pending_staking_operation::<T>(operator.current_domain_id)?;
618        }
619
620        let domain_stake_summary = DomainStakingSummary::<T>::get(operator.current_domain_id)
621            .ok_or(Error::DomainNotInitialized)?;
622
623        let new_deposit = deposit_reserve_for_storage_fund::<T>(operator_id, &nominator_id, amount)
625            .map_err(Error::BundleStorageFund)?;
626
627        hold_deposit::<T>(&nominator_id, operator_id, new_deposit.staking)?;
628        Pallet::<T>::deposit_event(Event::OperatorNominated {
629            operator_id,
630            nominator_id: nominator_id.clone(),
631            amount: new_deposit.staking,
632        });
633
634        operator.deposits_in_epoch = operator
636            .deposits_in_epoch
637            .checked_add(&new_deposit.staking)
638            .ok_or(Error::BalanceOverflow)?;
639
640        operator.total_storage_fee_deposit = operator
642            .total_storage_fee_deposit
643            .checked_add(&new_deposit.storage_fee_deposit)
644            .ok_or(Error::BalanceOverflow)?;
645
646        let current_domain_epoch = (
647            operator.current_domain_id,
648            domain_stake_summary.current_epoch_index,
649        )
650            .into();
651
652        do_calculate_previous_epoch_deposit_shares_and_add_new_deposit::<T>(
653            operator_id,
654            nominator_id,
655            current_domain_epoch,
656            new_deposit,
657            Some(operator.minimum_nominator_stake),
658        )?;
659
660        Ok(())
661    })
662}
663
664pub(crate) fn hold_deposit<T: Config>(
665    who: &T::AccountId,
666    operator_id: OperatorId,
667    amount: BalanceOf<T>,
668) -> Result<(), Error> {
669    ensure!(
671        T::Currency::reducible_balance(who, Preservation::Preserve, Fortitude::Polite) >= amount,
672        Error::InsufficientBalance
673    );
674
675    DepositOnHold::<T>::try_mutate((operator_id, who), |deposit_on_hold| {
676        *deposit_on_hold = deposit_on_hold
677            .checked_add(&amount)
678            .ok_or(Error::BalanceOverflow)?;
679        Ok(())
680    })?;
681
682    let pending_deposit_hold_id = T::HoldIdentifier::staking_staked();
683    T::Currency::hold(&pending_deposit_hold_id, who, amount).map_err(|_| Error::BalanceFreeze)?;
684
685    Ok(())
686}
687
688pub(crate) fn do_deregister_operator<T: Config>(
691    operator_owner: T::AccountId,
692    operator_id: OperatorId,
693) -> Result<Weight, Error> {
694    ensure!(
695        OperatorIdOwner::<T>::get(operator_id) == Some(operator_owner),
696        Error::NotOperatorOwner
697    );
698
699    let mut weight = T::WeightInfo::deregister_operator();
700    Operators::<T>::try_mutate(operator_id, |maybe_operator| {
701        let operator = maybe_operator.as_mut().ok_or(Error::UnknownOperator)?;
702
703        ensure!(
704            operator.is_operator_registered_or_deactivated::<T>(operator_id),
705            Error::OperatorNotRegisterdOrDeactivated
706        );
707
708        DomainStakingSummary::<T>::try_mutate(
709            operator.current_domain_id,
710            |maybe_domain_stake_summary| {
711                let stake_summary = maybe_domain_stake_summary
712                    .as_mut()
713                    .ok_or(Error::DomainNotInitialized)?;
714
715                let head_domain_number = HeadDomainNumber::<T>::get(operator.current_domain_id);
716                let unlock_operator_at_domain_block_number = head_domain_number
717                    .checked_add(&T::StakeWithdrawalLockingPeriod::get())
718                    .ok_or(Error::BlockNumberOverflow)?;
719                let operator_deregister_info = (
720                    operator.current_domain_id,
721                    stake_summary.current_epoch_index,
722                    unlock_operator_at_domain_block_number,
723                )
724                    .into();
725
726                if matches!(operator.partial_status, OperatorStatus::Deactivated(_)) {
730                    DeactivatedOperators::<T>::mutate(operator.current_domain_id, |operators| {
731                        operators.remove(&operator_id);
732                    });
733                    weight = T::WeightInfo::deregister_deactivated_operator();
734                }
735
736                operator.update_status(OperatorStatus::Deregistered(operator_deregister_info));
737
738                stake_summary.next_operators.remove(&operator_id);
739
740                DeregisteredOperators::<T>::mutate(operator.current_domain_id, |operators| {
741                    operators.insert(operator_id)
742                });
743                Ok(())
744            },
745        )
746    })?;
747
748    Ok(weight)
749}
750
751pub(crate) fn do_deactivate_operator<T: Config>(operator_id: OperatorId) -> Result<(), Error> {
756    Operators::<T>::try_mutate(operator_id, |maybe_operator| {
757        let operator = maybe_operator.as_mut().ok_or(Error::UnknownOperator)?;
758
759        ensure!(
760            *operator.status::<T>(operator_id) == OperatorStatus::Registered,
761            Error::OperatorNotRegistered
762        );
763
764        DomainStakingSummary::<T>::try_mutate(
765            operator.current_domain_id,
766            |maybe_domain_stake_summary| {
767                let stake_summary = maybe_domain_stake_summary
768                    .as_mut()
769                    .ok_or(Error::DomainNotInitialized)?;
770
771                let current_epoch = stake_summary.current_epoch_index;
772                let reactivation_delay = current_epoch
773                    .checked_add(T::OperatorActivationDelayInEpochs::get())
774                    .ok_or(Error::EpochOverflow)?;
775
776                operator.update_status(OperatorStatus::Deactivated(reactivation_delay));
777
778                if stake_summary
782                    .current_operators
783                    .remove(&operator_id)
784                    .is_some()
785                {
786                    stake_summary.current_total_stake = stake_summary
787                        .current_total_stake
788                        .checked_sub(&operator.current_total_stake)
789                        .ok_or(Error::BalanceUnderflow)?;
790                }
791                stake_summary.next_operators.remove(&operator_id);
792
793                DeactivatedOperators::<T>::mutate(operator.current_domain_id, |operators| {
794                    operators.insert(operator_id)
795                });
796                Pallet::<T>::deposit_event(Event::OperatorDeactivated {
797                    domain_id: operator.current_domain_id,
798                    operator_id,
799                    reactivation_delay,
800                });
801
802                Ok(())
803            },
804        )
805    })
806}
807
808pub(crate) fn do_reactivate_operator<T: Config>(operator_id: OperatorId) -> Result<(), Error> {
811    Operators::<T>::try_mutate(operator_id, |maybe_operator| {
812        let operator = maybe_operator.as_mut().ok_or(Error::UnknownOperator)?;
813        let operator_status = operator.status::<T>(operator_id);
814        let reactivation_delay =
815            if let OperatorStatus::Deactivated(reactivation_delay) = operator_status {
816                *reactivation_delay
817            } else {
818                return Err(Error::OperatorNotDeactivated);
819            };
820
821        DomainStakingSummary::<T>::try_mutate(
822            operator.current_domain_id,
823            |maybe_domain_stake_summary| {
824                let stake_summary = maybe_domain_stake_summary
825                    .as_mut()
826                    .ok_or(Error::DomainNotInitialized)?;
827
828                let current_epoch = stake_summary.current_epoch_index;
829                ensure!(
830                    current_epoch >= reactivation_delay,
831                    Error::ReactivationDelayPeriodIncomplete
832                );
833
834                operator.update_status(OperatorStatus::Registered);
835                stake_summary.next_operators.insert(operator_id);
836
837                DeactivatedOperators::<T>::mutate(operator.current_domain_id, |operators| {
841                    operators.remove(&operator_id);
842                });
843
844                Pallet::<T>::deposit_event(Event::OperatorReactivated {
845                    domain_id: operator.current_domain_id,
846                    operator_id,
847                });
848
849                Ok(())
850            },
851        )
852    })
853}
854
855pub(crate) fn current_share_price<T: Config>(
858    operator_id: OperatorId,
859    operator: &Operator<BalanceOf<T>, T::Share, DomainBlockNumberFor<T>, ReceiptHashFor<T>>,
860    domain_stake_summary: &StakingSummary<OperatorId, BalanceOf<T>>,
861) -> Result<SharePrice, Error> {
862    let total_stake = domain_stake_summary
864        .current_epoch_rewards
865        .get(&operator_id)
866        .and_then(|rewards| {
867            let operator_tax = operator.nomination_tax.mul_floor(*rewards);
868            operator
869                .current_total_stake
870                .checked_add(rewards)?
871                .checked_sub(&operator_tax)
873        })
874        .unwrap_or(operator.current_total_stake);
875
876    SharePrice::new::<T>(operator.current_total_shares, total_stake)
877}
878
879pub(crate) fn do_withdraw_stake<T: Config>(
888    operator_id: OperatorId,
889    nominator_id: NominatorId<T>,
890    to_withdraw: T::Share,
891) -> Result<Weight, Error> {
892    ensure!(!to_withdraw.is_zero(), Error::ZeroWithdraw);
895
896    let mut weight = T::WeightInfo::withdraw_stake();
897    Operators::<T>::try_mutate(operator_id, |maybe_operator| {
898        let operator = maybe_operator.as_mut().ok_or(Error::UnknownOperator)?;
899        ensure!(
900            operator.is_operator_registered_or_deactivated::<T>(operator_id),
901            Error::OperatorNotRegisterdOrDeactivated
902        );
903
904        if operator.deposits_in_epoch.is_zero() && operator.withdrawals_in_epoch.is_zero() {
906            note_pending_staking_operation::<T>(operator.current_domain_id)?;
907        }
908
909        let domain_stake_summary = DomainStakingSummary::<T>::get(operator.current_domain_id)
911            .ok_or(Error::DomainNotInitialized)?;
912        let domain_current_epoch = (
913            operator.current_domain_id,
914            domain_stake_summary.current_epoch_index,
915        )
916            .into();
917
918        let known_shares =
919            Deposits::<T>::try_mutate(operator_id, nominator_id.clone(), |maybe_deposit| {
920                let deposit = maybe_deposit.as_mut().ok_or(Error::UnknownNominator)?;
921                do_convert_previous_epoch_deposits::<T>(
922                    operator_id,
923                    deposit,
924                    domain_stake_summary.current_epoch_index,
925                )?;
926                Ok(deposit.known.shares)
927            })?;
928
929        Withdrawals::<T>::try_mutate(operator_id, nominator_id.clone(), |maybe_withdrawal| {
930            if let Some(withdrawal) = maybe_withdrawal {
931                do_convert_previous_epoch_withdrawal::<T>(
932                    operator_id,
933                    withdrawal,
934                    domain_stake_summary.current_epoch_index,
935                )?;
936                if withdrawal.withdrawals.len() as u32 >= T::WithdrawalLimit::get() {
937                    return Err(Error::TooManyWithdrawals);
938                }
939            }
940            Ok(())
941        })?;
942
943        if matches!(operator.partial_status, OperatorStatus::Deactivated(_)) {
946            DeactivatedOperators::<T>::mutate(operator.current_domain_id, |operators| {
947                operators.insert(operator_id)
948            });
949
950            weight = T::WeightInfo::withdraw_stake_from_deactivated_operator();
951        }
952
953        let operator_owner =
954            OperatorIdOwner::<T>::get(operator_id).ok_or(Error::UnknownOperator)?;
955
956        let is_operator_owner = operator_owner == nominator_id;
957
958        Deposits::<T>::try_mutate(operator_id, nominator_id.clone(), |maybe_deposit| {
959            let deposit = maybe_deposit.as_mut().ok_or(Error::UnknownNominator)?;
960
961            let (remaining_shares, shares_withdrew) = {
962                let remaining_shares = known_shares
963                    .checked_sub(&to_withdraw)
964                    .ok_or(Error::InsufficientShares)?;
965
966                if remaining_shares.is_zero() {
968                    if is_operator_owner {
969                        return Err(Error::MinimumOperatorStake);
970                    }
971
972                    (remaining_shares, to_withdraw)
973                } else {
974                    let share_price =
975                        current_share_price::<T>(operator_id, operator, &domain_stake_summary)?;
976
977                    let remaining_storage_fee =
978                        Perquintill::from_rational(remaining_shares.into(), known_shares.into())
979                            .mul_floor(deposit.known.storage_fee_deposit);
980
981                    let remaining_stake = share_price
982                        .shares_to_stake::<T>(remaining_shares)
983                        .checked_add(&remaining_storage_fee)
984                        .ok_or(Error::BalanceOverflow)?;
985
986                    if is_operator_owner && remaining_stake.lt(&T::MinOperatorStake::get()) {
989                        return Err(Error::MinimumOperatorStake);
990                    }
991
992                    if !is_operator_owner && remaining_stake.lt(&operator.minimum_nominator_stake) {
994                        (T::Share::zero(), known_shares)
995                    } else {
996                        (remaining_shares, to_withdraw)
997                    }
998                }
999            };
1000
1001            let storage_fee_to_withdraw =
1004                Perquintill::from_rational(shares_withdrew.into(), known_shares.into())
1005                    .mul_floor(deposit.known.storage_fee_deposit);
1006
1007            let withdraw_storage_fee = {
1008                let storage_fund_redeem_price = bundle_storage_fund::storage_fund_redeem_price::<T>(
1009                    operator_id,
1010                    operator.total_storage_fee_deposit,
1011                );
1012                bundle_storage_fund::withdraw_and_hold::<T>(
1013                    operator_id,
1014                    &nominator_id,
1015                    storage_fund_redeem_price.redeem(storage_fee_to_withdraw),
1016                )
1017                .map_err(Error::BundleStorageFund)?
1018            };
1019
1020            deposit.known.storage_fee_deposit = deposit
1021                .known
1022                .storage_fee_deposit
1023                .checked_sub(&storage_fee_to_withdraw)
1024                .ok_or(Error::BalanceOverflow)?;
1025
1026            operator.total_storage_fee_deposit = operator
1027                .total_storage_fee_deposit
1028                .checked_sub(&storage_fee_to_withdraw)
1029                .ok_or(Error::BalanceOverflow)?;
1030
1031            operator.withdrawals_in_epoch = operator
1033                .withdrawals_in_epoch
1034                .checked_add(&shares_withdrew)
1035                .ok_or(Error::ShareOverflow)?;
1036
1037            deposit.known.shares = remaining_shares;
1038            if remaining_shares.is_zero()
1039                && let Some(pending_deposit) = deposit.pending
1040            {
1041                ensure!(
1044                    pending_deposit.total()? >= operator.minimum_nominator_stake,
1045                    Error::MinimumNominatorStake
1046                );
1047            }
1048
1049            let head_domain_number = HeadDomainNumber::<T>::get(operator.current_domain_id);
1050            let unlock_at_confirmed_domain_block_number = head_domain_number
1051                .checked_add(&T::StakeWithdrawalLockingPeriod::get())
1052                .ok_or(Error::BlockNumberOverflow)?;
1053
1054            Withdrawals::<T>::try_mutate(operator_id, nominator_id, |maybe_withdrawal| {
1055                let mut withdrawal = maybe_withdrawal.take().unwrap_or_default();
1056                let new_withdrawal_in_shares = match withdrawal.withdrawal_in_shares.take() {
1059                    Some(WithdrawalInShares {
1060                        shares,
1061                        storage_fee_refund,
1062                        ..
1063                    }) => WithdrawalInShares {
1064                        domain_epoch: domain_current_epoch,
1065                        shares: shares
1066                            .checked_add(&shares_withdrew)
1067                            .ok_or(Error::ShareOverflow)?,
1068                        unlock_at_confirmed_domain_block_number,
1069                        storage_fee_refund: storage_fee_refund
1070                            .checked_add(&withdraw_storage_fee)
1071                            .ok_or(Error::BalanceOverflow)?,
1072                    },
1073                    None => WithdrawalInShares {
1074                        domain_epoch: domain_current_epoch,
1075                        unlock_at_confirmed_domain_block_number,
1076                        shares: shares_withdrew,
1077                        storage_fee_refund: withdraw_storage_fee,
1078                    },
1079                };
1080                withdrawal.withdrawal_in_shares = Some(new_withdrawal_in_shares);
1081                withdrawal.total_storage_fee_withdrawal = withdrawal
1082                    .total_storage_fee_withdrawal
1083                    .checked_add(&withdraw_storage_fee)
1084                    .ok_or(Error::BalanceOverflow)?;
1085
1086                *maybe_withdrawal = Some(withdrawal);
1087                Ok(())
1088            })
1089        })
1090    })?;
1091
1092    Ok(weight)
1093}
1094
1095pub(crate) fn do_unlock_funds<T: Config>(
1099    operator_id: OperatorId,
1100    nominator_id: NominatorId<T>,
1101) -> Result<u32, Error> {
1102    let operator = Operators::<T>::get(operator_id).ok_or(Error::UnknownOperator)?;
1103    ensure!(
1104        operator.is_operator_registered_or_deactivated::<T>(operator_id),
1105        Error::OperatorNotRegisterdOrDeactivated
1106    );
1107
1108    let current_domain_epoch_index = DomainStakingSummary::<T>::get(operator.current_domain_id)
1109        .ok_or(Error::DomainNotInitialized)?
1110        .current_epoch_index;
1111
1112    Withdrawals::<T>::try_mutate_exists(operator_id, nominator_id.clone(), |maybe_withdrawal| {
1113        let withdrawal = maybe_withdrawal.as_mut().ok_or(Error::MissingWithdrawal)?;
1114        do_convert_previous_epoch_withdrawal::<T>(
1115            operator_id,
1116            withdrawal,
1117            current_domain_epoch_index,
1118        )?;
1119
1120        ensure!(!withdrawal.withdrawals.is_empty(), Error::MissingWithdrawal);
1121
1122        let head_domain_number = HeadDomainNumber::<T>::get(operator.current_domain_id);
1123
1124        let mut withdrawal_count = 0;
1125        let mut total_unlocked_amount = BalanceOf::<T>::zero();
1126        let mut total_storage_fee_refund = BalanceOf::<T>::zero();
1127        loop {
1128            if withdrawal
1129                .withdrawals
1130                .front()
1131                .map(|w| w.unlock_at_confirmed_domain_block_number > head_domain_number)
1132                .unwrap_or(true)
1133            {
1134                break;
1135            }
1136
1137            let WithdrawalInBalance {
1138                amount_to_unlock,
1139                storage_fee_refund,
1140                ..
1141            } = withdrawal
1142                .withdrawals
1143                .pop_front()
1144                .expect("Must not empty as checked above; qed");
1145
1146            total_unlocked_amount = total_unlocked_amount
1147                .checked_add(&amount_to_unlock)
1148                .ok_or(Error::BalanceOverflow)?;
1149
1150            total_storage_fee_refund = total_storage_fee_refund
1151                .checked_add(&storage_fee_refund)
1152                .ok_or(Error::BalanceOverflow)?;
1153
1154            withdrawal_count += 1;
1155        }
1156
1157        ensure!(
1160            !total_unlocked_amount.is_zero() || !total_storage_fee_refund.is_zero(),
1161            Error::UnlockPeriodNotComplete
1162        );
1163
1164        withdrawal.total_withdrawal_amount = withdrawal
1166            .total_withdrawal_amount
1167            .checked_sub(&total_unlocked_amount)
1168            .ok_or(Error::BalanceUnderflow)?;
1169
1170        withdrawal.total_storage_fee_withdrawal = withdrawal
1171            .total_storage_fee_withdrawal
1172            .checked_sub(&total_storage_fee_refund)
1173            .ok_or(Error::BalanceUnderflow)?;
1174
1175        let (amount_to_mint, amount_to_release) = DepositOnHold::<T>::try_mutate(
1178            (operator_id, nominator_id.clone()),
1179            |deposit_on_hold| {
1180                let amount_to_release = total_unlocked_amount.min(*deposit_on_hold);
1181                let amount_to_mint = total_unlocked_amount.saturating_sub(*deposit_on_hold);
1182
1183                *deposit_on_hold = deposit_on_hold.saturating_sub(amount_to_release);
1184
1185                Ok((amount_to_mint, amount_to_release))
1186            },
1187        )?;
1188
1189        if !amount_to_mint.is_zero() {
1191            mint_funds::<T>(&nominator_id, amount_to_mint)?;
1192        }
1193        if !amount_to_release.is_zero() {
1195            let staked_hold_id = T::HoldIdentifier::staking_staked();
1196            T::Currency::release(
1197                &staked_hold_id,
1198                &nominator_id,
1199                amount_to_release,
1200                Precision::Exact,
1201            )
1202            .map_err(|_| Error::RemoveLock)?;
1203        }
1204
1205        Pallet::<T>::deposit_event(Event::NominatedStakedUnlocked {
1206            operator_id,
1207            nominator_id: nominator_id.clone(),
1208            unlocked_amount: total_unlocked_amount,
1209        });
1210
1211        let storage_fund_hold_id = T::HoldIdentifier::storage_fund_withdrawal();
1213        T::Currency::release(
1214            &storage_fund_hold_id,
1215            &nominator_id,
1216            total_storage_fee_refund,
1217            Precision::Exact,
1218        )
1219        .map_err(|_| Error::RemoveLock)?;
1220
1221        Pallet::<T>::deposit_event(Event::StorageFeeUnlocked {
1222            operator_id,
1223            nominator_id: nominator_id.clone(),
1224            storage_fee: total_storage_fee_refund,
1225        });
1226
1227        if withdrawal.withdrawals.is_empty() && withdrawal.withdrawal_in_shares.is_none() {
1229            *maybe_withdrawal = None;
1230            Deposits::<T>::mutate_exists(operator_id, nominator_id.clone(), |maybe_deposit| {
1232                if let Some(deposit) = maybe_deposit
1233                    && deposit.known.shares.is_zero()
1234                    && deposit.pending.is_none()
1235                {
1236                    *maybe_deposit = None;
1237
1238                    DepositOnHold::<T>::mutate_exists(
1239                        (operator_id, nominator_id),
1240                        |maybe_deposit_on_hold| {
1241                            if let Some(deposit_on_hold) = maybe_deposit_on_hold
1242                                && deposit_on_hold.is_zero()
1243                            {
1244                                *maybe_deposit_on_hold = None
1245                            }
1246                        },
1247                    );
1248                }
1249            });
1250        }
1251
1252        Ok(withdrawal_count)
1253    })
1254}
1255
1256pub(crate) fn do_unlock_nominator<T: Config>(
1258    operator_id: OperatorId,
1259    nominator_id: NominatorId<T>,
1260) -> Result<(), Error> {
1261    Operators::<T>::try_mutate_exists(operator_id, |maybe_operator| {
1262        let mut operator = maybe_operator.take().ok_or(Error::UnknownOperator)?;
1264        let OperatorDeregisteredInfo {
1265            domain_epoch,
1266            unlock_at_confirmed_domain_block_number,
1267        } = match operator.status::<T>(operator_id) {
1268            OperatorStatus::Deregistered(operator_deregistered_info) => operator_deregistered_info,
1269            _ => return Err(Error::OperatorNotDeregistered),
1270        };
1271
1272        let (domain_id, _) = domain_epoch.deconstruct();
1273        let head_domain_number = HeadDomainNumber::<T>::get(domain_id);
1274        ensure!(
1275            *unlock_at_confirmed_domain_block_number <= head_domain_number,
1276            Error::UnlockPeriodNotComplete
1277        );
1278
1279        let current_domain_epoch_index = DomainStakingSummary::<T>::get(operator.current_domain_id)
1280            .ok_or(Error::DomainNotInitialized)?
1281            .current_epoch_index;
1282
1283        let mut total_shares = operator.current_total_shares;
1284        let mut total_stake = operator.current_total_stake;
1285        let share_price = SharePrice::new::<T>(total_shares, total_stake)?;
1286
1287        let mut total_storage_fee_deposit = operator.total_storage_fee_deposit;
1288        let storage_fund_redeem_price = bundle_storage_fund::storage_fund_redeem_price::<T>(
1289            operator_id,
1290            total_storage_fee_deposit,
1291        );
1292        let mut deposit = Deposits::<T>::take(operator_id, nominator_id.clone())
1293            .ok_or(Error::UnknownNominator)?;
1294
1295        do_convert_previous_epoch_deposits::<T>(
1302            operator_id,
1303            &mut deposit,
1304            current_domain_epoch_index,
1305        )?;
1306
1307        let (
1310            amount_ready_to_withdraw,
1311            total_storage_fee_withdrawal,
1312            shares_withdrew_in_current_epoch,
1313        ) = Withdrawals::<T>::take(operator_id, nominator_id.clone())
1314            .map(|mut withdrawal| {
1315                do_convert_previous_epoch_withdrawal::<T>(
1320                    operator_id,
1321                    &mut withdrawal,
1322                    current_domain_epoch_index,
1323                )?;
1324                Ok((
1325                    withdrawal.total_withdrawal_amount,
1326                    withdrawal.total_storage_fee_withdrawal,
1327                    withdrawal
1328                        .withdrawal_in_shares
1329                        .map(|WithdrawalInShares { shares, .. }| shares)
1330                        .unwrap_or_default(),
1331                ))
1332            })
1333            .unwrap_or(Ok((Zero::zero(), Zero::zero(), Zero::zero())))?;
1334
1335        let nominator_shares = deposit
1337            .known
1338            .shares
1339            .checked_add(&shares_withdrew_in_current_epoch)
1340            .ok_or(Error::ShareOverflow)?;
1341        total_shares = total_shares
1342            .checked_sub(&nominator_shares)
1343            .ok_or(Error::ShareOverflow)?;
1344
1345        let nominator_staked_amount = share_price.shares_to_stake::<T>(nominator_shares);
1347        total_stake = total_stake
1348            .checked_sub(&nominator_staked_amount)
1349            .ok_or(Error::BalanceOverflow)?;
1350
1351        let amount_deposited_in_epoch = deposit
1353            .pending
1354            .map(|pending_deposit| pending_deposit.amount)
1355            .unwrap_or_default();
1356
1357        let total_amount_to_unlock = nominator_staked_amount
1358            .checked_add(&amount_ready_to_withdraw)
1359            .and_then(|amount| amount.checked_add(&amount_deposited_in_epoch))
1360            .ok_or(Error::BalanceOverflow)?;
1361
1362        let current_locked_amount = DepositOnHold::<T>::take((operator_id, nominator_id.clone()));
1364        if let Some(amount_to_mint) = total_amount_to_unlock.checked_sub(¤t_locked_amount) {
1365            mint_funds::<T>(&nominator_id, amount_to_mint)?;
1366        }
1367        if !current_locked_amount.is_zero() {
1368            let staked_hold_id = T::HoldIdentifier::staking_staked();
1369            T::Currency::release(
1370                &staked_hold_id,
1371                &nominator_id,
1372                current_locked_amount,
1373                Precision::Exact,
1374            )
1375            .map_err(|_| Error::RemoveLock)?;
1376        }
1377
1378        Pallet::<T>::deposit_event(Event::NominatedStakedUnlocked {
1379            operator_id,
1380            nominator_id: nominator_id.clone(),
1381            unlocked_amount: total_amount_to_unlock,
1382        });
1383
1384        let nominator_total_storage_fee_deposit = deposit
1386            .pending
1387            .map(|pending_deposit| pending_deposit.storage_fee_deposit)
1388            .unwrap_or(Zero::zero())
1389            .checked_add(&deposit.known.storage_fee_deposit)
1390            .ok_or(Error::BalanceOverflow)?;
1391
1392        bundle_storage_fund::withdraw_to::<T>(
1393            operator_id,
1394            &nominator_id,
1395            storage_fund_redeem_price.redeem(nominator_total_storage_fee_deposit),
1396        )
1397        .map_err(Error::BundleStorageFund)?;
1398
1399        let storage_fund_hold_id = T::HoldIdentifier::storage_fund_withdrawal();
1401        T::Currency::release(
1402            &storage_fund_hold_id,
1403            &nominator_id,
1404            total_storage_fee_withdrawal,
1405            Precision::Exact,
1406        )
1407        .map_err(|_| Error::RemoveLock)?;
1408
1409        Pallet::<T>::deposit_event(Event::StorageFeeUnlocked {
1410            operator_id,
1411            nominator_id: nominator_id.clone(),
1412            storage_fee: total_storage_fee_withdrawal,
1413        });
1414
1415        total_storage_fee_deposit =
1417            total_storage_fee_deposit.saturating_sub(nominator_total_storage_fee_deposit);
1418
1419        let cleanup_operator = !Deposits::<T>::contains_prefix(operator_id)
1422            && !Withdrawals::<T>::contains_prefix(operator_id);
1423
1424        if cleanup_operator {
1425            do_cleanup_operator::<T>(operator_id, total_stake)?
1426        } else {
1427            operator.current_total_shares = total_shares;
1429            operator.current_total_stake = total_stake;
1430            operator.total_storage_fee_deposit = total_storage_fee_deposit;
1431
1432            *maybe_operator = Some(operator);
1433        }
1434
1435        Ok(())
1436    })
1437}
1438
1439pub(crate) fn do_cleanup_operator<T: Config>(
1441    operator_id: OperatorId,
1442    total_stake: BalanceOf<T>,
1443) -> Result<(), Error> {
1444    bundle_storage_fund::transfer_all_to_treasury::<T>(operator_id)
1446        .map_err(Error::BundleStorageFund)?;
1447
1448    mint_into_treasury::<T>(total_stake)?;
1450
1451    OperatorIdOwner::<T>::remove(operator_id);
1453
1454    OperatorHighestSlot::<T>::remove(operator_id);
1456
1457    let _ = OperatorEpochSharePrice::<T>::clear_prefix(operator_id, u32::MAX, None);
1459
1460    Ok(())
1461}
1462
1463pub(crate) fn do_reward_operators<T: Config>(
1465    domain_id: DomainId,
1466    source: OperatorRewardSource<BlockNumberFor<T>>,
1467    operators: IntoIter<OperatorId>,
1468    rewards: BalanceOf<T>,
1469) -> Result<(), Error> {
1470    if rewards.is_zero() {
1471        return Ok(());
1472    }
1473    DomainStakingSummary::<T>::mutate(domain_id, |maybe_stake_summary| {
1474        let stake_summary = maybe_stake_summary
1475            .as_mut()
1476            .ok_or(Error::DomainNotInitialized)?;
1477
1478        let total_count = operators.len() as u64;
1479        let operator_weights = operators.into_iter().fold(
1481            BTreeMap::<OperatorId, u64>::new(),
1482            |mut acc, operator_id| {
1483                acc.entry(operator_id)
1484                    .and_modify(|weight| *weight += 1)
1485                    .or_insert(1);
1486                acc
1487            },
1488        );
1489
1490        let mut allocated_rewards = BalanceOf::<T>::zero();
1491        for (operator_id, weight) in operator_weights {
1492            let operator_reward = {
1493                let distribution = Perquintill::from_rational(weight, total_count);
1494                distribution.mul_floor(rewards)
1495            };
1496
1497            stake_summary
1498                .current_epoch_rewards
1499                .entry(operator_id)
1500                .and_modify(|rewards| *rewards = rewards.saturating_add(operator_reward))
1501                .or_insert(operator_reward);
1502
1503            Pallet::<T>::deposit_event(Event::OperatorRewarded {
1504                source: source.clone(),
1505                operator_id,
1506                reward: operator_reward,
1507            });
1508
1509            allocated_rewards = allocated_rewards
1510                .checked_add(&operator_reward)
1511                .ok_or(Error::BalanceOverflow)?;
1512        }
1513
1514        mint_into_treasury::<T>(
1516            rewards
1517                .checked_sub(&allocated_rewards)
1518                .ok_or(Error::BalanceUnderflow)?,
1519        )
1520    })
1521}
1522
1523pub(crate) fn do_mark_operators_as_slashed<T: Config>(
1526    operator_ids: impl AsRef<[OperatorId]>,
1527    slash_reason: SlashedReason<DomainBlockNumberFor<T>, ReceiptHashFor<T>>,
1528) -> Result<(), Error> {
1529    for operator_id in operator_ids.as_ref() {
1530        Operators::<T>::try_mutate(operator_id, |maybe_operator| {
1531            let operator = match maybe_operator.as_mut() {
1532                None => return Ok(()),
1536                Some(operator) => operator,
1537            };
1538            let mut pending_slashes =
1539                PendingSlashes::<T>::get(operator.current_domain_id).unwrap_or_default();
1540
1541            if pending_slashes.contains(operator_id) {
1542                return Ok(());
1543            }
1544
1545            DomainStakingSummary::<T>::try_mutate(
1546                operator.current_domain_id,
1547                |maybe_domain_stake_summary| {
1548                    let stake_summary = maybe_domain_stake_summary
1549                        .as_mut()
1550                        .ok_or(Error::DomainNotInitialized)?;
1551
1552                    operator.update_status(OperatorStatus::Slashed);
1554
1555                    if stake_summary
1558                        .current_operators
1559                        .remove(operator_id)
1560                        .is_some()
1561                    {
1562                        stake_summary.current_total_stake = stake_summary
1563                            .current_total_stake
1564                            .checked_sub(&operator.current_total_stake)
1565                            .ok_or(Error::BalanceUnderflow)?;
1566                    }
1567                    stake_summary.next_operators.remove(operator_id);
1568                    pending_slashes.insert(*operator_id);
1569                    PendingSlashes::<T>::insert(operator.current_domain_id, pending_slashes);
1570                    Pallet::<T>::deposit_event(Event::OperatorSlashed {
1571                        operator_id: *operator_id,
1572                        reason: slash_reason.clone(),
1573                    });
1574                    Ok(())
1575                },
1576            )
1577        })?
1578    }
1579
1580    Ok(())
1581}
1582
1583pub(crate) fn do_mark_invalid_bundle_authors<T: Config>(
1585    domain_id: DomainId,
1586    er: &ExecutionReceiptOf<T>,
1587) -> Result<(), Error> {
1588    let invalid_bundle_authors = invalid_bundle_authors_for_receipt::<T>(domain_id, er);
1589    let er_hash = er.hash::<DomainHashingFor<T>>();
1590    let pending_slashes = PendingSlashes::<T>::get(domain_id).unwrap_or_default();
1591    let mut invalid_bundle_authors_in_epoch = InvalidBundleAuthors::<T>::get(domain_id);
1592    let mut stake_summary =
1593        DomainStakingSummary::<T>::get(domain_id).ok_or(Error::DomainNotInitialized)?;
1594
1595    for operator_id in invalid_bundle_authors {
1596        if pending_slashes.contains(&operator_id) {
1597            continue;
1598        }
1599
1600        mark_invalid_bundle_author::<T>(
1601            operator_id,
1602            er_hash,
1603            &mut stake_summary,
1604            &mut invalid_bundle_authors_in_epoch,
1605        )?;
1606    }
1607
1608    DomainStakingSummary::<T>::insert(domain_id, stake_summary);
1609    InvalidBundleAuthors::<T>::insert(domain_id, invalid_bundle_authors_in_epoch);
1610    Ok(())
1611}
1612
1613pub(crate) fn mark_invalid_bundle_author<T: Config>(
1614    operator_id: OperatorId,
1615    er_hash: ReceiptHashFor<T>,
1616    stake_summary: &mut StakingSummary<OperatorId, BalanceOf<T>>,
1617    invalid_bundle_authors: &mut BTreeSet<OperatorId>,
1618) -> Result<(), Error> {
1619    Operators::<T>::try_mutate(operator_id, |maybe_operator| {
1620        let operator = match maybe_operator.as_mut() {
1621            None => return Ok(()),
1625            Some(operator) => operator,
1626        };
1627
1628        if operator.status::<T>(operator_id) != &OperatorStatus::Registered {
1631            return Ok(());
1632        }
1633
1634        operator.update_status(OperatorStatus::InvalidBundle(er_hash));
1636        invalid_bundle_authors.insert(operator_id);
1637        if stake_summary
1638            .current_operators
1639            .remove(&operator_id)
1640            .is_some()
1641        {
1642            stake_summary.current_total_stake = stake_summary
1643                .current_total_stake
1644                .checked_sub(&operator.current_total_stake)
1645                .ok_or(Error::BalanceUnderflow)?;
1646        }
1647        stake_summary.next_operators.remove(&operator_id);
1648        Ok(())
1649    })
1650}
1651
1652pub(crate) fn do_unmark_invalid_bundle_authors<T: Config>(
1656    domain_id: DomainId,
1657    er: &ExecutionReceiptOf<T>,
1658) -> Result<(), Error> {
1659    let invalid_bundle_authors = invalid_bundle_authors_for_receipt::<T>(domain_id, er);
1660    let er_hash = er.hash::<DomainHashingFor<T>>();
1661    let pending_slashes = PendingSlashes::<T>::get(domain_id).unwrap_or_default();
1662    let mut invalid_bundle_authors_in_epoch = InvalidBundleAuthors::<T>::get(domain_id);
1663    let mut stake_summary =
1664        DomainStakingSummary::<T>::get(domain_id).ok_or(Error::DomainNotInitialized)?;
1665
1666    for operator_id in invalid_bundle_authors {
1667        if pending_slashes.contains(&operator_id)
1668            || Pallet::<T>::is_operator_pending_to_slash(domain_id, operator_id)
1669        {
1670            continue;
1671        }
1672
1673        unmark_invalid_bundle_author::<T>(
1674            operator_id,
1675            er_hash,
1676            &mut stake_summary,
1677            &mut invalid_bundle_authors_in_epoch,
1678        )?;
1679    }
1680
1681    DomainStakingSummary::<T>::insert(domain_id, stake_summary);
1682    InvalidBundleAuthors::<T>::insert(domain_id, invalid_bundle_authors_in_epoch);
1683    Ok(())
1684}
1685
1686fn unmark_invalid_bundle_author<T: Config>(
1687    operator_id: OperatorId,
1688    er_hash: ReceiptHashFor<T>,
1689    stake_summary: &mut StakingSummary<OperatorId, BalanceOf<T>>,
1690    invalid_bundle_authors: &mut BTreeSet<OperatorId>,
1691) -> Result<(), Error> {
1692    Operators::<T>::try_mutate(operator_id, |maybe_operator| {
1693        let operator = match maybe_operator.as_mut() {
1694            None => return Ok(()),
1698            Some(operator) => operator,
1699        };
1700
1701        if operator.partial_status != OperatorStatus::InvalidBundle(er_hash) {
1703            return Ok(());
1704        }
1705
1706        operator.update_status(OperatorStatus::Registered);
1708        invalid_bundle_authors.remove(&operator_id);
1709        stake_summary.next_operators.insert(operator_id);
1710        Ok(())
1711    })
1712}
1713
1714#[cfg(test)]
1715pub(crate) mod tests {
1716    use crate::domain_registry::{DomainConfig, DomainObject};
1717    use crate::pallet::{
1718        Config, DepositOnHold, Deposits, DomainRegistry, DomainStakingSummary, HeadDomainNumber,
1719        NextOperatorId, OperatorIdOwner, Operators, PendingSlashes, Withdrawals,
1720    };
1721    use crate::staking::{
1722        DomainEpoch, Error as StakingError, Operator, OperatorConfig, OperatorDeregisteredInfo,
1723        OperatorStatus, SharePrice, StakingSummary, do_convert_previous_epoch_withdrawal,
1724        do_mark_operators_as_slashed, do_nominate_operator, do_reward_operators, do_unlock_funds,
1725        do_withdraw_stake,
1726    };
1727    use crate::staking_epoch::{do_finalize_domain_current_epoch, do_slash_operator};
1728    use crate::tests::{ExistentialDeposit, MinOperatorStake, RuntimeOrigin, Test, new_test_ext};
1729    use crate::{
1730        BalanceOf, DeactivatedOperators, DeregisteredOperators, Error, MAX_NOMINATORS_TO_SLASH,
1731        NominatorId, OperatorEpochSharePrice, SlashedReason, bundle_storage_fund,
1732    };
1733    use domain_runtime_primitives::DEFAULT_EVM_CHAIN_ID;
1734    use frame_support::traits::Currency;
1735    use frame_support::traits::fungible::Mutate;
1736    use frame_support::weights::Weight;
1737    use frame_support::{assert_err, assert_ok};
1738    use prop_test::prelude::*;
1739    use prop_test::proptest::test_runner::TestCaseResult;
1740    use sp_core::{Pair, sr25519};
1741    use sp_domains::{
1742        DomainId, OperatorAllowList, OperatorId, OperatorPair, OperatorPublicKey,
1743        OperatorRewardSource,
1744    };
1745    use sp_runtime::traits::Zero;
1746    use sp_runtime::{PerThing, Percent, Perquintill};
1747    use std::collections::{BTreeMap, BTreeSet};
1748    use std::ops::RangeInclusive;
1749    use std::vec;
1750    use subspace_runtime_primitives::AI3;
1751
1752    type Balances = pallet_balances::Pallet<Test>;
1753    type Domains = crate::Pallet<Test>;
1754
1755    const STORAGE_FEE_RESERVE: Perquintill = Perquintill::from_percent(20);
1756
1757    #[allow(clippy::too_many_arguments)]
1758    pub(crate) fn register_operator(
1759        domain_id: DomainId,
1760        operator_account: <Test as frame_system::Config>::AccountId,
1761        operator_free_balance: BalanceOf<Test>,
1762        operator_stake: BalanceOf<Test>,
1763        minimum_nominator_stake: BalanceOf<Test>,
1764        signing_key: OperatorPublicKey,
1765        nomination_tax: Percent,
1766        mut nominators: BTreeMap<NominatorId<Test>, (BalanceOf<Test>, BalanceOf<Test>)>,
1767    ) -> (OperatorId, OperatorConfig<BalanceOf<Test>>) {
1768        nominators.insert(operator_account, (operator_free_balance, operator_stake));
1769        for nominator in &nominators {
1770            Balances::set_balance(nominator.0, nominator.1.0);
1771            assert_eq!(Balances::usable_balance(nominator.0), nominator.1.0);
1772        }
1773        nominators.remove(&operator_account);
1774
1775        if !DomainRegistry::<Test>::contains_key(domain_id) {
1776            let domain_config = DomainConfig {
1777                domain_name: String::from_utf8(vec![0; 1024]).unwrap(),
1778                runtime_id: 0,
1779                max_bundle_size: u32::MAX,
1780                max_bundle_weight: Weight::MAX,
1781                bundle_slot_probability: (0, 0),
1782                operator_allow_list: OperatorAllowList::Anyone,
1783                initial_balances: Default::default(),
1784            };
1785
1786            let domain_obj = DomainObject {
1787                owner_account_id: 0,
1788                created_at: 0,
1789                genesis_receipt_hash: Default::default(),
1790                domain_config,
1791                domain_runtime_info: (DEFAULT_EVM_CHAIN_ID, Default::default()).into(),
1792                domain_instantiation_deposit: Default::default(),
1793            };
1794
1795            DomainRegistry::<Test>::insert(domain_id, domain_obj);
1796        }
1797
1798        if !DomainStakingSummary::<Test>::contains_key(domain_id) {
1799            DomainStakingSummary::<Test>::insert(
1800                domain_id,
1801                StakingSummary {
1802                    current_epoch_index: 0,
1803                    current_total_stake: 0,
1804                    current_operators: BTreeMap::new(),
1805                    next_operators: BTreeSet::new(),
1806                    current_epoch_rewards: BTreeMap::new(),
1807                },
1808            );
1809        }
1810
1811        let operator_config = OperatorConfig {
1812            signing_key,
1813            minimum_nominator_stake,
1814            nomination_tax,
1815        };
1816
1817        let res = Domains::register_operator(
1818            RuntimeOrigin::signed(operator_account),
1819            domain_id,
1820            operator_stake,
1821            operator_config.clone(),
1822        );
1823        assert_ok!(res);
1824
1825        let operator_id = NextOperatorId::<Test>::get() - 1;
1826        for nominator in nominators {
1827            if nominator.1.1.is_zero() {
1828                continue;
1829            }
1830
1831            let res = Domains::nominate_operator(
1832                RuntimeOrigin::signed(nominator.0),
1833                operator_id,
1834                nominator.1.1,
1835            );
1836            assert_ok!(res);
1837            assert!(Deposits::<Test>::contains_key(operator_id, nominator.0));
1838        }
1839
1840        (operator_id, operator_config)
1841    }
1842
1843    #[test]
1844    fn test_register_operator_invalid_signing_key() {
1845        let domain_id = DomainId::new(0);
1846        let operator_account = 1;
1847
1848        let mut ext = new_test_ext();
1849        ext.execute_with(|| {
1850            let operator_config = OperatorConfig {
1851                signing_key: OperatorPublicKey::from(sr25519::Public::default()),
1852                minimum_nominator_stake: Default::default(),
1853                nomination_tax: Default::default(),
1854            };
1855
1856            let res = Domains::register_operator(
1857                RuntimeOrigin::signed(operator_account),
1858                domain_id,
1859                Default::default(),
1860                operator_config,
1861            );
1862            assert_err!(
1863                res,
1864                Error::<Test>::Staking(StakingError::InvalidOperatorSigningKey)
1865            );
1866        });
1867    }
1868
1869    #[test]
1870    fn test_register_operator_minimum_nominator_stake() {
1871        let domain_id = DomainId::new(0);
1872        let operator_account = 1;
1873        let pair = OperatorPair::from_seed(&[0; 32]);
1874
1875        let mut ext = new_test_ext();
1876        ext.execute_with(|| {
1877            let operator_config = OperatorConfig {
1878                signing_key: pair.public(),
1879                minimum_nominator_stake: Default::default(),
1880                nomination_tax: Default::default(),
1881            };
1882
1883            let res = Domains::register_operator(
1884                RuntimeOrigin::signed(operator_account),
1885                domain_id,
1886                Default::default(),
1887                operator_config,
1888            );
1889            assert_err!(
1890                res,
1891                Error::<Test>::Staking(StakingError::MinimumNominatorStake)
1892            );
1893        });
1894    }
1895
1896    #[test]
1897    fn test_register_operator() {
1898        let domain_id = DomainId::new(0);
1899        let operator_account = 1;
1900        let operator_free_balance = 2500 * AI3;
1901        let operator_total_stake = 1000 * AI3;
1902        let operator_stake = 800 * AI3;
1903        let operator_storage_fee_deposit = 200 * AI3;
1904        let pair = OperatorPair::from_seed(&[0; 32]);
1905
1906        let mut ext = new_test_ext();
1907        ext.execute_with(|| {
1908            let (operator_id, mut operator_config) = register_operator(
1909                domain_id,
1910                operator_account,
1911                operator_free_balance,
1912                operator_total_stake,
1913                AI3,
1914                pair.public(),
1915                Default::default(),
1916                BTreeMap::new(),
1917            );
1918
1919            assert_eq!(NextOperatorId::<Test>::get(), 1);
1920            assert_eq!(
1922                OperatorIdOwner::<Test>::get(operator_id).unwrap(),
1923                operator_account
1924            );
1925            assert_eq!(
1926                Operators::<Test>::get(operator_id).unwrap(),
1927                Operator {
1928                    signing_key: pair.public(),
1929                    current_domain_id: domain_id,
1930                    next_domain_id: domain_id,
1931                    minimum_nominator_stake: AI3,
1932                    nomination_tax: Default::default(),
1933                    current_total_stake: operator_stake,
1934                    current_total_shares: operator_stake,
1935                    partial_status: OperatorStatus::Registered,
1936                    deposits_in_epoch: 0,
1937                    withdrawals_in_epoch: 0,
1938                    total_storage_fee_deposit: operator_storage_fee_deposit,
1939                }
1940            );
1941
1942            let stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
1943            assert!(stake_summary.next_operators.contains(&operator_id));
1944            assert_eq!(stake_summary.current_total_stake, operator_stake);
1945
1946            assert_eq!(
1947                Balances::usable_balance(operator_account),
1948                operator_free_balance - operator_total_stake - ExistentialDeposit::get()
1949            );
1950
1951            let res = Domains::register_operator(
1953                RuntimeOrigin::signed(operator_account),
1954                domain_id,
1955                operator_stake,
1956                operator_config.clone(),
1957            );
1958            assert_ok!(res);
1959
1960            let new_pair = OperatorPair::from_seed(&[1; 32]);
1962            operator_config.signing_key = new_pair.public();
1963            let res = Domains::register_operator(
1964                RuntimeOrigin::signed(operator_account),
1965                domain_id,
1966                operator_stake,
1967                operator_config,
1968            );
1969            assert_err!(
1970                res,
1971                Error::<Test>::Staking(crate::staking::Error::InsufficientBalance)
1972            );
1973        });
1974    }
1975
1976    #[test]
1977    fn nominate_operator() {
1978        let domain_id = DomainId::new(0);
1979        let operator_account = 1;
1980        let operator_free_balance = 1500 * AI3;
1981        let operator_total_stake = 1000 * AI3;
1982        let operator_stake = 800 * AI3;
1983        let operator_storage_fee_deposit = 200 * AI3;
1984        let pair = OperatorPair::from_seed(&[0; 32]);
1985
1986        let nominator_account = 2;
1987        let nominator_free_balance = 150 * AI3;
1988        let nominator_total_stake = 100 * AI3;
1989        let nominator_stake = 80 * AI3;
1990        let nominator_storage_fee_deposit = 20 * AI3;
1991
1992        let mut ext = new_test_ext();
1993        ext.execute_with(|| {
1994            let (operator_id, _) = register_operator(
1995                domain_id,
1996                operator_account,
1997                operator_free_balance,
1998                operator_total_stake,
1999                10 * AI3,
2000                pair.public(),
2001                Default::default(),
2002                BTreeMap::from_iter(vec![(
2003                    nominator_account,
2004                    (nominator_free_balance, nominator_total_stake),
2005                )]),
2006            );
2007
2008            let domain_staking_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2009            assert_eq!(domain_staking_summary.current_total_stake, operator_stake);
2010
2011            let operator = Operators::<Test>::get(operator_id).unwrap();
2012            assert_eq!(operator.current_total_stake, operator_stake);
2013            assert_eq!(operator.current_total_shares, operator_stake);
2014            assert_eq!(
2015                operator.total_storage_fee_deposit,
2016                operator_storage_fee_deposit + nominator_storage_fee_deposit
2017            );
2018            assert_eq!(operator.deposits_in_epoch, nominator_stake);
2019
2020            let pending_deposit = Deposits::<Test>::get(0, nominator_account)
2021                .unwrap()
2022                .pending
2023                .unwrap();
2024            assert_eq!(pending_deposit.amount, nominator_stake);
2025            assert_eq!(
2026                pending_deposit.storage_fee_deposit,
2027                nominator_storage_fee_deposit
2028            );
2029            assert_eq!(pending_deposit.total().unwrap(), nominator_total_stake);
2030
2031            assert_eq!(
2032                Balances::usable_balance(nominator_account),
2033                nominator_free_balance - nominator_total_stake - ExistentialDeposit::get()
2034            );
2035
2036            let additional_nomination_total_stake = 40 * AI3;
2038            let additional_nomination_stake = 32 * AI3;
2039            let additional_nomination_storage_fee_deposit = 8 * AI3;
2040            let res = Domains::nominate_operator(
2041                RuntimeOrigin::signed(nominator_account),
2042                operator_id,
2043                additional_nomination_total_stake,
2044            );
2045            assert_ok!(res);
2046            let pending_deposit = Deposits::<Test>::get(0, nominator_account)
2047                .unwrap()
2048                .pending
2049                .unwrap();
2050            assert_eq!(
2051                pending_deposit.amount,
2052                nominator_stake + additional_nomination_stake
2053            );
2054            assert_eq!(
2055                pending_deposit.storage_fee_deposit,
2056                nominator_storage_fee_deposit + additional_nomination_storage_fee_deposit
2057            );
2058
2059            let operator = Operators::<Test>::get(operator_id).unwrap();
2060            assert_eq!(operator.current_total_stake, operator_stake);
2061            assert_eq!(
2062                operator.deposits_in_epoch,
2063                nominator_stake + additional_nomination_stake
2064            );
2065            assert_eq!(
2066                operator.total_storage_fee_deposit,
2067                operator_storage_fee_deposit
2068                    + nominator_storage_fee_deposit
2069                    + additional_nomination_storage_fee_deposit
2070            );
2071
2072            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
2074
2075            let operator = Operators::<Test>::get(operator_id).unwrap();
2076            assert_eq!(
2077                operator.current_total_stake,
2078                operator_stake + nominator_stake + additional_nomination_stake
2079            );
2080
2081            let domain_staking_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2082            assert_eq!(
2083                domain_staking_summary.current_total_stake,
2084                operator_stake + nominator_stake + additional_nomination_stake
2085            );
2086        });
2087    }
2088
2089    #[test]
2090    fn operator_deregistration() {
2091        let domain_id = DomainId::new(0);
2092        let operator_account = 1;
2093        let operator_stake = 200 * AI3;
2094        let operator_free_balance = 250 * AI3;
2095        let pair = OperatorPair::from_seed(&[0; 32]);
2096        let mut ext = new_test_ext();
2097        ext.execute_with(|| {
2098            let (operator_id, _) = register_operator(
2099                domain_id,
2100                operator_account,
2101                operator_free_balance,
2102                operator_stake,
2103                AI3,
2104                pair.public(),
2105                Default::default(),
2106                BTreeMap::new(),
2107            );
2108
2109            let res =
2110                Domains::deregister_operator(RuntimeOrigin::signed(operator_account), operator_id);
2111            assert_ok!(res);
2112
2113            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2114            assert!(!domain_stake_summary.next_operators.contains(&operator_id));
2115
2116            let operator = Operators::<Test>::get(operator_id).unwrap();
2117            assert_eq!(
2118                *operator.status::<Test>(operator_id),
2119                OperatorStatus::Deregistered(
2120                    (
2121                        domain_id,
2122                        domain_stake_summary.current_epoch_index,
2123                        5
2125                    )
2126                        .into()
2127                )
2128            );
2129
2130            let new_domain_id = DomainId::new(1);
2132            let domain_config = DomainConfig {
2133                domain_name: String::from_utf8(vec![0; 1024]).unwrap(),
2134                runtime_id: 0,
2135                max_bundle_size: u32::MAX,
2136                max_bundle_weight: Weight::MAX,
2137                bundle_slot_probability: (0, 0),
2138                operator_allow_list: OperatorAllowList::Anyone,
2139                initial_balances: Default::default(),
2140            };
2141
2142            let domain_obj = DomainObject {
2143                owner_account_id: 0,
2144                created_at: 0,
2145                genesis_receipt_hash: Default::default(),
2146                domain_config,
2147                domain_runtime_info: (DEFAULT_EVM_CHAIN_ID, Default::default()).into(),
2148                domain_instantiation_deposit: Default::default(),
2149            };
2150
2151            DomainRegistry::<Test>::insert(new_domain_id, domain_obj);
2152            DomainStakingSummary::<Test>::insert(
2153                new_domain_id,
2154                StakingSummary {
2155                    current_epoch_index: 0,
2156                    current_total_stake: 0,
2157                    current_operators: BTreeMap::new(),
2158                    next_operators: BTreeSet::new(),
2159                    current_epoch_rewards: BTreeMap::new(),
2160                },
2161            );
2162
2163            let nominator_account = 100;
2165            let nominator_stake = 100 * AI3;
2166            let res = Domains::nominate_operator(
2167                RuntimeOrigin::signed(nominator_account),
2168                operator_id,
2169                nominator_stake,
2170            );
2171            assert_err!(
2172                res,
2173                Error::<Test>::Staking(crate::staking::Error::OperatorNotRegistered)
2174            );
2175        });
2176    }
2177
2178    #[test]
2179    fn operator_deactivation() {
2180        let domain_id = DomainId::new(0);
2181        let operator_account = 1;
2182        let operator_stake = 200 * AI3;
2183        let operator_free_balance = 250 * AI3;
2184        let pair = OperatorPair::from_seed(&[0; 32]);
2185        let mut ext = new_test_ext();
2186        ext.execute_with(|| {
2187            let (operator_id, _) = register_operator(
2188                domain_id,
2189                operator_account,
2190                operator_free_balance,
2191                operator_stake,
2192                AI3,
2193                pair.public(),
2194                Default::default(),
2195                BTreeMap::new(),
2196            );
2197
2198            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2199            let total_current_stake = domain_stake_summary.current_total_stake;
2200
2201            let res = Domains::deactivate_operator(RuntimeOrigin::root(), operator_id);
2202            assert_ok!(res);
2203
2204            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2205            assert!(!domain_stake_summary.next_operators.contains(&operator_id));
2206            assert!(
2207                !domain_stake_summary
2208                    .current_operators
2209                    .contains_key(&operator_id)
2210            );
2211
2212            let current_epoch_index = domain_stake_summary.current_epoch_index;
2213            let reactivation_delay =
2214                current_epoch_index + <Test as Config>::OperatorActivationDelayInEpochs::get();
2215            let operator = Operators::<Test>::get(operator_id).unwrap();
2216            assert_eq!(
2217                *operator.status::<Test>(operator_id),
2218                OperatorStatus::Deactivated(reactivation_delay)
2219            );
2220
2221            let operator_stake = operator.current_total_stake;
2222            assert_eq!(
2223                total_current_stake,
2224                domain_stake_summary.current_total_stake + operator_stake
2225            );
2226
2227            let nominator_account = 100;
2229            let nominator_stake = 100 * AI3;
2230            let res = Domains::nominate_operator(
2231                RuntimeOrigin::signed(nominator_account),
2232                operator_id,
2233                nominator_stake,
2234            );
2235            assert_err!(
2236                res,
2237                Error::<Test>::Staking(crate::staking::Error::OperatorNotRegistered)
2238            );
2239        });
2240    }
2241
2242    #[test]
2243    fn operator_deactivation_withdraw_stake() {
2244        let domain_id = DomainId::new(0);
2245        let operator_account = 1;
2246        let operator_stake = 200 * AI3;
2247        let operator_free_balance = 250 * AI3;
2248        let pair = OperatorPair::from_seed(&[0; 32]);
2249        let nominator_account = 100;
2250        let nominator_free_balance = 150 * AI3;
2251        let nominator_total_stake = 100 * AI3;
2252        let mut ext = new_test_ext();
2253        ext.execute_with(|| {
2254            let (operator_id, _) = register_operator(
2255                domain_id,
2256                operator_account,
2257                operator_free_balance,
2258                operator_stake,
2259                AI3,
2260                pair.public(),
2261                Default::default(),
2262                BTreeMap::from_iter(vec![(
2263                    nominator_account,
2264                    (nominator_free_balance, nominator_total_stake),
2265                )]),
2266            );
2267
2268            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2269            let total_current_stake = domain_stake_summary.current_total_stake;
2270
2271            let res = Domains::deactivate_operator(RuntimeOrigin::root(), operator_id);
2273            assert_ok!(res);
2274
2275            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2276            assert!(!domain_stake_summary.next_operators.contains(&operator_id));
2277            assert!(
2278                !domain_stake_summary
2279                    .current_operators
2280                    .contains_key(&operator_id)
2281            );
2282
2283            let current_epoch_index = domain_stake_summary.current_epoch_index;
2284            let reactivation_delay =
2285                current_epoch_index + <Test as Config>::OperatorActivationDelayInEpochs::get();
2286            let operator = Operators::<Test>::get(operator_id).unwrap();
2287            assert_eq!(
2288                *operator.status::<Test>(operator_id),
2289                OperatorStatus::Deactivated(reactivation_delay)
2290            );
2291
2292            let operator_stake = operator.current_total_stake;
2293            assert_eq!(
2294                total_current_stake,
2295                domain_stake_summary.current_total_stake + operator_stake
2296            );
2297
2298            assert!(DeactivatedOperators::<Test>::get(domain_id).contains(&operator_id));
2300
2301            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
2303
2304            assert!(!DeactivatedOperators::<Test>::get(domain_id).contains(&operator_id));
2306
2307            let nominator_shares = Perquintill::from_percent(80).mul_floor(nominator_total_stake);
2309            let res = Domains::withdraw_stake(
2310                RuntimeOrigin::signed(nominator_account),
2311                operator_id,
2312                nominator_shares,
2313            );
2314            assert_ok!(res);
2315
2316            assert!(DeactivatedOperators::<Test>::get(domain_id).contains(&operator_id));
2319
2320            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2321            let current_epoch_index = domain_stake_summary.current_epoch_index;
2322
2323            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
2325
2326            let domain_epoch: DomainEpoch = (domain_id, current_epoch_index).into();
2328            assert!(OperatorEpochSharePrice::<Test>::get(operator_id, domain_epoch).is_some());
2329
2330            let nominator_withdrawals =
2331                Withdrawals::<Test>::get(operator_id, nominator_account).unwrap();
2332            let unlock_funds_at = nominator_withdrawals
2333                .withdrawal_in_shares
2334                .unwrap()
2335                .unlock_at_confirmed_domain_block_number;
2336            HeadDomainNumber::<Test>::insert(domain_id, unlock_funds_at + 1);
2337
2338            let res = Domains::unlock_funds(RuntimeOrigin::signed(nominator_account), operator_id);
2340            assert_ok!(res);
2341        });
2342    }
2343
2344    #[test]
2345    fn operator_deactivation_deregister_operator() {
2346        let domain_id = DomainId::new(0);
2347        let operator_account = 1;
2348        let operator_stake = 200 * AI3;
2349        let operator_free_balance = 250 * AI3;
2350        let pair = OperatorPair::from_seed(&[0; 32]);
2351        let nominator_account = 100;
2352        let nominator_free_balance = 150 * AI3;
2353        let nominator_total_stake = 100 * AI3;
2354        let mut ext = new_test_ext();
2355        ext.execute_with(|| {
2356            let (operator_id, _) = register_operator(
2357                domain_id,
2358                operator_account,
2359                operator_free_balance,
2360                operator_stake,
2361                AI3,
2362                pair.public(),
2363                Default::default(),
2364                BTreeMap::from_iter(vec![(
2365                    nominator_account,
2366                    (nominator_free_balance, nominator_total_stake),
2367                )]),
2368            );
2369
2370            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2371            let total_current_stake = domain_stake_summary.current_total_stake;
2372
2373            let res = Domains::deactivate_operator(RuntimeOrigin::root(), operator_id);
2375            assert_ok!(res);
2376
2377            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2378            assert!(!domain_stake_summary.next_operators.contains(&operator_id));
2379            assert!(
2380                !domain_stake_summary
2381                    .current_operators
2382                    .contains_key(&operator_id)
2383            );
2384
2385            let current_epoch_index = domain_stake_summary.current_epoch_index;
2386            let reactivation_delay =
2387                current_epoch_index + <Test as Config>::OperatorActivationDelayInEpochs::get();
2388            let operator = Operators::<Test>::get(operator_id).unwrap();
2389            assert_eq!(
2390                *operator.status::<Test>(operator_id),
2391                OperatorStatus::Deactivated(reactivation_delay)
2392            );
2393
2394            let operator_stake = operator.current_total_stake;
2395            assert_eq!(
2396                total_current_stake,
2397                domain_stake_summary.current_total_stake + operator_stake
2398            );
2399
2400            assert!(DeactivatedOperators::<Test>::get(domain_id).contains(&operator_id));
2402
2403            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
2405
2406            assert!(!DeactivatedOperators::<Test>::get(domain_id).contains(&operator_id));
2408
2409            let nominator_shares = Perquintill::from_percent(80).mul_floor(nominator_total_stake);
2411            let res = Domains::withdraw_stake(
2412                RuntimeOrigin::signed(nominator_account),
2413                operator_id,
2414                nominator_shares,
2415            );
2416            assert_ok!(res);
2417
2418            assert!(DeactivatedOperators::<Test>::get(domain_id).contains(&operator_id));
2421
2422            let res =
2424                Domains::deregister_operator(RuntimeOrigin::signed(operator_account), operator_id);
2425            assert_ok!(res);
2426
2427            assert!(!DeactivatedOperators::<Test>::get(domain_id).contains(&operator_id));
2430            assert!(DeregisteredOperators::<Test>::get(domain_id).contains(&operator_id));
2431
2432            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2433            let current_epoch_index = domain_stake_summary.current_epoch_index;
2434
2435            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
2437
2438            let domain_epoch: DomainEpoch = (domain_id, current_epoch_index).into();
2440            assert!(OperatorEpochSharePrice::<Test>::get(operator_id, domain_epoch).is_some());
2441
2442            let unlock_operator_at = HeadDomainNumber::<Test>::get(domain_id)
2443                + <Test as Config>::StakeWithdrawalLockingPeriod::get();
2444
2445            let operator = Operators::<Test>::get(operator_id).unwrap();
2446            assert_eq!(
2447                operator.partial_status,
2448                OperatorStatus::Deregistered(OperatorDeregisteredInfo {
2449                    domain_epoch,
2450                    unlock_at_confirmed_domain_block_number: unlock_operator_at
2451                })
2452            );
2453
2454            HeadDomainNumber::<Test>::insert(domain_id, unlock_operator_at);
2455
2456            let res =
2458                Domains::unlock_nominator(RuntimeOrigin::signed(nominator_account), operator_id);
2459            assert_ok!(res);
2460
2461            let res =
2463                Domains::unlock_nominator(RuntimeOrigin::signed(operator_account), operator_id);
2464            assert_ok!(res);
2465
2466            assert!(OperatorIdOwner::<Test>::get(operator_id).is_none());
2468        });
2469    }
2470
2471    #[test]
2472    fn operator_reactivation() {
2473        let domain_id = DomainId::new(0);
2474        let operator_account = 1;
2475        let operator_stake = 200 * AI3;
2476        let operator_free_balance = 250 * AI3;
2477        let nominator_account = 100;
2478        let nominator_free_balance = 150 * AI3;
2479        let nominator_total_stake = 100 * AI3;
2480        let pair = OperatorPair::from_seed(&[0; 32]);
2481        let mut ext = new_test_ext();
2482        ext.execute_with(|| {
2483            let (operator_id, _) = register_operator(
2484                domain_id,
2485                operator_account,
2486                operator_free_balance,
2487                operator_stake,
2488                AI3,
2489                pair.public(),
2490                Default::default(),
2491                BTreeMap::from_iter(vec![(
2492                    nominator_account,
2493                    (nominator_free_balance, nominator_total_stake),
2494                )]),
2495            );
2496
2497            let res = Domains::deactivate_operator(RuntimeOrigin::root(), operator_id);
2498            assert_ok!(res);
2499
2500            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2501            assert!(!domain_stake_summary.next_operators.contains(&operator_id));
2502            assert!(
2503                !domain_stake_summary
2504                    .current_operators
2505                    .contains_key(&operator_id)
2506            );
2507
2508            let current_epoch_index = domain_stake_summary.current_epoch_index;
2509            let reactivation_delay =
2510                current_epoch_index + <Test as Config>::OperatorActivationDelayInEpochs::get();
2511            let operator = Operators::<Test>::get(operator_id).unwrap();
2512            assert_eq!(
2513                *operator.status::<Test>(operator_id),
2514                OperatorStatus::Deactivated(reactivation_delay)
2515            );
2516
2517            let res = Domains::reactivate_operator(RuntimeOrigin::root(), operator_id);
2519            assert_err!(
2520                res,
2521                Error::<Test>::Staking(crate::staking::Error::ReactivationDelayPeriodIncomplete)
2522            );
2523
2524            for expected_epoch in (current_epoch_index + 1)..=reactivation_delay {
2525                do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
2526                let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2527                assert_eq!(domain_stake_summary.current_epoch_index, expected_epoch);
2528            }
2529
2530            assert!(!DeactivatedOperators::<Test>::get(domain_id).contains(&operator_id));
2532
2533            let nominator_shares = Perquintill::from_percent(80).mul_floor(nominator_total_stake);
2535            let res = Domains::withdraw_stake(
2536                RuntimeOrigin::signed(nominator_account),
2537                operator_id,
2538                nominator_shares,
2539            );
2540            assert_ok!(res);
2541
2542            assert!(DeactivatedOperators::<Test>::get(domain_id).contains(&operator_id));
2544
2545            let res = Domains::reactivate_operator(RuntimeOrigin::root(), operator_id);
2546            assert_ok!(res);
2547
2548            assert!(!DeactivatedOperators::<Test>::get(domain_id).contains(&operator_id));
2551
2552            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
2553            assert!(domain_stake_summary.next_operators.contains(&operator_id));
2554
2555            let operator = Operators::<Test>::get(operator_id).unwrap();
2556            assert_eq!(
2557                *operator.status::<Test>(operator_id),
2558                OperatorStatus::Registered
2559            );
2560        });
2561    }
2562
2563    type WithdrawWithResult = Vec<(Share, Result<(), StakingError>)>;
2564
2565    type ExpectedWithdrawAmount = Option<(BalanceOf<Test>, bool)>;
2569
2570    type StorageFundChange = (bool, u32);
2572
2573    pub(crate) type Share = <Test as Config>::Share;
2574
2575    struct WithdrawParams {
2576        minimum_nominator_stake: BalanceOf<Test>,
2578        nominators: Vec<(NominatorId<Test>, BalanceOf<Test>)>,
2581        operator_reward: BalanceOf<Test>,
2583        nominator_id: NominatorId<Test>,
2585        withdraws: WithdrawWithResult,
2587        maybe_deposit: Option<BalanceOf<Test>>,
2589        expected_withdraw: ExpectedWithdrawAmount,
2592        expected_nominator_count_reduced_by: u32,
2594        storage_fund_change: StorageFundChange,
2596    }
2597
2598    fn withdraw_stake(params: WithdrawParams) {
2600        withdraw_stake_inner(params, false).expect("always panics rather than returning an error");
2601    }
2602
2603    fn withdraw_stake_prop(params: WithdrawParams) -> TestCaseResult {
2605        withdraw_stake_inner(params, true)
2606    }
2607
2608    fn withdraw_stake_inner(params: WithdrawParams, is_proptest: bool) -> TestCaseResult {
2610        let WithdrawParams {
2611            minimum_nominator_stake,
2612            nominators,
2613            operator_reward,
2614            nominator_id,
2615            withdraws,
2616            maybe_deposit,
2617            expected_withdraw,
2618            expected_nominator_count_reduced_by,
2619            storage_fund_change,
2620        } = params;
2621        let domain_id = DomainId::new(0);
2622        let operator_account = 0;
2623        let pair = OperatorPair::from_seed(&[0; 32]);
2624        let mut total_balance = nominators.iter().map(|n| n.1).sum::<BalanceOf<Test>>()
2625            + operator_reward
2626            + maybe_deposit.unwrap_or(0);
2627
2628        let mut nominators = BTreeMap::from_iter(
2629            nominators
2630                .into_iter()
2631                .map(|(id, bal)| (id, (bal + ExistentialDeposit::get(), bal)))
2632                .collect::<Vec<(NominatorId<Test>, (BalanceOf<Test>, BalanceOf<Test>))>>(),
2633        );
2634
2635        let mut ext = new_test_ext();
2636        ext.execute_with(|| {
2637            let (operator_free_balance, operator_stake) =
2638                nominators.remove(&operator_account).unwrap();
2639            let (operator_id, _) = register_operator(
2640                domain_id,
2641                operator_account,
2642                operator_free_balance,
2643                operator_stake,
2644                minimum_nominator_stake,
2645                pair.public(),
2646                Default::default(),
2647                nominators,
2648            );
2649
2650            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
2651
2652            if !operator_reward.is_zero() {
2653                do_reward_operators::<Test>(
2654                    domain_id,
2655                    OperatorRewardSource::Dummy,
2656                    vec![operator_id].into_iter(),
2657                    operator_reward,
2658                )
2659                .unwrap();
2660            }
2661
2662            let head_domain_number = HeadDomainNumber::<Test>::get(domain_id);
2663
2664            if let Some(deposit_amount) = maybe_deposit {
2665                Balances::mint_into(&nominator_id, deposit_amount).unwrap();
2666                let res = Domains::nominate_operator(
2667                    RuntimeOrigin::signed(nominator_id),
2668                    operator_id,
2669                    deposit_amount,
2670                );
2671                assert_ok!(res);
2672            }
2673
2674            let operator = Operators::<Test>::get(operator_id).unwrap();
2675            let (is_storage_fund_increased, storage_fund_change_amount) = storage_fund_change;
2676            if is_storage_fund_increased {
2677                bundle_storage_fund::refund_storage_fee::<Test>(
2678                    storage_fund_change_amount as u128 * AI3,
2679                    BTreeMap::from_iter([(operator_id, 1)]),
2680                )
2681                .unwrap();
2682                assert_eq!(
2683                    operator.total_storage_fee_deposit + storage_fund_change_amount as u128 * AI3,
2684                    bundle_storage_fund::total_balance::<Test>(operator_id)
2685                );
2686                total_balance += storage_fund_change_amount as u128 * AI3;
2687            } else {
2688                bundle_storage_fund::charge_bundle_storage_fee::<Test>(
2689                    operator_id,
2690                    storage_fund_change_amount,
2691                )
2692                .unwrap();
2693                assert_eq!(
2694                    operator.total_storage_fee_deposit - storage_fund_change_amount as u128 * AI3,
2695                    bundle_storage_fund::total_balance::<Test>(operator_id)
2696                );
2697                total_balance -= storage_fund_change_amount as u128 * AI3;
2698            }
2699
2700            for (withdraw, expected_result) in withdraws {
2701                let withdraw_share_amount = STORAGE_FEE_RESERVE.left_from_one().mul_ceil(withdraw);
2702                let res = Domains::withdraw_stake(
2703                    RuntimeOrigin::signed(nominator_id),
2704                    operator_id,
2705                    withdraw_share_amount,
2706                )
2707                .map(|_| ());
2708                if is_proptest {
2709                    prop_assert_eq!(
2710                        res,
2711                        expected_result.map_err(|err| Error::<Test>::Staking(err).into()),
2712                        "unexpected withdraw_stake result",
2713                    );
2714                } else {
2715                    assert_eq!(
2716                        res,
2717                        expected_result.map_err(|err| Error::<Test>::Staking(err).into()),
2718                        "unexpected withdraw_stake result",
2719                    );
2720                }
2721            }
2722
2723            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
2724
2725            if let Some((withdraw, include_ed)) = expected_withdraw {
2726                let previous_usable_balance = Balances::usable_balance(nominator_id);
2727
2728                HeadDomainNumber::<Test>::set(
2730                    domain_id,
2731                    head_domain_number
2732                        + <Test as crate::Config>::StakeWithdrawalLockingPeriod::get(),
2733                );
2734                assert_ok!(do_unlock_funds::<Test>(operator_id, nominator_id));
2735
2736                let expected_balance = if include_ed {
2737                    total_balance += crate::tests::ExistentialDeposit::get();
2738                    previous_usable_balance + withdraw + crate::tests::ExistentialDeposit::get()
2739                } else {
2740                    previous_usable_balance + withdraw
2741                };
2742
2743                if is_proptest {
2744                    prop_assert_approx!(Balances::usable_balance(nominator_id), expected_balance);
2745                } else {
2746                    assert_eq!(
2747                        Balances::usable_balance(nominator_id),
2748                        expected_balance,
2749                        "usable balance is not equal to expected balance:\n{} !=\n{}",
2750                        Balances::usable_balance(nominator_id),
2751                        expected_balance,
2752                    );
2753                }
2754
2755                assert!(Withdrawals::<Test>::get(operator_id, nominator_id).is_none());
2757            }
2758
2759            if expected_nominator_count_reduced_by > 0 {
2762                if is_proptest {
2763                    prop_assert!(
2764                        Deposits::<Test>::get(operator_id, nominator_id).is_none(),
2765                        "deposit exists for nominator: {nominator_id}",
2766                    );
2767                    prop_assert!(
2768                        !DepositOnHold::<Test>::contains_key((operator_id, nominator_id)),
2769                        "deposit on hold exists for nominator: {nominator_id}",
2770                    );
2771                } else {
2772                    assert!(
2773                        Deposits::<Test>::get(operator_id, nominator_id).is_none(),
2774                        "deposit exists for nominator: {nominator_id}",
2775                    );
2776                    assert!(
2777                        !DepositOnHold::<Test>::contains_key((operator_id, nominator_id)),
2778                        "deposit on hold exists for nominator: {nominator_id}",
2779                    );
2780                }
2781            }
2782
2783            let operator = Operators::<Test>::get(operator_id).unwrap();
2785            if is_proptest {
2786                prop_assert_approx!(
2787                    Balances::usable_balance(nominator_id)
2788                        + operator.current_total_stake
2789                        + bundle_storage_fund::total_balance::<Test>(operator_id),
2790                    total_balance,
2791                    "\n{} + {} + {} =",
2792                    Balances::usable_balance(nominator_id),
2793                    operator.current_total_stake,
2794                    bundle_storage_fund::total_balance::<Test>(operator_id),
2795                );
2796            } else {
2797                assert_eq!(
2798                    Balances::usable_balance(nominator_id)
2799                        + operator.current_total_stake
2800                        + bundle_storage_fund::total_balance::<Test>(operator_id),
2801                    total_balance,
2802                    "initial total balance is not equal to final total balance:\n\
2803                    {} + {} + {} =\n{} !=\n{}",
2804                    Balances::usable_balance(nominator_id),
2805                    operator.current_total_stake,
2806                    bundle_storage_fund::total_balance::<Test>(operator_id),
2807                    Balances::usable_balance(nominator_id)
2808                        + operator.current_total_stake
2809                        + bundle_storage_fund::total_balance::<Test>(operator_id),
2810                    total_balance,
2811                );
2812            }
2813
2814            Ok(())
2815        })
2816    }
2817
2818    macro_rules! prop_assert_approx {
2824            ($actual:expr, $expected:expr $(,)?) => {
2825                prop_assert_approx!($actual, $expected, "");
2826            };
2827
2828            ($actual:expr, $expected:expr, $fmt:expr) => {
2829                prop_assert_approx!($actual, $expected, $fmt,);
2830            };
2831
2832            ($actual:expr, $expected:expr, $fmt:expr, $($args:tt)*) => {{
2833                let actual = $actual;
2834                let expected = $expected;
2835                let extra = format!($fmt, $($args)*);
2836                prop_test::proptest::prop_assert!(
2837                    actual <= expected,
2838                    "extra minting: actual amount is greater than expected amount:{}{}\
2839                    \n{} >\
2840                    \n{}",
2841                    if extra.is_empty() { "" } else { "\n" },
2842                    extra,
2843                    actual,
2844                    expected,
2845                );
2846                let expected_rounded_down = $crate::staking::tests::PROP_ROUNDING_DOWN_FACTOR
2847                    .mul_floor(expected)
2848                    .saturating_sub($crate::staking::tests::PROP_ABSOLUTE_ROUNDING_ERROR);
2849                prop_test::proptest::prop_assert!(
2850                    actual >= expected_rounded_down,
2851                    "excess rounding losses: actual amount is less than expected amount \
2852                    rounded down:{}{}\
2853                    \n{} <\
2854                    \n{} (from\
2855                    \n{})",
2856                    if extra.is_empty() { "" } else { "\n" },
2857                    extra,
2858                    actual,
2859                    expected_rounded_down,
2860                    expected,
2861                );
2862            }};
2863        }
2864
2865    pub(crate) use prop_assert_approx;
2867
2868    pub(crate) const PROP_ROUNDING_DOWN_FACTOR: Perquintill =
2872        Perquintill::from_parts(1_000_000_000_000_000_000 - 1_000_000_000);
2873
2874    pub(crate) const PROP_ABSOLUTE_ROUNDING_ERROR: u128 = 1000;
2877
2878    pub(crate) const MAX_PROP_BALANCE: u128 = 2u128.pow(122);
2886
2887    pub(crate) const MIN_PROP_OPERATOR_STAKE: u128 = 3 * <Test as Config>::MinOperatorStake::get();
2890
2891    pub(crate) const MIN_PROP_NOMINATOR_STAKE: u128 =
2893        <Test as Config>::MinNominatorStake::get() + 1;
2894
2895    pub(crate) const PROP_OPERATOR_STAKE_RANGE: RangeInclusive<u128> =
2897        MIN_PROP_OPERATOR_STAKE..=MAX_PROP_BALANCE;
2898
2899    pub(crate) const PROP_NOMINATOR_STAKE_RANGE: RangeInclusive<u128> =
2901        MIN_PROP_NOMINATOR_STAKE..=MAX_PROP_BALANCE;
2902
2903    pub(crate) const PROP_DEPOSIT_RANGE: RangeInclusive<u128> =
2906        MIN_PROP_NOMINATOR_STAKE..=MAX_PROP_BALANCE;
2907
2908    pub(crate) const PROP_REWARD_RANGE: RangeInclusive<u128> = 0..=MAX_PROP_BALANCE;
2910
2911    pub(crate) const PROP_FREE_BALANCE_RANGE: RangeInclusive<u128> =
2913        MIN_PROP_NOMINATOR_STAKE..=MAX_PROP_BALANCE;
2914
2915    #[test]
2921    fn prop_withdraw_excess_operator_stake() {
2922        prop_test!(&PROP_OPERATOR_STAKE_RANGE, |operator_stake| {
2923            let mut excess_stake =
2924                operator_stake.saturating_sub(<Test as Config>::MinOperatorStake::get());
2925
2926            excess_stake = Perquintill::from_parts(1)
2928                .left_from_one()
2929                .mul_ceil(excess_stake);
2930            excess_stake -= 1;
2931
2932            prop_assert!(excess_stake > 0, "would cause ZeroWithdraw error");
2933
2934            let expected_withdraw = (excess_stake, false);
2935
2936            withdraw_stake_prop(WithdrawParams {
2937                minimum_nominator_stake: <Test as Config>::MinOperatorStake::get(),
2938                nominators: vec![(0, operator_stake)],
2939                operator_reward: 0,
2940                nominator_id: 0,
2941                withdraws: vec![(excess_stake, Ok(()))],
2942                maybe_deposit: None,
2943                expected_withdraw: Some(expected_withdraw),
2944                expected_nominator_count_reduced_by: 0,
2945                storage_fund_change: (true, 0),
2946            })
2947        });
2948    }
2949
2950    #[test]
2953    fn prop_withdraw_excess_operator_stake_with_nominator() {
2954        prop_test!(
2955            &(PROP_OPERATOR_STAKE_RANGE, PROP_NOMINATOR_STAKE_RANGE,),
2956            |(operator_stake, nominator_stake)| {
2957                prop_assert!(
2959                    [operator_stake, nominator_stake]
2960                        .into_iter()
2961                        .try_fold(0_u128, |acc, value| acc.checked_add(value))
2962                        .is_some()
2963                );
2964
2965                let expected_withdraw = (nominator_stake, true);
2966
2967                let excess_stake = expected_withdraw
2968                    .0
2969                    .saturating_sub(<Test as Config>::MinNominatorStake::get());
2970
2971                prop_assert!(excess_stake > 0, "would cause ZeroWithdraw error");
2972
2973                withdraw_stake_prop(WithdrawParams {
2974                    minimum_nominator_stake: <Test as Config>::MinNominatorStake::get(),
2975                    nominators: vec![(0, operator_stake), (1, nominator_stake)],
2976                    operator_reward: 0,
2977                    nominator_id: 1,
2978                    withdraws: vec![(excess_stake, Ok(()))],
2979                    maybe_deposit: None,
2980                    expected_withdraw: Some(expected_withdraw),
2981                    expected_nominator_count_reduced_by: 1,
2982                    storage_fund_change: (true, 0),
2983                })
2984            }
2985        );
2986    }
2987
2988    #[test]
2991    fn prop_withdraw_excess_operator_stake_with_deposit() {
2992        prop_test!(
2993            &(PROP_OPERATOR_STAKE_RANGE, PROP_DEPOSIT_RANGE,),
2994            |(mut operator_stake, maybe_deposit)| {
2995                operator_stake = operator_stake.saturating_add(maybe_deposit);
2996
2997                prop_assert!(
2998                    operator_stake.saturating_sub(maybe_deposit)
2999                        >= <Test as Config>::MinOperatorStake::get(),
3000                    "would cause MinimumOperatorStake error"
3001                );
3002
3003                prop_assert!(
3005                    [operator_stake, maybe_deposit]
3006                        .into_iter()
3007                        .try_fold(0_u128, |acc, value| acc.checked_add(value))
3008                        .is_some()
3009                );
3010
3011                let expected_withdraw = (
3012                    STORAGE_FEE_RESERVE
3014                        .left_from_one()
3015                        .mul_ceil(operator_stake)
3016                        .saturating_sub(maybe_deposit),
3017                    true,
3018                );
3019
3020                let excess_stake = expected_withdraw
3021                    .0
3022                    .saturating_sub(<Test as Config>::MinOperatorStake::get());
3023
3024                prop_assume!(excess_stake > 0, "would cause ZeroWithdraw error");
3025
3026                let maybe_deposit = if maybe_deposit == 0 {
3028                    None
3029                } else {
3030                    Some(maybe_deposit)
3031                };
3032
3033                withdraw_stake_prop(WithdrawParams {
3034                    minimum_nominator_stake: <Test as Config>::MinNominatorStake::get(),
3035                    nominators: vec![(0, operator_stake)],
3036                    operator_reward: 0,
3037                    nominator_id: 0,
3038                    withdraws: vec![(excess_stake, Ok(()))],
3039                    maybe_deposit,
3040                    expected_withdraw: Some(expected_withdraw),
3041                    expected_nominator_count_reduced_by: 0,
3042                    storage_fund_change: (true, 0),
3043                })
3044            }
3045        );
3046    }
3047
3048    #[test]
3052    fn prop_withdraw_excess_nominator_stake_with_deposit_and_fixed_operator_stake() {
3053        prop_test!(
3054            &(PROP_NOMINATOR_STAKE_RANGE, PROP_DEPOSIT_RANGE,),
3055            |(mut nominator_stake, maybe_deposit)| {
3056                nominator_stake = nominator_stake.saturating_add(maybe_deposit);
3057
3058                prop_assert!(
3059                    nominator_stake.saturating_sub(maybe_deposit)
3060                        >= <Test as Config>::MinNominatorStake::get(),
3061                    "would cause MinimumNominatorStake error"
3062                );
3063
3064                let operator_stake = <Test as Config>::MinOperatorStake::get();
3066
3067                prop_assert!(
3069                    [operator_stake, nominator_stake, maybe_deposit]
3070                        .into_iter()
3071                        .try_fold(0_u128, |acc, value| acc.checked_add(value))
3072                        .is_some()
3073                );
3074
3075                let expected_withdraw = (
3076                    STORAGE_FEE_RESERVE
3077                        .left_from_one()
3078                        .mul_ceil(nominator_stake)
3079                        .saturating_sub(maybe_deposit),
3080                    true,
3081                );
3082
3083                let excess_stake = expected_withdraw
3084                    .0
3085                    .saturating_sub(<Test as Config>::MinNominatorStake::get());
3086
3087                prop_assume!(excess_stake > 0, "would cause ZeroWithdraw error");
3088
3089                let maybe_deposit = if maybe_deposit == 0 {
3091                    None
3092                } else {
3093                    Some(maybe_deposit)
3094                };
3095
3096                withdraw_stake_prop(WithdrawParams {
3097                    minimum_nominator_stake: <Test as Config>::MinNominatorStake::get(),
3098                    nominators: vec![(0, operator_stake), (1, nominator_stake)],
3099                    operator_reward: 0,
3100                    nominator_id: 1,
3101                    withdraws: vec![(excess_stake, Ok(()))],
3102                    maybe_deposit,
3103                    expected_withdraw: Some(expected_withdraw),
3104                    expected_nominator_count_reduced_by: 0,
3105                    storage_fund_change: (true, 0),
3106                })
3107            }
3108        );
3109    }
3110
3111    #[test]
3114    fn prop_withdraw_excess_nominator_stake_with_deposit() {
3115        prop_test!(
3116            &(
3117                PROP_OPERATOR_STAKE_RANGE,
3118                PROP_NOMINATOR_STAKE_RANGE,
3119                PROP_DEPOSIT_RANGE,
3120            ),
3121            |(operator_stake, mut nominator_stake, maybe_deposit)| {
3122                nominator_stake = nominator_stake.saturating_add(maybe_deposit);
3123
3124                prop_assert!(
3125                    nominator_stake.saturating_sub(maybe_deposit)
3126                        >= <Test as Config>::MinNominatorStake::get(),
3127                    "would cause MinimumNominatorStake error"
3128                );
3129
3130                prop_assert!(
3132                    [operator_stake, nominator_stake, maybe_deposit]
3133                        .into_iter()
3134                        .try_fold(0_u128, |acc, value| acc.checked_add(value))
3135                        .is_some()
3136                );
3137
3138                let expected_withdraw = (
3140                    STORAGE_FEE_RESERVE
3141                        .left_from_one()
3142                        .mul_ceil(nominator_stake)
3143                        .saturating_sub(maybe_deposit),
3144                    true,
3145                );
3146
3147                let excess_stake = expected_withdraw
3148                    .0
3149                    .saturating_sub(<Test as Config>::MinNominatorStake::get());
3150
3151                prop_assume!(excess_stake > 0, "would cause ZeroWithdraw error");
3152
3153                let maybe_deposit = if maybe_deposit == 0 {
3155                    None
3156                } else {
3157                    Some(maybe_deposit)
3158                };
3159
3160                withdraw_stake_prop(WithdrawParams {
3161                    minimum_nominator_stake: <Test as Config>::MinNominatorStake::get(),
3162                    nominators: vec![(0, operator_stake), (1, nominator_stake)],
3163                    operator_reward: 0,
3164                    nominator_id: 1,
3165                    withdraws: vec![(excess_stake, Ok(()))],
3166                    maybe_deposit,
3167                    expected_withdraw: Some(expected_withdraw),
3168                    expected_nominator_count_reduced_by: 0,
3169                    storage_fund_change: (true, 0),
3170                })
3171            }
3172        );
3173    }
3174
3175    #[test]
3178    fn prop_withdraw_excess_operator_stake_with_nominator_and_reward() {
3179        prop_test!(
3180            &(
3181                PROP_OPERATOR_STAKE_RANGE,
3182                PROP_NOMINATOR_STAKE_RANGE,
3183                PROP_REWARD_RANGE,
3184            ),
3185            |(operator_stake, nominator_stake, operator_reward)| {
3186                prop_assert!(
3188                    [operator_stake, nominator_stake, operator_reward]
3189                        .into_iter()
3190                        .try_fold(0_u128, |acc, value| acc.checked_add(value))
3191                        .is_some()
3192                );
3193
3194                let total_stake = operator_stake.saturating_add(nominator_stake);
3196                let operator_reward_split = Perquintill::from_rational(operator_stake, total_stake)
3197                    .mul_floor(operator_reward);
3198
3199                let excess_stake =
3202                    operator_stake.saturating_sub(2 * <Test as Config>::MinOperatorStake::get());
3203
3204                let expected_withdraw = (excess_stake.saturating_add(operator_reward_split), true);
3205
3206                prop_assert!(excess_stake > 0, "would cause ZeroWithdraw error");
3207
3208                withdraw_stake_prop(WithdrawParams {
3209                    minimum_nominator_stake: <Test as Config>::MinNominatorStake::get(),
3210                    nominators: vec![(0, operator_stake), (1, nominator_stake)],
3211                    operator_reward,
3212                    nominator_id: 0,
3213                    withdraws: vec![(excess_stake, Ok(()))],
3214                    maybe_deposit: None,
3215                    expected_withdraw: Some(expected_withdraw),
3216                    expected_nominator_count_reduced_by: 0,
3217                    storage_fund_change: (true, 0),
3218                })
3219            }
3220        );
3221    }
3222
3223    #[test]
3226    fn prop_withdraw_excess_operator_stake_with_deposit_and_reward() {
3227        prop_test!(
3228            &(
3229                PROP_OPERATOR_STAKE_RANGE,
3230                PROP_DEPOSIT_RANGE,
3231                PROP_REWARD_RANGE,
3232            ),
3233            |(mut operator_stake, maybe_deposit, operator_reward)| {
3234                operator_stake = operator_stake.saturating_add(maybe_deposit);
3235
3236                prop_assert!(
3237                    operator_stake.saturating_sub(maybe_deposit)
3238                        >= <Test as Config>::MinOperatorStake::get(),
3239                    "would cause MinimumOperatorStake error"
3240                );
3241
3242                prop_assert!(
3244                    [operator_stake, maybe_deposit, operator_reward]
3245                        .into_iter()
3246                        .try_fold(0_u128, |acc, value| acc.checked_add(value))
3247                        .is_some()
3248                );
3249
3250                let reserve = 2 * <Test as Config>::MinOperatorStake::get();
3253                let excess_stake = operator_stake.saturating_sub(reserve);
3254
3255                let expected_withdraw = (
3257                    operator_stake
3258                        .saturating_add(operator_reward)
3259                        .saturating_sub(reserve),
3260                    true,
3261                );
3262
3263                prop_assume!(excess_stake > 0, "would cause ZeroWithdraw error");
3264
3265                let maybe_deposit = if maybe_deposit == 0 {
3267                    None
3268                } else {
3269                    Some(maybe_deposit)
3270                };
3271
3272                withdraw_stake_prop(WithdrawParams {
3273                    minimum_nominator_stake: <Test as Config>::MinNominatorStake::get(),
3274                    nominators: vec![(0, operator_stake)],
3275                    operator_reward,
3276                    nominator_id: 0,
3277                    withdraws: vec![(excess_stake, Ok(()))],
3278                    maybe_deposit,
3279                    expected_withdraw: Some(expected_withdraw),
3280                    expected_nominator_count_reduced_by: 0,
3281                    storage_fund_change: (true, 0),
3282                })
3283            }
3284        );
3285    }
3286
3287    #[test]
3288    fn withdraw_stake_operator_all() {
3289        withdraw_stake(WithdrawParams {
3290            minimum_nominator_stake: 10 * AI3,
3291            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3292            operator_reward: 20 * AI3,
3293            nominator_id: 0,
3294            withdraws: vec![(150 * AI3, Err(StakingError::MinimumOperatorStake))],
3295            maybe_deposit: None,
3296            expected_withdraw: None,
3297            expected_nominator_count_reduced_by: 0,
3298            storage_fund_change: (true, 0),
3299        })
3300    }
3301
3302    #[test]
3303    fn withdraw_stake_operator_below_minimum() {
3304        withdraw_stake(WithdrawParams {
3305            minimum_nominator_stake: 10 * AI3,
3306            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3307            operator_reward: 20 * AI3,
3308            nominator_id: 0,
3309            withdraws: vec![(65 * AI3, Err(StakingError::MinimumOperatorStake))],
3310            maybe_deposit: None,
3311            expected_withdraw: None,
3312            expected_nominator_count_reduced_by: 0,
3313            storage_fund_change: (true, 0),
3314        })
3315    }
3316
3317    #[test]
3318    fn withdraw_stake_operator_below_minimum_no_rewards() {
3319        withdraw_stake(WithdrawParams {
3320            minimum_nominator_stake: 10 * AI3,
3321            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3322            operator_reward: Zero::zero(),
3323            nominator_id: 0,
3324            withdraws: vec![(51 * AI3, Err(StakingError::MinimumOperatorStake))],
3325            maybe_deposit: None,
3326            expected_withdraw: None,
3327            expected_nominator_count_reduced_by: 0,
3328            storage_fund_change: (true, 0),
3329        })
3330    }
3331
3332    #[test]
3333    fn withdraw_stake_operator_above_minimum() {
3334        withdraw_stake(WithdrawParams {
3335            minimum_nominator_stake: 10 * AI3,
3336            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3337            operator_reward: 20 * AI3,
3338            nominator_id: 0,
3339            withdraws: vec![(58 * AI3, Ok(()))],
3340            maybe_deposit: None,
3343            expected_withdraw: Some((63523809523809523770, false)),
3344            expected_nominator_count_reduced_by: 0,
3345            storage_fund_change: (true, 0),
3346        })
3347    }
3348
3349    #[test]
3350    fn withdraw_stake_operator_above_minimum_multiple_withdraws_error() {
3351        withdraw_stake(WithdrawParams {
3352            minimum_nominator_stake: 10 * AI3,
3353            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3354            operator_reward: 20 * AI3,
3355            nominator_id: 0,
3356            withdraws: vec![
3357                (58 * AI3, Ok(())),
3358                (5 * AI3, Err(StakingError::MinimumOperatorStake)),
3359            ],
3360            maybe_deposit: None,
3361            expected_withdraw: Some((63523809523809523770, false)),
3362            expected_nominator_count_reduced_by: 0,
3363            storage_fund_change: (true, 0),
3364        })
3365    }
3366
3367    #[test]
3368    fn withdraw_stake_operator_above_minimum_multiple_withdraws() {
3369        withdraw_stake(WithdrawParams {
3370            minimum_nominator_stake: 10 * AI3,
3371            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3372            operator_reward: 20 * AI3,
3373            nominator_id: 0,
3374            withdraws: vec![(53 * AI3, Ok(())), (5 * AI3, Ok(()))],
3375            maybe_deposit: None,
3376            expected_withdraw: Some((63523809523809523769, false)),
3377            expected_nominator_count_reduced_by: 0,
3378            storage_fund_change: (true, 0),
3379        })
3380    }
3381
3382    #[test]
3383    fn withdraw_stake_operator_above_minimum_no_rewards() {
3384        withdraw_stake(WithdrawParams {
3385            minimum_nominator_stake: 10 * AI3,
3386            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3387            operator_reward: Zero::zero(),
3388            nominator_id: 0,
3389            withdraws: vec![(49 * AI3, Ok(()))],
3390            maybe_deposit: None,
3391            expected_withdraw: Some((48999999999999999980, false)),
3392            expected_nominator_count_reduced_by: 0,
3393            storage_fund_change: (true, 0),
3394        })
3395    }
3396
3397    #[test]
3398    fn withdraw_stake_operator_above_minimum_multiple_withdraws_no_rewards() {
3399        withdraw_stake(WithdrawParams {
3400            minimum_nominator_stake: 10 * AI3,
3401            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3402            operator_reward: Zero::zero(),
3403            nominator_id: 0,
3404            withdraws: vec![(29 * AI3, Ok(())), (20 * AI3, Ok(()))],
3405            maybe_deposit: None,
3406            expected_withdraw: Some((48999999999999999981, false)),
3407            expected_nominator_count_reduced_by: 0,
3408            storage_fund_change: (true, 0),
3409        })
3410    }
3411
3412    #[test]
3413    fn withdraw_stake_operator_above_minimum_multiple_withdraws_no_rewards_with_errors() {
3414        withdraw_stake(WithdrawParams {
3415            minimum_nominator_stake: 10 * AI3,
3416            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3417            operator_reward: Zero::zero(),
3418            nominator_id: 0,
3419            withdraws: vec![
3420                (29 * AI3, Ok(())),
3421                (20 * AI3, Ok(())),
3422                (20 * AI3, Err(StakingError::MinimumOperatorStake)),
3423            ],
3424            maybe_deposit: None,
3425            expected_withdraw: Some((48999999999999999981, false)),
3426            expected_nominator_count_reduced_by: 0,
3427            storage_fund_change: (true, 0),
3428        })
3429    }
3430
3431    #[test]
3432    fn withdraw_stake_nominator_below_minimum_with_rewards() {
3433        withdraw_stake(WithdrawParams {
3434            minimum_nominator_stake: 10 * AI3,
3435            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3436            operator_reward: 20 * AI3,
3437            nominator_id: 1,
3438            withdraws: vec![(45 * AI3, Ok(()))],
3439            maybe_deposit: None,
3443            expected_withdraw: Some((54761904761904761888, true)),
3444            expected_nominator_count_reduced_by: 1,
3445            storage_fund_change: (true, 0),
3446        })
3447    }
3448
3449    #[test]
3450    fn withdraw_stake_nominator_below_minimum_with_rewards_multiple_withdraws() {
3451        withdraw_stake(WithdrawParams {
3452            minimum_nominator_stake: 10 * AI3,
3453            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3454            operator_reward: 20 * AI3,
3455            nominator_id: 1,
3456            withdraws: vec![(25 * AI3, Ok(())), (20 * AI3, Ok(()))],
3457            maybe_deposit: None,
3461            expected_withdraw: Some((54761904761904761888, true)),
3462            expected_nominator_count_reduced_by: 1,
3463            storage_fund_change: (true, 0),
3464        })
3465    }
3466
3467    #[test]
3468    fn withdraw_stake_nominator_below_minimum_with_rewards_multiple_withdraws_with_errors() {
3469        withdraw_stake(WithdrawParams {
3470            minimum_nominator_stake: 10 * AI3,
3471            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3472            operator_reward: 20 * AI3,
3473            nominator_id: 1,
3474            withdraws: vec![
3475                (25 * AI3, Ok(())),
3476                (20 * AI3, Ok(())),
3477                (20 * AI3, Err(StakingError::InsufficientShares)),
3478            ],
3479            maybe_deposit: None,
3483            expected_withdraw: Some((54761904761904761888, true)),
3484            expected_nominator_count_reduced_by: 1,
3485            storage_fund_change: (true, 0),
3486        })
3487    }
3488
3489    #[test]
3490    fn withdraw_stake_nominator_below_minimum_no_reward() {
3491        withdraw_stake(WithdrawParams {
3492            minimum_nominator_stake: 10 * AI3,
3493            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3494            operator_reward: Zero::zero(),
3495            nominator_id: 1,
3496            withdraws: vec![(45 * AI3, Ok(()))],
3497            maybe_deposit: None,
3498            expected_withdraw: Some((50 * AI3, true)),
3499            expected_nominator_count_reduced_by: 1,
3500            storage_fund_change: (true, 0),
3501        })
3502    }
3503
3504    #[test]
3505    fn withdraw_stake_nominator_below_minimum_no_reward_multiple_rewards() {
3506        withdraw_stake(WithdrawParams {
3507            minimum_nominator_stake: 10 * AI3,
3508            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3509            operator_reward: Zero::zero(),
3510            nominator_id: 1,
3511            withdraws: vec![(25 * AI3, Ok(())), (20 * AI3, Ok(()))],
3512            maybe_deposit: None,
3513            expected_withdraw: Some((50 * AI3, true)),
3514            expected_nominator_count_reduced_by: 1,
3515            storage_fund_change: (true, 0),
3516        })
3517    }
3518
3519    #[test]
3520    fn withdraw_stake_nominator_below_minimum_no_reward_multiple_rewards_with_errors() {
3521        withdraw_stake(WithdrawParams {
3522            minimum_nominator_stake: 10 * AI3,
3523            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3524            operator_reward: Zero::zero(),
3525            nominator_id: 1,
3526            withdraws: vec![
3527                (25 * AI3, Ok(())),
3528                (20 * AI3, Ok(())),
3529                (20 * AI3, Err(StakingError::InsufficientShares)),
3530            ],
3531            maybe_deposit: None,
3532            expected_withdraw: Some((50 * AI3, true)),
3533            expected_nominator_count_reduced_by: 1,
3534            storage_fund_change: (true, 0),
3535        })
3536    }
3537
3538    #[test]
3539    fn withdraw_stake_nominator_above_minimum() {
3540        withdraw_stake(WithdrawParams {
3541            minimum_nominator_stake: 10 * AI3,
3542            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3543            operator_reward: 20 * AI3,
3544            nominator_id: 1,
3545            withdraws: vec![(40 * AI3, Ok(()))],
3546            maybe_deposit: None,
3547            expected_withdraw: Some((43809523809523809511, false)),
3548            expected_nominator_count_reduced_by: 0,
3549            storage_fund_change: (true, 0),
3550        })
3551    }
3552
3553    #[test]
3554    fn withdraw_stake_nominator_above_minimum_multiple_withdraws() {
3555        withdraw_stake(WithdrawParams {
3556            minimum_nominator_stake: 10 * AI3,
3557            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3558            operator_reward: 20 * AI3,
3559            nominator_id: 1,
3560            withdraws: vec![(35 * AI3, Ok(())), (5 * AI3, Ok(()))],
3561            maybe_deposit: None,
3562            expected_withdraw: Some((43809523809523809510, false)),
3563            expected_nominator_count_reduced_by: 0,
3564            storage_fund_change: (true, 0),
3565        })
3566    }
3567
3568    #[test]
3569    fn withdraw_stake_nominator_above_minimum_withdraw_all_multiple_withdraws_error() {
3570        withdraw_stake(WithdrawParams {
3571            minimum_nominator_stake: 10 * AI3,
3572            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3573            operator_reward: 20 * AI3,
3574            nominator_id: 1,
3575            withdraws: vec![
3576                (35 * AI3, Ok(())),
3577                (5 * AI3, Ok(())),
3578                (15 * AI3, Err(StakingError::InsufficientShares)),
3579            ],
3580            maybe_deposit: None,
3581            expected_withdraw: Some((43809523809523809510, false)),
3582            expected_nominator_count_reduced_by: 0,
3583            storage_fund_change: (true, 0),
3584        })
3585    }
3586
3587    #[test]
3588    fn withdraw_stake_nominator_above_minimum_no_rewards() {
3589        withdraw_stake(WithdrawParams {
3590            minimum_nominator_stake: 10 * AI3,
3591            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3592            operator_reward: Zero::zero(),
3593            nominator_id: 1,
3594            withdraws: vec![(39 * AI3, Ok(()))],
3595            maybe_deposit: None,
3596            expected_withdraw: Some((39 * AI3, false)),
3597            expected_nominator_count_reduced_by: 0,
3598            storage_fund_change: (true, 0),
3599        })
3600    }
3601
3602    #[test]
3603    fn withdraw_stake_nominator_above_minimum_no_rewards_multiple_withdraws() {
3604        withdraw_stake(WithdrawParams {
3605            minimum_nominator_stake: 10 * AI3,
3606            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3607            operator_reward: Zero::zero(),
3608            nominator_id: 1,
3609            withdraws: vec![(35 * AI3, Ok(())), (5 * AI3 - 100000000000, Ok(()))],
3610            maybe_deposit: None,
3611            expected_withdraw: Some((39999999899999999998, false)),
3612            expected_nominator_count_reduced_by: 0,
3613            storage_fund_change: (true, 0),
3614        })
3615    }
3616
3617    #[test]
3618    fn withdraw_stake_nominator_above_minimum_no_rewards_multiple_withdraws_with_errors() {
3619        withdraw_stake(WithdrawParams {
3620            minimum_nominator_stake: 10 * AI3,
3621            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3622            operator_reward: Zero::zero(),
3623            nominator_id: 1,
3624            withdraws: vec![
3625                (35 * AI3, Ok(())),
3626                (5 * AI3 - 100000000000, Ok(())),
3627                (15 * AI3, Err(StakingError::InsufficientShares)),
3628            ],
3629            maybe_deposit: None,
3630            expected_withdraw: Some((39999999899999999998, false)),
3631            expected_nominator_count_reduced_by: 0,
3632            storage_fund_change: (true, 0),
3633        })
3634    }
3635
3636    #[test]
3637    fn withdraw_stake_nominator_no_rewards_multiple_withdraws_with_error_min_nominator_stake() {
3638        withdraw_stake(WithdrawParams {
3639            minimum_nominator_stake: 10 * AI3,
3640            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3641            operator_reward: Zero::zero(),
3642            nominator_id: 1,
3643            withdraws: vec![
3644                (35 * AI3, Ok(())),
3645                (5 * AI3 - 100000000000, Ok(())),
3646                (10 * AI3, Err(StakingError::MinimumNominatorStake)),
3647            ],
3648            maybe_deposit: Some(2 * AI3),
3649            expected_withdraw: Some((39999999899999999998, false)),
3650            expected_nominator_count_reduced_by: 0,
3651            storage_fund_change: (true, 0),
3652        })
3653    }
3654
3655    #[test]
3656    fn withdraw_stake_nominator_with_rewards_multiple_withdraws_with_error_min_nominator_stake() {
3657        withdraw_stake(WithdrawParams {
3658            minimum_nominator_stake: 10 * AI3,
3659            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3660            operator_reward: 20 * AI3,
3661            nominator_id: 1,
3662            withdraws: vec![
3663                (35 * AI3, Ok(())),
3664                (5 * AI3, Ok(())),
3665                (10 * AI3, Err(StakingError::MinimumNominatorStake)),
3666            ],
3667            maybe_deposit: Some(2 * AI3),
3671            expected_withdraw: Some((43809523809523809510, false)),
3672            expected_nominator_count_reduced_by: 0,
3673            storage_fund_change: (true, 0),
3674        })
3675    }
3676
3677    #[test]
3678    fn withdraw_stake_nominator_zero_amount() {
3679        withdraw_stake(WithdrawParams {
3680            minimum_nominator_stake: 10 * AI3,
3681            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3682            operator_reward: Zero::zero(),
3683            nominator_id: 1,
3684            withdraws: vec![(0, Err(StakingError::ZeroWithdraw))],
3685            maybe_deposit: None,
3686            expected_withdraw: None,
3687            expected_nominator_count_reduced_by: 0,
3688            storage_fund_change: (true, 0),
3689        })
3690    }
3691
3692    #[test]
3693    fn withdraw_stake_nominator_all_with_storage_fee_profit() {
3694        withdraw_stake(WithdrawParams {
3695            minimum_nominator_stake: 10 * AI3,
3696            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3697            operator_reward: Zero::zero(),
3698            nominator_id: 1,
3699            withdraws: vec![(50 * AI3, Ok(()))],
3700            maybe_deposit: None,
3701            storage_fund_change: (true, 21),
3704            expected_withdraw: Some((54999999999999999985, true)),
3705            expected_nominator_count_reduced_by: 1,
3706        })
3707    }
3708
3709    #[test]
3710    fn withdraw_stake_nominator_all_with_storage_fee_loss() {
3711        withdraw_stake(WithdrawParams {
3712            minimum_nominator_stake: 10 * AI3,
3713            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3714            operator_reward: Zero::zero(),
3715            nominator_id: 1,
3716            withdraws: vec![(50 * AI3, Ok(()))],
3717            maybe_deposit: None,
3718            storage_fund_change: (false, 21),
3721            expected_withdraw: Some((44999999999999999995, true)),
3722            expected_nominator_count_reduced_by: 1,
3723        })
3724    }
3725
3726    #[test]
3727    fn withdraw_stake_nominator_all_with_storage_fee_loss_all() {
3728        withdraw_stake(WithdrawParams {
3729            minimum_nominator_stake: 10 * AI3,
3730            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3731            operator_reward: Zero::zero(),
3732            nominator_id: 1,
3733            withdraws: vec![(50 * AI3, Ok(()))],
3734            maybe_deposit: None,
3735            storage_fund_change: (false, 42),
3738            expected_withdraw: Some((40 * AI3, true)),
3739            expected_nominator_count_reduced_by: 1,
3740        })
3741    }
3742
3743    #[test]
3744    fn withdraw_stake_nominator_multiple_withdraws_with_storage_fee_profit() {
3745        withdraw_stake(WithdrawParams {
3746            minimum_nominator_stake: 10 * AI3,
3747            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3748            operator_reward: Zero::zero(),
3749            nominator_id: 1,
3750            withdraws: vec![(5 * AI3, Ok(())), (10 * AI3, Ok(())), (15 * AI3, Ok(()))],
3751            maybe_deposit: None,
3752            storage_fund_change: (true, 21),
3756            expected_withdraw: Some((30 * AI3 + 2999999999999999863, false)),
3757            expected_nominator_count_reduced_by: 0,
3758        })
3759    }
3760
3761    #[test]
3762    fn withdraw_stake_nominator_multiple_withdraws_with_storage_fee_loss() {
3763        withdraw_stake(WithdrawParams {
3764            minimum_nominator_stake: 10 * AI3,
3765            nominators: vec![(0, 150 * AI3), (1, 50 * AI3), (2, 10 * AI3)],
3766            operator_reward: Zero::zero(),
3767            nominator_id: 1,
3768            withdraws: vec![(5 * AI3, Ok(())), (5 * AI3, Ok(())), (10 * AI3, Ok(()))],
3769            maybe_deposit: None,
3770            storage_fund_change: (false, 21),
3774            expected_withdraw: Some((20 * AI3 - 2 * AI3 - 39, false)),
3775            expected_nominator_count_reduced_by: 0,
3776        })
3777    }
3778
3779    #[test]
3780    fn unlock_multiple_withdrawals() {
3781        let domain_id = DomainId::new(0);
3782        let operator_account = 1;
3783        let operator_free_balance = 250 * AI3;
3784        let operator_stake = 200 * AI3;
3785        let pair = OperatorPair::from_seed(&[0; 32]);
3786        let nominator_account = 2;
3787        let nominator_free_balance = 150 * AI3;
3788        let nominator_stake = 100 * AI3;
3789
3790        let nominators = vec![
3791            (operator_account, (operator_free_balance, operator_stake)),
3792            (nominator_account, (nominator_free_balance, nominator_stake)),
3793        ];
3794
3795        let total_deposit = 300 * AI3;
3796        let init_total_stake = STORAGE_FEE_RESERVE.left_from_one() * total_deposit;
3797        let init_total_storage_fund = STORAGE_FEE_RESERVE * total_deposit;
3798
3799        let mut ext = new_test_ext();
3800        ext.execute_with(|| {
3801            let (operator_id, _) = register_operator(
3802                domain_id,
3803                operator_account,
3804                operator_free_balance,
3805                operator_stake,
3806                10 * AI3,
3807                pair.public(),
3808                Default::default(),
3809                BTreeMap::from_iter(nominators),
3810            );
3811
3812            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
3813            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
3814            assert_eq!(domain_stake_summary.current_total_stake, init_total_stake);
3815
3816            let operator = Operators::<Test>::get(operator_id).unwrap();
3817            assert_eq!(operator.current_total_stake, init_total_stake);
3818            assert_eq!(operator.total_storage_fee_deposit, init_total_storage_fund);
3819            assert_eq!(
3820                operator.total_storage_fee_deposit,
3821                bundle_storage_fund::total_balance::<Test>(operator_id)
3822            );
3823
3824            let shares_per_withdraw = init_total_stake / 100;
3826            let head_domain_number = HeadDomainNumber::<Test>::get(domain_id);
3827
3828            for _ in 1..<Test as crate::Config>::WithdrawalLimit::get() {
3830                do_withdraw_stake::<Test>(operator_id, nominator_account, shares_per_withdraw)
3831                    .unwrap();
3832                do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
3833            }
3834            HeadDomainNumber::<Test>::set(domain_id, head_domain_number + 1);
3836
3837            for _ in 0..5 {
3840                do_withdraw_stake::<Test>(operator_id, nominator_account, shares_per_withdraw)
3841                    .unwrap();
3842            }
3843            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
3844
3845            assert_err!(
3847                do_withdraw_stake::<Test>(operator_id, nominator_account, shares_per_withdraw,),
3848                StakingError::TooManyWithdrawals
3849            );
3850            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
3851            Withdrawals::<Test>::try_mutate(operator_id, nominator_account, |maybe_withdrawal| {
3852                let withdrawal = maybe_withdrawal.as_mut().unwrap();
3853                do_convert_previous_epoch_withdrawal::<Test>(
3854                    operator_id,
3855                    withdrawal,
3856                    domain_stake_summary.current_epoch_index,
3857                )
3858                .unwrap();
3859                assert_eq!(
3860                    withdrawal.withdrawals.len() as u32,
3861                    <Test as crate::Config>::WithdrawalLimit::get()
3862                );
3863                Ok::<(), StakingError>(())
3864            })
3865            .unwrap();
3866
3867            HeadDomainNumber::<Test>::set(
3869                domain_id,
3870                head_domain_number + <Test as crate::Config>::StakeWithdrawalLockingPeriod::get(),
3871            );
3872            let total_balance = Balances::usable_balance(nominator_account);
3873            assert_ok!(do_unlock_funds::<Test>(operator_id, nominator_account));
3874            assert_eq!(
3875                Balances::usable_balance(nominator_account) + 74, total_balance
3877                    + (<Test as crate::Config>::WithdrawalLimit::get() as u128 - 1) * total_deposit
3878                        / 100
3879            );
3880            let withdrawal = Withdrawals::<Test>::get(operator_id, nominator_account).unwrap();
3881            assert_eq!(withdrawal.withdrawals.len(), 1);
3882
3883            HeadDomainNumber::<Test>::set(
3885                domain_id,
3886                head_domain_number
3887                    + <Test as crate::Config>::StakeWithdrawalLockingPeriod::get()
3888                    + 1,
3889            );
3890            let total_balance = Balances::usable_balance(nominator_account);
3891            assert_ok!(do_unlock_funds::<Test>(operator_id, nominator_account));
3892            assert_eq!(
3893                Balances::usable_balance(nominator_account) - 2, total_balance + 5 * total_deposit / 100
3895            );
3896            assert!(Withdrawals::<Test>::get(operator_id, nominator_account).is_none());
3897        });
3898    }
3899
3900    #[test]
3901    fn slash_operator() {
3902        let domain_id = DomainId::new(0);
3903        let operator_account = 1;
3904        let operator_free_balance = 250 * AI3;
3905        let operator_stake = 200 * AI3;
3906        let operator_extra_deposit = 40 * AI3;
3907        let pair = OperatorPair::from_seed(&[0; 32]);
3908        let nominator_account = 2;
3909        let nominator_free_balance = 150 * AI3;
3910        let nominator_stake = 100 * AI3;
3911        let nominator_extra_deposit = 40 * AI3;
3912
3913        let nominators = vec![
3914            (operator_account, (operator_free_balance, operator_stake)),
3915            (nominator_account, (nominator_free_balance, nominator_stake)),
3916        ];
3917
3918        let unlocking = vec![(operator_account, 10 * AI3), (nominator_account, 10 * AI3)];
3919
3920        let deposits = vec![
3921            (operator_account, operator_extra_deposit),
3922            (nominator_account, nominator_extra_deposit),
3923        ];
3924
3925        let init_total_stake = STORAGE_FEE_RESERVE.left_from_one() * 300 * AI3;
3926        let init_total_storage_fund = STORAGE_FEE_RESERVE * 300 * AI3;
3927
3928        let mut ext = new_test_ext();
3929        ext.execute_with(|| {
3930            let (operator_id, _) = register_operator(
3931                domain_id,
3932                operator_account,
3933                operator_free_balance,
3934                operator_stake,
3935                10 * AI3,
3936                pair.public(),
3937                Default::default(),
3938                BTreeMap::from_iter(nominators),
3939            );
3940
3941            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
3942            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
3943            assert_eq!(domain_stake_summary.current_total_stake, init_total_stake);
3944
3945            let operator = Operators::<Test>::get(operator_id).unwrap();
3946            assert_eq!(operator.current_total_stake, init_total_stake);
3947            assert_eq!(operator.total_storage_fee_deposit, init_total_storage_fund);
3948            assert_eq!(
3949                operator.total_storage_fee_deposit,
3950                bundle_storage_fund::total_balance::<Test>(operator_id)
3951            );
3952
3953            for unlock in &unlocking {
3954                do_withdraw_stake::<Test>(operator_id, unlock.0, unlock.1).unwrap();
3955            }
3956
3957            do_reward_operators::<Test>(
3958                domain_id,
3959                OperatorRewardSource::Dummy,
3960                vec![operator_id].into_iter(),
3961                20 * AI3,
3962            )
3963            .unwrap();
3964            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
3965
3966            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
3968            for id in [operator_account, nominator_account] {
3969                Withdrawals::<Test>::try_mutate(operator_id, id, |maybe_withdrawal| {
3970                    do_convert_previous_epoch_withdrawal::<Test>(
3971                        operator_id,
3972                        maybe_withdrawal.as_mut().unwrap(),
3973                        domain_stake_summary.current_epoch_index,
3974                    )
3975                })
3976                .unwrap();
3977            }
3978
3979            let operator = Operators::<Test>::get(operator_id).unwrap();
3982            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
3983            let operator_withdrawal =
3984                Withdrawals::<Test>::get(operator_id, operator_account).unwrap();
3985            let nominator_withdrawal =
3986                Withdrawals::<Test>::get(operator_id, nominator_account).unwrap();
3987
3988            let total_deposit =
3989                domain_stake_summary.current_total_stake + operator.total_storage_fee_deposit;
3990            let total_stake_withdrawal = operator_withdrawal.total_withdrawal_amount
3991                + nominator_withdrawal.total_withdrawal_amount;
3992            let total_storage_fee_withdrawal = operator_withdrawal.withdrawals[0]
3993                .storage_fee_refund
3994                + nominator_withdrawal.withdrawals[0].storage_fee_refund;
3995            assert_eq!(293333333333333333336, total_deposit,);
3996            assert_eq!(21666666666666666664, total_stake_withdrawal);
3997            assert_eq!(5000000000000000000, total_storage_fee_withdrawal);
3998            assert_eq!(
3999                320 * AI3,
4000                total_deposit + total_stake_withdrawal + total_storage_fee_withdrawal
4001            );
4002            assert_eq!(
4003                operator.total_storage_fee_deposit,
4004                bundle_storage_fund::total_balance::<Test>(operator_id)
4005            );
4006
4007            for deposit in deposits {
4008                do_nominate_operator::<Test>(operator_id, deposit.0, deposit.1).unwrap();
4009            }
4010
4011            do_mark_operators_as_slashed::<Test>(
4012                vec![operator_id],
4013                SlashedReason::InvalidBundle(1),
4014            )
4015            .unwrap();
4016
4017            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
4018            assert!(!domain_stake_summary.next_operators.contains(&operator_id));
4019
4020            let operator = Operators::<Test>::get(operator_id).unwrap();
4021            assert_eq!(
4022                *operator.status::<Test>(operator_id),
4023                OperatorStatus::Slashed
4024            );
4025
4026            let pending_slashes = PendingSlashes::<Test>::get(domain_id).unwrap();
4027            assert!(pending_slashes.contains(&operator_id));
4028
4029            assert_eq!(
4030                Balances::total_balance(&crate::tests::TreasuryAccount::get()),
4031                0
4032            );
4033
4034            do_slash_operator::<Test>(domain_id, MAX_NOMINATORS_TO_SLASH).unwrap();
4035            assert_eq!(PendingSlashes::<Test>::get(domain_id), None);
4036            assert_eq!(Operators::<Test>::get(operator_id), None);
4037            assert_eq!(OperatorIdOwner::<Test>::get(operator_id), None);
4038
4039            assert_eq!(
4040                Balances::total_balance(&operator_account),
4041                operator_free_balance - operator_stake
4042            );
4043            assert_eq!(
4044                Balances::total_balance(&nominator_account),
4045                nominator_free_balance - nominator_stake
4046            );
4047
4048            assert!(Balances::total_balance(&crate::tests::TreasuryAccount::get()) >= 320 * AI3);
4049            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), 0);
4050        });
4051    }
4052
4053    #[test]
4054    fn slash_operator_with_more_than_max_nominators_to_slash() {
4055        let domain_id = DomainId::new(0);
4056        let operator_account = 1;
4057        let operator_free_balance = 250 * AI3;
4058        let operator_stake = 200 * AI3;
4059        let operator_extra_deposit = 40 * AI3;
4060        let operator_extra_withdraw = 5 * AI3;
4061        let pair = OperatorPair::from_seed(&[0; 32]);
4062
4063        let nominator_accounts: Vec<crate::tests::AccountId> = (2..22).collect();
4064        let nominator_free_balance = 150 * AI3;
4065        let nominator_stake = 100 * AI3;
4066        let nominator_extra_deposit = 40 * AI3;
4067        let nominator_extra_withdraw = 5 * AI3;
4068
4069        let mut nominators = vec![(operator_account, (operator_free_balance, operator_stake))];
4070        for nominator_account in nominator_accounts.clone() {
4071            nominators.push((nominator_account, (nominator_free_balance, nominator_stake)))
4072        }
4073
4074        let last_nominator_account = nominator_accounts.last().cloned().unwrap();
4075        let unlocking = vec![
4076            (operator_account, 10 * AI3),
4077            (last_nominator_account, 10 * AI3),
4078        ];
4079
4080        let deposits = vec![
4081            (operator_account, operator_extra_deposit),
4082            (last_nominator_account, nominator_extra_deposit),
4083        ];
4084        let withdrawals = vec![
4085            (operator_account, operator_extra_withdraw),
4086            (last_nominator_account, nominator_extra_withdraw),
4087        ];
4088
4089        let init_total_stake = STORAGE_FEE_RESERVE.left_from_one()
4090            * (200 + (100 * nominator_accounts.len() as u128))
4091            * AI3;
4092        let init_total_storage_fund =
4093            STORAGE_FEE_RESERVE * (200 + (100 * nominator_accounts.len() as u128)) * AI3;
4094
4095        let mut ext = new_test_ext();
4096        ext.execute_with(|| {
4097            let (operator_id, _) = register_operator(
4098                domain_id,
4099                operator_account,
4100                operator_free_balance,
4101                operator_stake,
4102                10 * AI3,
4103                pair.public(),
4104                Default::default(),
4105                BTreeMap::from_iter(nominators),
4106            );
4107
4108            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
4109            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
4110            assert_eq!(domain_stake_summary.current_total_stake, init_total_stake);
4111
4112            let operator = Operators::<Test>::get(operator_id).unwrap();
4113            assert_eq!(operator.current_total_stake, init_total_stake);
4114            assert_eq!(operator.total_storage_fee_deposit, init_total_storage_fund);
4115            assert_eq!(
4116                operator.total_storage_fee_deposit,
4117                bundle_storage_fund::total_balance::<Test>(operator_id)
4118            );
4119
4120            for unlock in &unlocking {
4121                do_withdraw_stake::<Test>(operator_id, unlock.0, unlock.1).unwrap();
4122            }
4123
4124            do_reward_operators::<Test>(
4125                domain_id,
4126                OperatorRewardSource::Dummy,
4127                vec![operator_id].into_iter(),
4128                20 * AI3,
4129            )
4130            .unwrap();
4131            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
4132
4133            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
4135            for id in [operator_account, last_nominator_account] {
4136                Withdrawals::<Test>::try_mutate(operator_id, id, |maybe_withdrawal| {
4137                    do_convert_previous_epoch_withdrawal::<Test>(
4138                        operator_id,
4139                        maybe_withdrawal.as_mut().unwrap(),
4140                        domain_stake_summary.current_epoch_index,
4141                    )
4142                })
4143                .unwrap();
4144            }
4145
4146            let operator = Operators::<Test>::get(operator_id).unwrap();
4149            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
4150            let operator_withdrawal =
4151                Withdrawals::<Test>::get(operator_id, operator_account).unwrap();
4152            let nominator_withdrawal =
4153                Withdrawals::<Test>::get(operator_id, last_nominator_account).unwrap();
4154
4155            let total_deposit =
4156                domain_stake_summary.current_total_stake + operator.total_storage_fee_deposit;
4157            let total_stake_withdrawal = operator_withdrawal.total_withdrawal_amount
4158                + nominator_withdrawal.total_withdrawal_amount;
4159            let total_storage_fee_withdrawal = operator_withdrawal.withdrawals[0]
4160                .storage_fee_refund
4161                + nominator_withdrawal.withdrawals[0].storage_fee_refund;
4162            assert_eq!(2194772727272727272734, total_deposit,);
4163            assert_eq!(20227272727272727266, total_stake_withdrawal);
4164            assert_eq!(5000000000000000000, total_storage_fee_withdrawal);
4165            assert_eq!(
4166                2220 * AI3,
4167                total_deposit + total_stake_withdrawal + total_storage_fee_withdrawal
4168            );
4169
4170            assert_eq!(
4171                operator.total_storage_fee_deposit,
4172                bundle_storage_fund::total_balance::<Test>(operator_id)
4173            );
4174
4175            for deposit in deposits {
4176                do_nominate_operator::<Test>(operator_id, deposit.0, deposit.1).unwrap();
4177            }
4178            for withdrawal in withdrawals {
4179                do_withdraw_stake::<Test>(
4180                    operator_id,
4181                    withdrawal.0,
4182                    withdrawal.1,
4185                )
4186                .unwrap();
4187            }
4188
4189            do_mark_operators_as_slashed::<Test>(
4190                vec![operator_id],
4191                SlashedReason::InvalidBundle(1),
4192            )
4193            .unwrap();
4194
4195            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
4196
4197            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
4198            assert!(!domain_stake_summary.next_operators.contains(&operator_id));
4199
4200            let operator = Operators::<Test>::get(operator_id).unwrap();
4201            assert_eq!(
4202                *operator.status::<Test>(operator_id),
4203                OperatorStatus::Slashed
4204            );
4205
4206            let pending_slashes = PendingSlashes::<Test>::get(domain_id).unwrap();
4207            assert!(pending_slashes.contains(&operator_id));
4208
4209            assert_eq!(
4210                Balances::total_balance(&crate::tests::TreasuryAccount::get()),
4211                0
4212            );
4213
4214            do_slash_operator::<Test>(domain_id, MAX_NOMINATORS_TO_SLASH).unwrap();
4217            do_slash_operator::<Test>(domain_id, MAX_NOMINATORS_TO_SLASH).unwrap();
4218            do_slash_operator::<Test>(domain_id, MAX_NOMINATORS_TO_SLASH).unwrap();
4219
4220            assert_eq!(PendingSlashes::<Test>::get(domain_id), None);
4221            assert_eq!(Operators::<Test>::get(operator_id), None);
4222            assert_eq!(OperatorIdOwner::<Test>::get(operator_id), None);
4223
4224            assert_eq!(
4225                Balances::total_balance(&operator_account),
4226                operator_free_balance - operator_stake
4227            );
4228            for nominator_account in nominator_accounts {
4229                assert_eq!(
4230                    Balances::total_balance(&nominator_account),
4231                    nominator_free_balance - nominator_stake
4232                );
4233            }
4234
4235            assert_eq!(
4236                Balances::total_balance(&crate::tests::TreasuryAccount::get()),
4237                2220 * AI3
4238            );
4239            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), 0);
4240        });
4241    }
4242
4243    #[test]
4244    fn slash_operators() {
4245        let domain_id = DomainId::new(0);
4246        let operator_free_balance = 250 * AI3;
4247        let operator_stake = 200 * AI3;
4248
4249        let operator_account_1 = 1;
4250        let operator_account_2 = 2;
4251        let operator_account_3 = 3;
4252
4253        let pair_1 = OperatorPair::from_seed(&[0; 32]);
4254        let pair_2 = OperatorPair::from_seed(&[1; 32]);
4255        let pair_3 = OperatorPair::from_seed(&[2; 32]);
4256
4257        let mut ext = new_test_ext();
4258        ext.execute_with(|| {
4259            let (operator_id_1, _) = register_operator(
4260                domain_id,
4261                operator_account_1,
4262                operator_free_balance,
4263                operator_stake,
4264                10 * AI3,
4265                pair_1.public(),
4266                Default::default(),
4267                Default::default(),
4268            );
4269
4270            let (operator_id_2, _) = register_operator(
4271                domain_id,
4272                operator_account_2,
4273                operator_free_balance,
4274                operator_stake,
4275                10 * AI3,
4276                pair_2.public(),
4277                Default::default(),
4278                Default::default(),
4279            );
4280
4281            let (operator_id_3, _) = register_operator(
4282                domain_id,
4283                operator_account_3,
4284                operator_free_balance,
4285                operator_stake,
4286                10 * AI3,
4287                pair_3.public(),
4288                Default::default(),
4289                Default::default(),
4290            );
4291
4292            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
4293            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
4294            assert!(domain_stake_summary.next_operators.contains(&operator_id_1));
4295            assert!(domain_stake_summary.next_operators.contains(&operator_id_2));
4296            assert!(domain_stake_summary.next_operators.contains(&operator_id_3));
4297            assert_eq!(
4298                domain_stake_summary.current_total_stake,
4299                STORAGE_FEE_RESERVE.left_from_one() * 600 * AI3
4300            );
4301            for operator_id in [operator_id_1, operator_id_2, operator_id_3] {
4302                let operator = Operators::<Test>::get(operator_id).unwrap();
4303                assert_eq!(
4304                    operator.total_storage_fee_deposit,
4305                    STORAGE_FEE_RESERVE * operator_stake
4306                );
4307                assert_eq!(
4308                    operator.total_storage_fee_deposit,
4309                    bundle_storage_fund::total_balance::<Test>(operator_id)
4310                );
4311            }
4312
4313            let res = Domains::deactivate_operator(RuntimeOrigin::root(), operator_id_3);
4315            assert_ok!(res);
4316            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
4317
4318            let operator = Operators::<Test>::get(operator_id_3).unwrap();
4319            assert_eq!(
4320                *operator.status::<Test>(operator_id_3),
4321                OperatorStatus::Deactivated(7)
4322            );
4323
4324            do_mark_operators_as_slashed::<Test>(
4325                vec![operator_id_1],
4326                SlashedReason::InvalidBundle(1),
4327            )
4328            .unwrap();
4329            do_mark_operators_as_slashed::<Test>(
4330                vec![operator_id_2],
4331                SlashedReason::InvalidBundle(2),
4332            )
4333            .unwrap();
4334            do_mark_operators_as_slashed::<Test>(
4335                vec![operator_id_3],
4336                SlashedReason::InvalidBundle(3),
4337            )
4338            .unwrap();
4339
4340            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
4341            assert!(!domain_stake_summary.next_operators.contains(&operator_id_1));
4342            assert!(!domain_stake_summary.next_operators.contains(&operator_id_2));
4343            assert!(!domain_stake_summary.next_operators.contains(&operator_id_3));
4344
4345            let operator = Operators::<Test>::get(operator_id_1).unwrap();
4346            assert_eq!(
4347                *operator.status::<Test>(operator_id_1),
4348                OperatorStatus::Slashed
4349            );
4350
4351            let operator = Operators::<Test>::get(operator_id_2).unwrap();
4352            assert_eq!(
4353                *operator.status::<Test>(operator_id_2),
4354                OperatorStatus::Slashed
4355            );
4356
4357            let operator = Operators::<Test>::get(operator_id_3).unwrap();
4358            assert_eq!(
4359                *operator.status::<Test>(operator_id_3),
4360                OperatorStatus::Slashed
4361            );
4362
4363            assert_eq!(
4364                Balances::total_balance(&crate::tests::TreasuryAccount::get()),
4365                0
4366            );
4367
4368            let slashed_operators = PendingSlashes::<Test>::get(domain_id).unwrap();
4369            slashed_operators.into_iter().for_each(|_| {
4370                do_slash_operator::<Test>(domain_id, MAX_NOMINATORS_TO_SLASH).unwrap();
4371            });
4372
4373            assert_eq!(PendingSlashes::<Test>::get(domain_id), None);
4374            assert_eq!(Operators::<Test>::get(operator_id_1), None);
4375            assert_eq!(OperatorIdOwner::<Test>::get(operator_id_1), None);
4376            assert_eq!(Operators::<Test>::get(operator_id_2), None);
4377            assert_eq!(OperatorIdOwner::<Test>::get(operator_id_2), None);
4378            assert_eq!(Operators::<Test>::get(operator_id_3), None);
4379            assert_eq!(OperatorIdOwner::<Test>::get(operator_id_3), None);
4380
4381            assert_eq!(
4382                Balances::total_balance(&crate::tests::TreasuryAccount::get()),
4383                600 * AI3
4384            );
4385            for operator_id in [operator_id_1, operator_id_2, operator_id_3] {
4386                assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), 0);
4387            }
4388        });
4389    }
4390
4391    #[test]
4392    fn bundle_storage_fund_charged_and_refund_storage_fee() {
4393        let domain_id = DomainId::new(0);
4394        let operator_account = 1;
4395        let operator_free_balance = 150 * AI3;
4396        let operator_total_stake = 100 * AI3;
4397        let operator_stake = 80 * AI3;
4398        let operator_storage_fee_deposit = 20 * AI3;
4399        let pair = OperatorPair::from_seed(&[0; 32]);
4400        let nominator_account = 2;
4401
4402        let mut ext = new_test_ext();
4403        ext.execute_with(|| {
4404            let (operator_id, _) = register_operator(
4405                domain_id,
4406                operator_account,
4407                operator_free_balance,
4408                operator_total_stake,
4409                AI3,
4410                pair.public(),
4411                Default::default(),
4412                BTreeMap::default(),
4413            );
4414
4415            let domain_staking_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
4416            assert_eq!(domain_staking_summary.current_total_stake, operator_stake);
4417
4418            let operator = Operators::<Test>::get(operator_id).unwrap();
4419            assert_eq!(operator.current_total_stake, operator_stake);
4420            assert_eq!(operator.current_total_shares, operator_stake);
4421            assert_eq!(
4422                operator.total_storage_fee_deposit,
4423                operator_storage_fee_deposit
4424            );
4425
4426            bundle_storage_fund::charge_bundle_storage_fee::<Test>(
4428                operator_id,
4429                (operator_storage_fee_deposit / AI3) as u32,
4431            )
4432            .unwrap();
4433            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), 0);
4434            assert_err!(
4435                bundle_storage_fund::charge_bundle_storage_fee::<Test>(operator_id, 1,),
4436                bundle_storage_fund::Error::BundleStorageFeePayment
4437            );
4438
4439            do_nominate_operator::<Test>(operator_id, operator_account, 5 * AI3).unwrap();
4441            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), AI3);
4442
4443            bundle_storage_fund::charge_bundle_storage_fee::<Test>(operator_id, 1).unwrap();
4444            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), 0);
4445
4446            Balances::set_balance(&nominator_account, 100 * AI3);
4448            do_nominate_operator::<Test>(operator_id, nominator_account, 5 * AI3).unwrap();
4449            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), AI3);
4450
4451            bundle_storage_fund::charge_bundle_storage_fee::<Test>(operator_id, 1).unwrap();
4452            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), 0);
4453
4454            bundle_storage_fund::refund_storage_fee::<Test>(
4456                10 * AI3,
4457                BTreeMap::from_iter([(operator_id, 1), (operator_id + 1, 9)]),
4458            )
4459            .unwrap();
4460            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), AI3);
4461
4462            assert_eq!(
4464                Balances::total_balance(&crate::tests::TreasuryAccount::get()),
4465                9 * AI3
4466            );
4467
4468            bundle_storage_fund::charge_bundle_storage_fee::<Test>(operator_id, 1).unwrap();
4469            assert_eq!(bundle_storage_fund::total_balance::<Test>(operator_id), 0);
4470        });
4471    }
4472
4473    #[test]
4474    fn zero_amount_deposit_and_withdraw() {
4475        let domain_id = DomainId::new(0);
4476        let operator_account = 1;
4477        let operator_free_balance = 250 * AI3;
4478        let operator_stake = 200 * AI3;
4479        let pair = OperatorPair::from_seed(&[0; 32]);
4480        let nominator_account = 2;
4481        let nominator_free_balance = 150 * AI3;
4482        let nominator_stake = 100 * AI3;
4483
4484        let nominators = vec![
4485            (operator_account, (operator_free_balance, operator_stake)),
4486            (nominator_account, (nominator_free_balance, nominator_stake)),
4487        ];
4488
4489        let total_deposit = 300 * AI3;
4490        let init_total_stake = STORAGE_FEE_RESERVE.left_from_one() * total_deposit;
4491
4492        let mut ext = new_test_ext();
4493        ext.execute_with(|| {
4494            let (operator_id, _) = register_operator(
4495                domain_id,
4496                operator_account,
4497                operator_free_balance,
4498                operator_stake,
4499                10 * AI3,
4500                pair.public(),
4501                Default::default(),
4502                BTreeMap::from_iter(nominators),
4503            );
4504
4505            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
4506            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
4507            assert_eq!(domain_stake_summary.current_total_stake, init_total_stake);
4508
4509            assert_err!(
4511                do_nominate_operator::<Test>(operator_id, nominator_account, 0),
4512                StakingError::ZeroDeposit
4513            );
4514
4515            assert_err!(
4517                do_withdraw_stake::<Test>(operator_id, nominator_account, 0),
4518                StakingError::ZeroWithdraw
4519            );
4520
4521            do_withdraw_stake::<Test>(
4523                operator_id,
4524                nominator_account,
4525                STORAGE_FEE_RESERVE.left_from_one() * operator_stake - MinOperatorStake::get(),
4527            )
4528            .unwrap();
4529            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
4530        });
4531    }
4532
4533    #[test]
4534    fn deposit_and_withdraw_should_be_rejected_due_to_missing_share_price() {
4535        let domain_id = DomainId::new(0);
4536        let operator_account = 1;
4537        let operator_free_balance = 250 * AI3;
4538        let operator_stake = 200 * AI3;
4539        let pair = OperatorPair::from_seed(&[0; 32]);
4540        let nominator_account = 2;
4541        let nominator_free_balance = 150 * AI3;
4542        let nominator_stake = 100 * AI3;
4543
4544        let nominators = vec![
4545            (operator_account, (operator_free_balance, operator_stake)),
4546            (nominator_account, (nominator_free_balance, nominator_stake)),
4547        ];
4548
4549        let total_deposit = 300 * AI3;
4550        let init_total_stake = STORAGE_FEE_RESERVE.left_from_one() * total_deposit;
4551
4552        let mut ext = new_test_ext();
4553        ext.execute_with(|| {
4554            let (operator_id, _) = register_operator(
4555                domain_id,
4556                operator_account,
4557                operator_free_balance,
4558                operator_stake,
4559                10 * AI3,
4560                pair.public(),
4561                Default::default(),
4562                BTreeMap::from_iter(nominators),
4563            );
4564
4565            do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
4566            let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
4567            assert_eq!(domain_stake_summary.current_total_stake, init_total_stake);
4568
4569            do_nominate_operator::<Test>(operator_id, nominator_account, 5 * AI3).unwrap();
4570            do_withdraw_stake::<Test>(operator_id, nominator_account, 3 * AI3).unwrap();
4572
4573            let previous_epoch = do_finalize_domain_current_epoch::<Test>(domain_id).unwrap();
4575            OperatorEpochSharePrice::<Test>::remove(
4577                operator_id,
4578                DomainEpoch::from((domain_id, previous_epoch.completed_epoch_index)),
4579            );
4580
4581            assert_err!(
4583                do_nominate_operator::<Test>(operator_id, nominator_account, AI3),
4584                StakingError::MissingOperatorEpochSharePrice
4585            );
4586            assert_err!(
4587                do_withdraw_stake::<Test>(operator_id, nominator_account, 1),
4588                StakingError::MissingOperatorEpochSharePrice
4589            );
4590        });
4591    }
4592
4593    #[test]
4594    fn test_share_price_deposit() {
4595        let total_shares = 45 * AI3;
4596        let total_stake = 45 * AI3 + 37;
4597        let sp = SharePrice::new::<Test>(total_shares, total_stake).unwrap();
4598
4599        let to_deposit_stakes = [
4601            5,
4602            7,
4603            9,
4604            11,
4605            17,
4606            23,
4607            934,
4608            24931,
4609            349083467,
4610            2 * AI3 + 32,
4611            52 * AI3 - 4729034,
4612            2732 * AI3 - 1720,
4613            1117 * AI3 + 1839832,
4614            31232 * AI3 - 987654321,
4615        ];
4616
4617        let mut deposited_share = 0;
4618        let mut deposited_stake = 0;
4619        for to_deposit_stake in to_deposit_stakes {
4620            let to_deposit_share = sp.stake_to_shares::<Test>(to_deposit_stake);
4621
4622            deposited_stake += to_deposit_stake;
4624            deposited_share += to_deposit_share;
4627
4628            let total_deposited_share = sp.stake_to_shares::<Test>(deposited_stake);
4633
4634            assert!(total_deposited_share >= deposited_share);
4639
4640            assert!(total_stake + deposited_stake > total_shares + total_deposited_share);
4643        }
4644    }
4645
4646    #[test]
4647    fn test_share_price_withdraw() {
4648        let total_shares = 123 * AI3;
4649        let total_stake = 123 * AI3 + 13;
4650        let sp = SharePrice::new::<Test>(total_shares, total_stake).unwrap();
4651
4652        let to_withdraw_shares = [
4654            1,
4655            3,
4656            7,
4657            13,
4658            17,
4659            123,
4660            43553,
4661            546393039,
4662            15 * AI3 + 1342,
4663            2 * AI3 - 423,
4664            31 * AI3 - 1321,
4665            42 * AI3 + 4564234,
4666            7 * AI3 - 987654321,
4667            3 * AI3 + 987654321123879,
4668        ];
4669
4670        let mut withdrawn_share = 0;
4671        let mut withdrawn_stake = 0;
4672        for to_withdraw_share in to_withdraw_shares {
4673            let to_withdraw_stake = sp.shares_to_stake::<Test>(to_withdraw_share);
4674
4675            withdrawn_share += to_withdraw_share;
4677            withdrawn_stake += to_withdraw_stake;
4680
4681            let total_withdrawn_stake = sp.shares_to_stake::<Test>(withdrawn_share);
4686
4687            assert!(total_withdrawn_stake >= withdrawn_stake);
4691
4692            assert!(total_stake - withdrawn_stake >= total_shares - withdrawn_share);
4695        }
4696    }
4697
4698    #[test]
4699    fn test_share_price_unlock() {
4700        let mut total_shares = 20 * AI3;
4701        let mut total_stake = 20 * AI3 + 12;
4702
4703        for to_unlock_share in [
4709            AI3 + 123,
4710            2 * AI3 - 456,
4711            3 * AI3 - 789,
4712            4 * AI3 - 123 + 456,
4713            7 * AI3 + 789 - 987654321,
4714            3 * AI3 + 987654321,
4715        ] {
4716            let sp = SharePrice::new::<Test>(total_shares, total_stake).unwrap();
4717
4718            let to_unlock_stake = sp.shares_to_stake::<Test>(to_unlock_share);
4719
4720            total_shares = total_shares.checked_sub(to_unlock_share).unwrap();
4721            total_stake = total_stake.checked_sub(to_unlock_stake).unwrap();
4722        }
4723        assert_eq!(total_shares, 0);
4724    }
4725}