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