git.delta.rocks / unique-network / refs/commits / 91c34acacac8

difftreelog

Merge pull request #882 from UniqueNetwork/feature/app-promo-unstake-behaviour

Yaroslav Bolyukin2023-02-15parents: #9f4fc06 #803eec0.patch.diff
in: master
Feature/app promo unstake behaviour

14 files changed

modifiedCargo.lockdiffbeforeafterboth
57825782
5783[[package]]5783[[package]]
5784name = "pallet-app-promotion"5784name = "pallet-app-promotion"
5785version = "0.1.4"5785version = "0.1.5"
5786dependencies = [5786dependencies = [
5787 "frame-benchmarking",5787 "frame-benchmarking",
5788 "frame-support",5788 "frame-support",
modifiedpallets/app-promotion/CHANGELOG.mddiffbeforeafterboth
44
5<!-- bureaucrate goes here -->5<!-- bureaucrate goes here -->
66
7## [0.1.5] - 2023-02-14
8
9### Added
10
11- `unstake_partial` extrinsic.
12
7## [0.1.4] - 2023-01-3113## [0.1.4] - 2023-01-31
814
9### Changed15### Changed
modifiedpallets/app-promotion/Cargo.tomldiffbeforeafterboth
9license = 'GPLv3'9license = 'GPLv3'
10name = 'pallet-app-promotion'10name = 'pallet-app-promotion'
11repository = 'https://github.com/UniqueNetwork/unique-chain'11repository = 'https://github.com/UniqueNetwork/unique-chain'
12version = '0.1.4'12version = '0.1.5'
1313
14[package.metadata.docs.rs]14[package.metadata.docs.rs]
15targets = ['x86_64-unknown-linux-gnu']15targets = ['x86_64-unknown-linux-gnu']
modifiedpallets/app-promotion/src/benchmarking.rsdiffbeforeafterboth
65 let staker = account::<T::AccountId>("staker", index, SEED);65 let staker = account::<T::AccountId>("staker", index, SEED);
66 <T as Config>::Currency::make_free_balance_be(&staker, Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());66 <T as Config>::Currency::make_free_balance_be(&staker, Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
67 PromototionPallet::<T>::stake(RawOrigin::Signed(staker.clone()).into(), Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get())?;67 PromototionPallet::<T>::stake(RawOrigin::Signed(staker.clone()).into(), Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get())?;
68 PromototionPallet::<T>::unstake(RawOrigin::Signed(staker.clone()).into()).map_err(|e| e.error)?;68 PromototionPallet::<T>::unstake_all(RawOrigin::Signed(staker.clone()).into())?;
69 Result::<(), sp_runtime::DispatchError>::Ok(())69 Result::<(), sp_runtime::DispatchError>::Ok(())
70 })?;70 })?;
71 let block_number = <frame_system::Pallet<T>>::current_block_number() + T::PendingInterval::get();71 let block_number = <frame_system::Pallet<T>>::current_block_number() + T::PendingInterval::get();
115 let _ = <T as Config>::Currency::make_free_balance_be(&caller, Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());115 let _ = <T as Config>::Currency::make_free_balance_be(&caller, Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
116 } : _(RawOrigin::Signed(caller.clone()), share * <T as Config>::Currency::total_balance(&caller))116 } : _(RawOrigin::Signed(caller.clone()), share * <T as Config>::Currency::total_balance(&caller))
117117
118 unstake {118 unstake_all {
119 let caller = account::<T::AccountId>("caller", 0, SEED);119 let caller = account::<T::AccountId>("caller", 0, SEED);
120 let share = Perbill::from_rational(1u32, 20);120 let share = Perbill::from_rational(1u32, 20);
121 let _ = <T as Config>::Currency::make_free_balance_be(&caller, Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());121 let _ = <T as Config>::Currency::make_free_balance_be(&caller, Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
130130
131 } : _(RawOrigin::Signed(caller.clone()))131 } : _(RawOrigin::Signed(caller.clone()))
132
133 unstake_partial {
134 let caller = account::<T::AccountId>("caller", 0, SEED);
135 let share = Perbill::from_rational(1u32, 20);
136 let _ = <T as Config>::Currency::make_free_balance_be(&caller, Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
137 (1..11).map(|i| {
138 // used to change block number
139 <frame_system::Pallet<T>>::set_block_number(i.into());
140 T::RelayBlockNumberProvider::set_block_number((2*i).into());
141 assert_eq!(<frame_system::Pallet<T>>::block_number(), i.into());
142 assert_eq!(T::RelayBlockNumberProvider::current_block_number(), (2*i).into());
143 PromototionPallet::<T>::stake(RawOrigin::Signed(caller.clone()).into(), Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get())
144 }).collect::<Result<Vec<_>, _>>()?;
145
146 } : _(RawOrigin::Signed(caller.clone()), Into::<BalanceOf<T>>::into(1000u128) * T::Nominal::get())
132147
133 sponsor_collection {148 sponsor_collection {
134 let pallet_admin = account::<T::AccountId>("admin", 0, SEED);149 let pallet_admin = account::<T::AccountId>("admin", 0, SEED);
modifiedpallets/app-promotion/src/lib.rsdiffbeforeafterboth
72 traits::{72 traits::{
73 Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,73 Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,
74 },74 },
75 ensure,75 ensure, BoundedVec,
76};76};
7777
78use weights::WeightInfo;78use weights::WeightInfo;
156 pub struct Pallet<T>(_);156 pub struct Pallet<T>(_);
157157
158 #[pallet::event]158 #[pallet::event]
159 #[pallet::generate_deposit(fn deposit_event)]159 #[pallet::generate_deposit(pub(super) fn deposit_event)]
160 pub enum Event<T: Config> {160 pub enum Event<T: Config> {
161 /// Staking recalculation was performed161 /// Staking recalculation was performed
162 ///162 ///
208 SponsorNotSet,208 SponsorNotSet,
209 /// Errors caused by incorrect actions with a locked balance.209 /// Errors caused by incorrect actions with a locked balance.
210 IncorrectLockedBalanceOperation,210 IncorrectLockedBalanceOperation,
211 /// Errors caused by insufficient staked balance.
212 InsufficientStakedBalance,
211 }213 }
212214
213 /// Stores the total staked amount.215 /// Stores the total staked amount.
489 }491 }
490492
491 /// Unstakes all stakes.493 /// Unstakes all stakes.
492 /// Moves the sum of all stakes to the `reserved` state.
493 /// After the end of `PendingInterval` this sum becomes completely494 /// After the end of `PendingInterval` this sum becomes completely
494 /// free for further use.495 /// free for further use.
495 #[pallet::call_index(2)]496 #[pallet::call_index(2)]
496 #[pallet::weight(<T as Config>::WeightInfo::unstake())]497 #[pallet::weight(<T as Config>::WeightInfo::unstake_all())]
497 pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {498 pub fn unstake_all(staker: OriginFor<T>) -> DispatchResult {
498 let staker_id = ensure_signed(staker)?;499 let staker_id = ensure_signed(staker)?;
499 let config = <PalletConfiguration<T>>::get();
500500
501 // calculate block number where the sum would be free501 Self::unstake_all_internal(staker_id)
502 let block = <frame_system::Pallet<T>>::block_number() + config.pending_interval;502 }
503503
504 let mut pendings = <PendingUnstake<T>>::get(block);504 /// Unstakes the amount of balance for the staker.
505 /// After the end of `PendingInterval` this sum becomes completely
506 /// free for further use.
507 ///
508 /// # Arguments
509 ///
510 /// * `staker`: staker account.
511 /// * `amount`: amount of unstaked funds.
512 #[pallet::call_index(8)]
513 #[pallet::weight(<T as Config>::WeightInfo::unstake_partial())]
514 pub fn unstake_partial(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {
515 let staker_id = ensure_signed(staker)?;
505516
506 // checks that we can do unreserve stakes in the block517 Self::unstake_partial_internal(staker_id, amount)
507 ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);
508
509 let mut total_stakes = 0u64;
510
511 let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))
512 .map(|(_, (amount, _))| {
513 total_stakes += 1;
514 amount
515 })
516 .sum();
517
518 if total_staked.is_zero() {
519 return Ok(None::<Weight>.into()); // TO-DO
520 }
521
522 pendings
523 .try_push((staker_id.clone(), total_staked))
524 .map_err(|_| Error::<T>::PendingForBlockOverflow)?;
525
526 <PendingUnstake<T>>::insert(block, pendings);
527
528 TotalStaked::<T>::set(
529 TotalStaked::<T>::get()
530 .checked_sub(&total_staked)
531 .ok_or(ArithmeticError::Underflow)?,
532 );
533
534 StakesPerAccount::<T>::remove(&staker_id);
535
536 Self::deposit_event(Event::Unstake(staker_id, total_staked));
537
538 Ok(None::<Weight>.into())
539 }518 }
540519
541 /// Sets the pallet to be the sponsor for the collection.520 /// Sets the pallet to be the sponsor for the collection.
809 T::PalletId::get().into_account_truncating()788 T::PalletId::get().into_account_truncating()
810 }789 }
811790
791 /// Unstakes the balance for the staker.
792 ///
793 /// - `staker`: staker account.
794 /// - `amount`: amount of unstaked funds.
795 fn unstake_partial_internal(
796 staker_id: T::AccountId,
797 unstaked_balance: BalanceOf<T>,
798 ) -> DispatchResult {
799 if unstaked_balance == Default::default() {
800 return Ok(());
801 }
802
803 let config = <PalletConfiguration<T>>::get();
804
805 // calculate block number where the sum would be free
806 let unpending_block = <frame_system::Pallet<T>>::block_number() + config.pending_interval;
807
808 let mut pendings = <PendingUnstake<T>>::get(unpending_block);
809
810 // checks that we can do unstake in the block
811 ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);
812
813 let mut stakes = Staked::<T>::iter_prefix((&staker_id,)).collect::<Vec<_>>();
814
815 let total_staked = stakes
816 .iter()
817 .fold(<BalanceOf<T>>::default(), |acc, (_, (balance, _))| {
818 acc + *balance
819 });
820
821 ensure!(
822 unstaked_balance <= total_staked,
823 <Error<T>>::InsufficientStakedBalance
824 );
825
826 <TotalStaked<T>>::set(
827 <TotalStaked<T>>::get()
828 .checked_sub(&unstaked_balance)
829 .ok_or(ArithmeticError::Underflow)?,
830 );
831
832 stakes.sort_by_key(|(block, _)| *block);
833
834 let mut acc_amount = unstaked_balance;
835 let mut will_deleted_stakes_count = 0u8;
836
837 let changed_stakes = stakes
838 .into_iter()
839 .map_while(|(block, (balance_per_block, _))| {
840 if acc_amount == <BalanceOf<T>>::default() {
841 return None;
842 }
843 if acc_amount < balance_per_block {
844 let res = (block, balance_per_block - acc_amount);
845 acc_amount = <BalanceOf<T>>::default();
846 return Some(res);
847 } else {
848 acc_amount -= balance_per_block;
849 will_deleted_stakes_count += 1;
850 return Some((block, <BalanceOf<T>>::default()));
851 }
852 })
853 .collect::<Vec<_>>();
854
855 pendings
856 .try_push((staker_id.clone(), unstaked_balance))
857 .map_err(|_| Error::<T>::PendingForBlockOverflow)?;
858
859 StakesPerAccount::<T>::try_mutate(&staker_id, |stakes| -> DispatchResult {
860 *stakes = stakes
861 .checked_sub(will_deleted_stakes_count)
862 .ok_or(ArithmeticError::Underflow)?;
863 Ok(())
864 })?;
865
866 changed_stakes
867 .into_iter()
868 .for_each(|(staked_block, current_stake_state)| {
869 if current_stake_state == Default::default() {
870 <Staked<T>>::remove((&staker_id, staked_block));
871 } else {
872 <Staked<T>>::mutate((&staker_id, staked_block), |(old_stake_state, _)| {
873 *old_stake_state = current_stake_state
874 });
875 }
876 });
877
878 <PendingUnstake<T>>::insert(unpending_block, pendings);
879
880 Self::deposit_event(Event::Unstake(staker_id, total_staked));
881
882 Ok(())
883 }
884
812 /// Adds the balance to locked by the pallet.885 /// Adds the balance to locked by the pallet.
813 ///886 ///
10041077
1005 unsorted_res.sort_by_key(|(block, _)| *block);1078 unsorted_res.sort_by_key(|(block, _)| *block);
1006 unsorted_res1079 unsorted_res
1080 }
1081
1082 fn unstake_all_internal(staker_id: T::AccountId) -> DispatchResult {
1083 let config = <PalletConfiguration<T>>::get();
1084
1085 // calculate block number where the sum would be free
1086 let block = <frame_system::Pallet<T>>::block_number() + config.pending_interval;
1087
1088 let mut pendings = <PendingUnstake<T>>::get(block);
1089
1090 // checks that we can do unstake in the block
1091 ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);
1092
1093 let mut total_stakes = 0u64;
1094
1095 let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))
1096 .map(|(_, (amount, _))| {
1097 total_stakes += 1;
1098 amount
1099 })
1100 .sum();
1101
1102 if total_staked.is_zero() {
1103 return Ok(());
1104 }
1105
1106 pendings
1107 .try_push((staker_id.clone(), total_staked))
1108 .map_err(|_| Error::<T>::PendingForBlockOverflow)?;
1109
1110 <PendingUnstake<T>>::insert(block, pendings);
1111
1112 TotalStaked::<T>::set(
1113 TotalStaked::<T>::get()
1114 .checked_sub(&total_staked)
1115 .ok_or(ArithmeticError::Underflow)?,
1116 );
1117
1118 StakesPerAccount::<T>::remove(&staker_id);
1119
1120 Self::deposit_event(Event::Unstake(staker_id, total_staked));
1121
1122 Ok(())
1007 }1123 }
1008}1124}
10091125
modifiedpallets/app-promotion/src/weights.rsdiffbeforeafterboth
3//! Autogenerated weights for pallet_app_promotion3//! Autogenerated weights for pallet_app_promotion
4//!4//!
5//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev5//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
6//! DATE: 2022-12-25, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`6//! DATE: 2023-02-15, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
7//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 10247//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
88
9// Executed Command:9// Executed Command:
38 fn set_admin_address() -> Weight;38 fn set_admin_address() -> Weight;
39 fn payout_stakers(b: u32, ) -> Weight;39 fn payout_stakers(b: u32, ) -> Weight;
40 fn stake() -> Weight;40 fn stake() -> Weight;
41 fn unstake() -> Weight;41 fn unstake_all() -> Weight;
42 fn unstake_partial() -> Weight;
42 fn sponsor_collection() -> Weight;43 fn sponsor_collection() -> Weight;
43 fn stop_sponsoring_collection() -> Weight;44 fn stop_sponsoring_collection() -> Weight;
44 fn sponsor_contract() -> Weight;45 fn sponsor_contract() -> Weight;
49pub struct SubstrateWeight<T>(PhantomData<T>);50pub struct SubstrateWeight<T>(PhantomData<T>);
50impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {51impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
51 // Storage: AppPromotion PendingUnstake (r:1 w:0)52 // Storage: AppPromotion PendingUnstake (r:1 w:0)
53 // Storage: Balances Locks (r:1 w:1)
52 // Storage: System Account (r:1 w:1)54 // Storage: System Account (r:1 w:1)
53 fn on_initialize(b: u32, ) -> Weight {55 fn on_initialize(b: u32, ) -> Weight {
54 Weight::from_ref_time(3_079_948 as u64)56 Weight::from_ref_time(2_592_346 as u64)
55 // Standard Error: 30_37657 // Standard Error: 23_629
56 .saturating_add(Weight::from_ref_time(6_343_630 as u64).saturating_mul(b as u64))58 .saturating_add(Weight::from_ref_time(7_523_802 as u64).saturating_mul(b as u64))
57 .saturating_add(T::DbWeight::get().reads(1 as u64))59 .saturating_add(T::DbWeight::get().reads(1 as u64))
58 .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(b as u64)))60 .saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(b as u64)))
59 .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(b as u64)))61 .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(b as u64)))
60 }62 }
61 // Storage: AppPromotion Admin (r:0 w:1)63 // Storage: AppPromotion Admin (r:0 w:1)
62 fn set_admin_address() -> Weight {64 fn set_admin_address() -> Weight {
63 Weight::from_ref_time(6_653_000 as u64)65 Weight::from_ref_time(6_209_000 as u64)
64 .saturating_add(T::DbWeight::get().writes(1 as u64))66 .saturating_add(T::DbWeight::get().writes(1 as u64))
65 }67 }
66 // Storage: AppPromotion Admin (r:1 w:0)68 // Storage: AppPromotion Admin (r:1 w:0)
72 // Storage: Balances Locks (r:1 w:1)74 // Storage: Balances Locks (r:1 w:1)
73 // Storage: AppPromotion TotalStaked (r:1 w:1)75 // Storage: AppPromotion TotalStaked (r:1 w:1)
74 fn payout_stakers(b: u32, ) -> Weight {76 fn payout_stakers(b: u32, ) -> Weight {
75 Weight::from_ref_time(74_048_000 as u64)77 Weight::from_ref_time(64_917_000 as u64)
76 // Standard Error: 33_22378 // Standard Error: 34_206
77 .saturating_add(Weight::from_ref_time(57_702_092 as u64).saturating_mul(b as u64))79 .saturating_add(Weight::from_ref_time(51_518_500 as u64).saturating_mul(b as u64))
78 .saturating_add(T::DbWeight::get().reads(7 as u64))80 .saturating_add(T::DbWeight::get().reads(7 as u64))
79 .saturating_add(T::DbWeight::get().reads((12 as u64).saturating_mul(b as u64)))81 .saturating_add(T::DbWeight::get().reads((12 as u64).saturating_mul(b as u64)))
80 .saturating_add(T::DbWeight::get().writes(3 as u64))82 .saturating_add(T::DbWeight::get().writes(3 as u64))
88 // Storage: AppPromotion Staked (r:1 w:1)90 // Storage: AppPromotion Staked (r:1 w:1)
89 // Storage: AppPromotion TotalStaked (r:1 w:1)91 // Storage: AppPromotion TotalStaked (r:1 w:1)
90 fn stake() -> Weight {92 fn stake() -> Weight {
91 Weight::from_ref_time(20_314_000 as u64)93 Weight::from_ref_time(18_208_000 as u64)
92 .saturating_add(T::DbWeight::get().reads(7 as u64))94 .saturating_add(T::DbWeight::get().reads(7 as u64))
93 .saturating_add(T::DbWeight::get().writes(5 as u64))95 .saturating_add(T::DbWeight::get().writes(5 as u64))
94 }96 }
95 // Storage: Configuration AppPromomotionConfigurationOverride (r:1 w:0)97 // Storage: Configuration AppPromomotionConfigurationOverride (r:1 w:0)
96 // Storage: AppPromotion PendingUnstake (r:1 w:1)98 // Storage: AppPromotion PendingUnstake (r:1 w:1)
97 // Storage: AppPromotion Staked (r:11 w:10)99 // Storage: AppPromotion Staked (r:11 w:10)
98 // Storage: Balances Locks (r:1 w:1)
99 // Storage: System Account (r:1 w:1)
100 // Storage: AppPromotion TotalStaked (r:1 w:1)100 // Storage: AppPromotion TotalStaked (r:1 w:1)
101 // Storage: AppPromotion StakesPerAccount (r:0 w:1)101 // Storage: AppPromotion StakesPerAccount (r:0 w:1)
102 fn unstake() -> Weight {102 fn unstake_all() -> Weight {
103 Weight::from_ref_time(64_582_000 as u64)103 Weight::from_ref_time(45_018_000 as u64)
104 .saturating_add(T::DbWeight::get().reads(16 as u64))104 .saturating_add(T::DbWeight::get().reads(14 as u64))
105 .saturating_add(T::DbWeight::get().writes(15 as u64))105 .saturating_add(T::DbWeight::get().writes(13 as u64))
106 }106 }
107 // Storage: Configuration AppPromomotionConfigurationOverride (r:1 w:0)
108 // Storage: AppPromotion PendingUnstake (r:1 w:1)
109 // Storage: AppPromotion Staked (r:11 w:10)
110 // Storage: AppPromotion TotalStaked (r:1 w:1)
111 // Storage: AppPromotion StakesPerAccount (r:1 w:1)
112 fn unstake_partial() -> Weight {
113 Weight::from_ref_time(49_066_000 as u64)
114 .saturating_add(T::DbWeight::get().reads(15 as u64))
115 .saturating_add(T::DbWeight::get().writes(13 as u64))
116 }
107 // Storage: AppPromotion Admin (r:1 w:0)117 // Storage: AppPromotion Admin (r:1 w:0)
108 // Storage: Common CollectionById (r:1 w:1)118 // Storage: Common CollectionById (r:1 w:1)
109 fn sponsor_collection() -> Weight {119 fn sponsor_collection() -> Weight {
110 Weight::from_ref_time(16_364_000 as u64)120 Weight::from_ref_time(15_039_000 as u64)
111 .saturating_add(T::DbWeight::get().reads(2 as u64))121 .saturating_add(T::DbWeight::get().reads(2 as u64))
112 .saturating_add(T::DbWeight::get().writes(1 as u64))122 .saturating_add(T::DbWeight::get().writes(1 as u64))
113 }123 }
114 // Storage: AppPromotion Admin (r:1 w:0)124 // Storage: AppPromotion Admin (r:1 w:0)
115 // Storage: Common CollectionById (r:1 w:1)125 // Storage: Common CollectionById (r:1 w:1)
116 fn stop_sponsoring_collection() -> Weight {126 fn stop_sponsoring_collection() -> Weight {
117 Weight::from_ref_time(15_710_000 as u64)127 Weight::from_ref_time(14_692_000 as u64)
118 .saturating_add(T::DbWeight::get().reads(2 as u64))128 .saturating_add(T::DbWeight::get().reads(2 as u64))
119 .saturating_add(T::DbWeight::get().writes(1 as u64))129 .saturating_add(T::DbWeight::get().writes(1 as u64))
120 }130 }
121 // Storage: AppPromotion Admin (r:1 w:0)131 // Storage: AppPromotion Admin (r:1 w:0)
122 // Storage: EvmContractHelpers Sponsoring (r:0 w:1)132 // Storage: EvmContractHelpers Sponsoring (r:0 w:1)
123 fn sponsor_contract() -> Weight {133 fn sponsor_contract() -> Weight {
124 Weight::from_ref_time(12_669_000 as u64)134 Weight::from_ref_time(11_810_000 as u64)
125 .saturating_add(T::DbWeight::get().reads(1 as u64))135 .saturating_add(T::DbWeight::get().reads(1 as u64))
126 .saturating_add(T::DbWeight::get().writes(1 as u64))136 .saturating_add(T::DbWeight::get().writes(1 as u64))
127 }137 }
128 // Storage: AppPromotion Admin (r:1 w:0)138 // Storage: AppPromotion Admin (r:1 w:0)
129 // Storage: EvmContractHelpers Sponsoring (r:1 w:1)139 // Storage: EvmContractHelpers Sponsoring (r:1 w:1)
130 fn stop_sponsoring_contract() -> Weight {140 fn stop_sponsoring_contract() -> Weight {
131 Weight::from_ref_time(14_406_000 as u64)141 Weight::from_ref_time(13_570_000 as u64)
132 .saturating_add(T::DbWeight::get().reads(2 as u64))142 .saturating_add(T::DbWeight::get().reads(2 as u64))
133 .saturating_add(T::DbWeight::get().writes(1 as u64))143 .saturating_add(T::DbWeight::get().writes(1 as u64))
134 }144 }
137// For backwards compatibility and tests147// For backwards compatibility and tests
138impl WeightInfo for () {148impl WeightInfo for () {
139 // Storage: AppPromotion PendingUnstake (r:1 w:0)149 // Storage: AppPromotion PendingUnstake (r:1 w:0)
150 // Storage: Balances Locks (r:1 w:1)
140 // Storage: System Account (r:1 w:1)151 // Storage: System Account (r:1 w:1)
141 fn on_initialize(b: u32, ) -> Weight {152 fn on_initialize(b: u32, ) -> Weight {
142 Weight::from_ref_time(3_079_948 as u64)153 Weight::from_ref_time(2_592_346 as u64)
143 // Standard Error: 30_376154 // Standard Error: 23_629
144 .saturating_add(Weight::from_ref_time(6_343_630 as u64).saturating_mul(b as u64))155 .saturating_add(Weight::from_ref_time(7_523_802 as u64).saturating_mul(b as u64))
145 .saturating_add(RocksDbWeight::get().reads(1 as u64))156 .saturating_add(RocksDbWeight::get().reads(1 as u64))
146 .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(b as u64)))157 .saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(b as u64)))
147 .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(b as u64)))158 .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(b as u64)))
148 }159 }
149 // Storage: AppPromotion Admin (r:0 w:1)160 // Storage: AppPromotion Admin (r:0 w:1)
150 fn set_admin_address() -> Weight {161 fn set_admin_address() -> Weight {
151 Weight::from_ref_time(6_653_000 as u64)162 Weight::from_ref_time(6_209_000 as u64)
152 .saturating_add(RocksDbWeight::get().writes(1 as u64))163 .saturating_add(RocksDbWeight::get().writes(1 as u64))
153 }164 }
154 // Storage: AppPromotion Admin (r:1 w:0)165 // Storage: AppPromotion Admin (r:1 w:0)
160 // Storage: Balances Locks (r:1 w:1)171 // Storage: Balances Locks (r:1 w:1)
161 // Storage: AppPromotion TotalStaked (r:1 w:1)172 // Storage: AppPromotion TotalStaked (r:1 w:1)
162 fn payout_stakers(b: u32, ) -> Weight {173 fn payout_stakers(b: u32, ) -> Weight {
163 Weight::from_ref_time(74_048_000 as u64)174 Weight::from_ref_time(64_917_000 as u64)
164 // Standard Error: 33_223175 // Standard Error: 34_206
165 .saturating_add(Weight::from_ref_time(57_702_092 as u64).saturating_mul(b as u64))176 .saturating_add(Weight::from_ref_time(51_518_500 as u64).saturating_mul(b as u64))
166 .saturating_add(RocksDbWeight::get().reads(7 as u64))177 .saturating_add(RocksDbWeight::get().reads(7 as u64))
167 .saturating_add(RocksDbWeight::get().reads((12 as u64).saturating_mul(b as u64)))178 .saturating_add(RocksDbWeight::get().reads((12 as u64).saturating_mul(b as u64)))
168 .saturating_add(RocksDbWeight::get().writes(3 as u64))179 .saturating_add(RocksDbWeight::get().writes(3 as u64))
176 // Storage: AppPromotion Staked (r:1 w:1)187 // Storage: AppPromotion Staked (r:1 w:1)
177 // Storage: AppPromotion TotalStaked (r:1 w:1)188 // Storage: AppPromotion TotalStaked (r:1 w:1)
178 fn stake() -> Weight {189 fn stake() -> Weight {
179 Weight::from_ref_time(20_314_000 as u64)190 Weight::from_ref_time(18_208_000 as u64)
180 .saturating_add(RocksDbWeight::get().reads(7 as u64))191 .saturating_add(RocksDbWeight::get().reads(7 as u64))
181 .saturating_add(RocksDbWeight::get().writes(5 as u64))192 .saturating_add(RocksDbWeight::get().writes(5 as u64))
182 }193 }
183 // Storage: Configuration AppPromomotionConfigurationOverride (r:1 w:0)194 // Storage: Configuration AppPromomotionConfigurationOverride (r:1 w:0)
184 // Storage: AppPromotion PendingUnstake (r:1 w:1)195 // Storage: AppPromotion PendingUnstake (r:1 w:1)
185 // Storage: AppPromotion Staked (r:11 w:10)196 // Storage: AppPromotion Staked (r:11 w:10)
186 // Storage: Balances Locks (r:1 w:1)
187 // Storage: System Account (r:1 w:1)
188 // Storage: AppPromotion TotalStaked (r:1 w:1)197 // Storage: AppPromotion TotalStaked (r:1 w:1)
189 // Storage: AppPromotion StakesPerAccount (r:0 w:1)198 // Storage: AppPromotion StakesPerAccount (r:0 w:1)
190 fn unstake() -> Weight {199 fn unstake_all() -> Weight {
191 Weight::from_ref_time(64_582_000 as u64)200 Weight::from_ref_time(45_018_000 as u64)
192 .saturating_add(RocksDbWeight::get().reads(16 as u64))201 .saturating_add(RocksDbWeight::get().reads(14 as u64))
193 .saturating_add(RocksDbWeight::get().writes(15 as u64))202 .saturating_add(RocksDbWeight::get().writes(13 as u64))
194 }203 }
204 // Storage: Configuration AppPromomotionConfigurationOverride (r:1 w:0)
205 // Storage: AppPromotion PendingUnstake (r:1 w:1)
206 // Storage: AppPromotion Staked (r:11 w:10)
207 // Storage: AppPromotion TotalStaked (r:1 w:1)
208 // Storage: AppPromotion StakesPerAccount (r:1 w:1)
209 fn unstake_partial() -> Weight {
210 Weight::from_ref_time(49_066_000 as u64)
211 .saturating_add(RocksDbWeight::get().reads(15 as u64))
212 .saturating_add(RocksDbWeight::get().writes(13 as u64))
213 }
195 // Storage: AppPromotion Admin (r:1 w:0)214 // Storage: AppPromotion Admin (r:1 w:0)
196 // Storage: Common CollectionById (r:1 w:1)215 // Storage: Common CollectionById (r:1 w:1)
197 fn sponsor_collection() -> Weight {216 fn sponsor_collection() -> Weight {
198 Weight::from_ref_time(16_364_000 as u64)217 Weight::from_ref_time(15_039_000 as u64)
199 .saturating_add(RocksDbWeight::get().reads(2 as u64))218 .saturating_add(RocksDbWeight::get().reads(2 as u64))
200 .saturating_add(RocksDbWeight::get().writes(1 as u64))219 .saturating_add(RocksDbWeight::get().writes(1 as u64))
201 }220 }
202 // Storage: AppPromotion Admin (r:1 w:0)221 // Storage: AppPromotion Admin (r:1 w:0)
203 // Storage: Common CollectionById (r:1 w:1)222 // Storage: Common CollectionById (r:1 w:1)
204 fn stop_sponsoring_collection() -> Weight {223 fn stop_sponsoring_collection() -> Weight {
205 Weight::from_ref_time(15_710_000 as u64)224 Weight::from_ref_time(14_692_000 as u64)
206 .saturating_add(RocksDbWeight::get().reads(2 as u64))225 .saturating_add(RocksDbWeight::get().reads(2 as u64))
207 .saturating_add(RocksDbWeight::get().writes(1 as u64))226 .saturating_add(RocksDbWeight::get().writes(1 as u64))
208 }227 }
209 // Storage: AppPromotion Admin (r:1 w:0)228 // Storage: AppPromotion Admin (r:1 w:0)
210 // Storage: EvmContractHelpers Sponsoring (r:0 w:1)229 // Storage: EvmContractHelpers Sponsoring (r:0 w:1)
211 fn sponsor_contract() -> Weight {230 fn sponsor_contract() -> Weight {
212 Weight::from_ref_time(12_669_000 as u64)231 Weight::from_ref_time(11_810_000 as u64)
213 .saturating_add(RocksDbWeight::get().reads(1 as u64))232 .saturating_add(RocksDbWeight::get().reads(1 as u64))
214 .saturating_add(RocksDbWeight::get().writes(1 as u64))233 .saturating_add(RocksDbWeight::get().writes(1 as u64))
215 }234 }
216 // Storage: AppPromotion Admin (r:1 w:0)235 // Storage: AppPromotion Admin (r:1 w:0)
217 // Storage: EvmContractHelpers Sponsoring (r:1 w:1)236 // Storage: EvmContractHelpers Sponsoring (r:1 w:1)
218 fn stop_sponsoring_contract() -> Weight {237 fn stop_sponsoring_contract() -> Weight {
219 Weight::from_ref_time(14_406_000 as u64)238 Weight::from_ref_time(13_570_000 as u64)
220 .saturating_add(RocksDbWeight::get().reads(2 as u64))239 .saturating_add(RocksDbWeight::get().reads(2 as u64))
221 .saturating_add(RocksDbWeight::get().writes(1 as u64))240 .saturating_add(RocksDbWeight::get().writes(1 as u64))
222 }241 }
modifiedpallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth
25};25};
26use sp_std::prelude::*;26use sp_std::prelude::*;
27use up_data_structs::{CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, budget::Unlimited};27use up_data_structs::{
28 CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, budget::Unlimited,
29 PropertyPermission,
30};
2831
29const SEED: u32 = 1;32const SEED: u32 = 1;
modifiedpallets/refungible/src/benchmarking.rsdiffbeforeafterboth
26};26};
27use sp_std::prelude::*;27use sp_std::prelude::*;
28use up_data_structs::{CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, budget::Unlimited};28use up_data_structs::{
29 CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, budget::Unlimited,
30 PropertyPermission,
31};
2932
30const SEED: u32 = 1;33const SEED: u32 = 1;
modifiedtests/src/sub/appPromotion/appPromotion.test.tsdiffbeforeafterboth
26let nominal: bigint;26let nominal: bigint;
27let palletAddress: string;27let palletAddress: string;
28let accounts: IKeyringPair[];28let accounts: IKeyringPair[];
29let usedAccounts: IKeyringPair[] = [];
30
31function getAccount(accountsNumber: number) {
32 const accs = accounts.splice(0, accountsNumber);
33 usedAccounts.push(...accs);
34 return accs;
35}
29// App promotion periods:36// App promotion periods:
30// LOCKING_PERIOD = 12 blocks of relay37// LOCKING_PERIOD = 12 blocks of relay
31// UNLOCKING_PERIOD = 6 blocks of parachain38// UNLOCKING_PERIOD = 6 blocks of parachain
39 palletAdmin = await privateKey('//PromotionAdmin');46 palletAdmin = await privateKey('//PromotionAdmin');
40 nominal = helper.balance.getOneTokenNominal();47 nominal = helper.balance.getOneTokenNominal();
4148
42 const accountBalances = new Array(100);49 const accountBalances = new Array(200).fill(1000n);
43 accountBalances.fill(1000n);
44 accounts = await helper.arrange.createAccounts(accountBalances, donor); // create accounts-pool to speed up tests50 accounts = await helper.arrange.createAccounts(accountBalances, donor); // create accounts-pool to speed up tests
45 });51 });
46 });52 });
4753
54 afterEach(async () => {
55 await usingPlaygrounds(async (helper) => {
56 let unstakeTxs = [];
57 for (const account of usedAccounts) {
58 if (unstakeTxs.length === 3) {
59 await Promise.all(unstakeTxs);
60 unstakeTxs = [];
61 }
62 unstakeTxs.push(helper.staking.unstakeAll(account));
63 }
64 await Promise.all(unstakeTxs);
65 usedAccounts = [];
66 });
67 });
68
48 describe('stake extrinsic', () => {69 describe('stake extrinsic', () => {
49 itSub('should "lock" staking balance, add it to "staked" map, and increase "totalStaked" amount', async ({helper}) => {70 itSub('should "lock" staking balance, add it to "staked" map, and increase "totalStaked" amount', async ({helper}) => {
50 const [staker, recepient] = [accounts.pop()!, accounts.pop()!];71 const [staker, recepient] = getAccount(2);
51 const totalStakedBefore = await helper.staking.getTotalStaked();72 const totalStakedBefore = await helper.staking.getTotalStaked();
5273
53 // Minimum stake amount is 100:74 // Minimum stake amount is 100:
73 expect(totalStakedPerBlock[1].amount).to.equal(200n * nominal);94 expect(totalStakedPerBlock[1].amount).to.equal(200n * nominal);
74 });95 });
7596
76 itSub('should allow to create maximum 10 stakes for account', async ({helper}) => {97 [
98 {unstake: 'unstakeAll' as const},
99 {unstake: 'unstakePartial' as const},
100 ].map(testCase => {
101 itSub('should allow to create maximum 10 stakes for account', async ({helper}) => {
77 const [staker] = await helper.arrange.createAccounts([2000n], donor);102 const [staker] = await helper.arrange.createAccounts([2000n], donor);
78 for (let i = 0; i < 10; i++) {103 const ONE_STAKE = 100n * nominal;
104 for (let i = 0; i < 10; i++) {
79 await helper.staking.stake(staker, 100n * nominal);105 await helper.staking.stake(staker, ONE_STAKE);
80 }106 }
81107
82 // can have 10 stakes108 // can have 10 stakes
83 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(1000n * nominal);109 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(1000n * nominal);
84 expect(await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).to.have.length(10);110 expect(await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).to.have.length(10);
85111
86 await expect(helper.staking.stake(staker, 100n * nominal)).to.be.rejectedWith('appPromotion.NoPermission');112 await expect(helper.staking.stake(staker, ONE_STAKE)).to.be.rejectedWith('appPromotion.NoPermission');
87113
88 // After unstake can stake again114 // After unstake can stake again
115
89 await helper.staking.unstake(staker);116 // CASE 1: unstakeAll
117 if (testCase.unstake === 'unstakeAll') {
118 await helper.staking.unstakeAll(staker);
90 await helper.staking.stake(staker, 100n * nominal);119 expect(await helper.staking.getStakesNumber({Substrate: staker.address})).to.eq(0);
120 await helper.staking.stake(staker, 100n * nominal);
91 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.equal(100n * nominal);121 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.equal(100n * nominal);
122 }
123 // CASE 2: unstakePartial
124 else {
125 await helper.staking.unstakePartial(staker, ONE_STAKE);
126 expect(await helper.staking.getStakesNumber({Substrate: staker.address})).to.eq(9);
127 await helper.staking.stake(staker, 100n * nominal);
128 expect(await helper.staking.getStakesNumber({Substrate: staker.address})).to.eq(10);
129 await expect(helper.staking.stake(staker, 100n * nominal)).to.be.rejectedWith('appPromotion.NoPermission');
130 await helper.staking.unstakePartial(staker, 150n * nominal);
131 expect(await helper.staking.getStakesNumber({Substrate: staker.address})).to.eq(9);
132 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.equal(850n * nominal);
133 }
134 });
92 });135 });
93136
94 itSub('should allow to stake() if balance is locked with different id', async ({helper}) => {137 itSub('should allow to stake() if balance is locked with different id', async ({helper}) => {
95 const staker = accounts.pop()!;138 const [staker] = getAccount(1);
96139
97 // staker has tokens locked with vesting id:140 // staker has tokens locked with vesting id:
98 await helper.balance.vestedTransfer(donor, staker.address, {start: 0n, period: 1n, periodCount: 1n, perPeriod: 200n * nominal});141 await helper.balance.vestedTransfer(donor, staker.address, {start: 0n, period: 1n, periodCount: 1n, perPeriod: 200n * nominal});
109 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.eq(1199n * nominal);152 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.eq(1199n * nominal);
110153
111 // staker can unstake154 // staker can unstake
112 await helper.staking.unstake(staker);155 await helper.staking.unstakeAll(staker);
113 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.eq(1199n * nominal);156 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.eq(1199n * nominal);
114 const [pendingUnstake] = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});157 const [pendingUnstake] = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});
115 await helper.wait.forParachainBlockNumber(pendingUnstake.block);158 await helper.wait.forParachainBlockNumber(pendingUnstake.block);
125 });168 });
126169
127 itSub('should not allow to stake(), if stake amount is more than total free balance minus locked by staking', async ({helper}) => {170 itSub('should not allow to stake(), if stake amount is more than total free balance minus locked by staking', async ({helper}) => {
128 const staker = accounts.pop()!;171 const [staker] = getAccount(1);
129172
130 // Can't stake full balance because Alice needs to pay some fee173 // Can't stake full balance because Alice needs to pay some fee
131 await expect(helper.staking.stake(staker, 1000n * nominal)).to.be.rejected; // With('Arithmetic')174 await expect(helper.staking.stake(staker, 1000n * nominal)).to.be.rejected; // With('Arithmetic')
137 });180 });
138181
139 itSub('for different accounts in one block is possible', async ({helper}) => {182 itSub('for different accounts in one block is possible', async ({helper}) => {
140 const crowd = [accounts.pop()!, accounts.pop()!, accounts.pop()!, accounts.pop()!];183 const crowd = getAccount(4);
141184
142 const crowdStartsToStake = crowd.map(user => helper.staking.stake(user, 100n * nominal));185 const crowdStartsToStake = crowd.map(user => helper.staking.stake(user, 100n * nominal));
143 await expect(Promise.all(crowdStartsToStake)).to.be.fulfilled;186 await expect(Promise.all(crowdStartsToStake)).to.be.fulfilled;
147 });190 });
148 });191 });
149192
150 describe('unstake extrinsic', () => {193 describe('Unstaking', () => {
151 itSub('should move tokens to "pendingUnstake" map and subtract it from totalStaked', async ({helper}) => {194 [
195 {method: 'unstakeAll' as const},
196 {method: 'unstakePartial' as const},
197 ].map(testCase => {
198 itSub(`[${testCase.method}] should move tokens to "pendingUnstake" and subtract it from totalStaked`, async ({helper}) => {
152 const [staker, recepient] = [accounts.pop()!, accounts.pop()!];199 const [staker, recepient] = getAccount(2);
153 const totalStakedBefore = await helper.staking.getTotalStaked();200 const totalStakedBefore = await helper.staking.getTotalStaked();
154 await helper.staking.stake(staker, 900n * nominal);201 const STAKE_AMOUNT = 900n * nominal;
155 await helper.staking.unstake(staker);
156202
157 // Right after unstake tokens are still locked203 await helper.staking.stake(staker, STAKE_AMOUNT);
204 testCase.method === 'unstakeAll'
205 ? await helper.staking.unstakeAll(staker)
206 : await helper.staking.unstakePartial(staker, STAKE_AMOUNT);
207
208 // Right after unstake tokens are still locked
158 expect(await helper.balance.getLocked(staker.address)).to.deep.eq([{id: 'appstake', amount: 900n * nominal, reasons: 'All'}]);209 expect(await helper.staking.getStakesNumber({Substrate: staker.address})).to.eq(0);
210 expect(await helper.balance.getLocked(staker.address)).to.deep.eq([{id: 'appstake', amount: STAKE_AMOUNT, reasons: 'All'}]);
159 expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, miscFrozen: 900n * nominal, feeFrozen: 900n * nominal});211 expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, miscFrozen: STAKE_AMOUNT, feeFrozen: STAKE_AMOUNT});
160 // Staker can not transfer212 // Staker can not transfer
161 await expect(helper.balance.transferToSubstrate(staker, recepient.address, 100n * nominal)).to.be.rejectedWith('balances.LiquidityRestrictions');213 await expect(helper.balance.transferToSubstrate(staker, recepient.address, 100n * nominal)).to.be.rejectedWith('balances.LiquidityRestrictions');
162 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(900n * nominal);214 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(STAKE_AMOUNT);
163 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);215 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);
164 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore);216 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore);
217 });
165 });218 });
166219
167 itSub('should unlock balance after unlocking period ends and remove it from "pendingUnstake"', async ({helper}) => {220 [
221 {method: 'unstakeAll' as const},
222 {method: 'unstakePartial' as const},
223 ].map(testCase => {
224 itSub(`[${testCase.method}] should unlock balance after unlocking period ends and remove it from "pendingUnstake"`, async ({helper}) => {
168 const staker = accounts.pop()!;225 const [staker] = getAccount(1);
169 await helper.staking.stake(staker, 100n * nominal);226 await helper.staking.stake(staker, 100n * nominal);
170 await helper.staking.unstake(staker);227 testCase.method === 'unstakeAll'
228 ? await helper.staking.unstakeAll(staker)
229 : await helper.staking.unstakePartial(staker, 100n * nominal);
171 const [pendingUnstake] = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});230 const [pendingUnstake] = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});
172231
173 // Wait for unstaking period. Balance now free ~1000; reserved, frozen, miscFrozeb: 0n232 // Wait for unstaking period. Balance now free ~1000; reserved, frozen, miscFrozeb: 0n
174 await helper.wait.forParachainBlockNumber(pendingUnstake.block);233 await helper.wait.forParachainBlockNumber(pendingUnstake.block);
175 expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, miscFrozen: 0n, feeFrozen: 0n});234 expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, miscFrozen: 0n, feeFrozen: 0n});
176 expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);235 expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);
177236
178 // staker can transfer:237 // staker can transfer:
179 await helper.balance.transferToSubstrate(staker, donor.address, 998n * nominal);238 await helper.balance.transferToSubstrate(staker, donor.address, 998n * nominal);
180 expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(1n);239 expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(1n);
240 });
181 });241 });
182242
183 itSub('should successfully unstake multiple stakes', async ({helper}) => {243 [
244 {method: 'unstakeAll' as const},
245 {method: 'unstakePartial' as const},
246 ].map(testCase => {
247 itSub(`[${testCase.method}] should successfully unstake multiple stakes`, async ({helper}) => {
184 const staker = accounts.pop()!;248 const [staker] = getAccount(1);
185 await helper.staking.stake(staker, 100n * nominal);249 await helper.staking.stake(staker, 100n * nominal);
186 await helper.staking.stake(staker, 200n * nominal);250 await helper.staking.stake(staker, 200n * nominal);
187 await helper.staking.stake(staker, 300n * nominal);251 await helper.staking.stake(staker, 300n * nominal);
188252
189 // staked: [100, 200, 300]; unstaked: 0253 // staked: [100, 200, 300]; unstaked: 0
190 let totalPendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});254 let totalPendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});
191 let pendingUnstake = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});255 let pendingUnstake = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});
192 let stakes = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});256 let stakes = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
193 expect(totalPendingUnstake).to.be.deep.equal(0n);257 expect(totalPendingUnstake).to.be.deep.equal(0n);
194 expect(pendingUnstake).to.be.deep.equal([]);258 expect(pendingUnstake).to.be.deep.equal([]);
195 expect(stakes[0].amount).to.equal(100n * nominal);259 expect(stakes[0].amount).to.equal(100n * nominal);
196 expect(stakes[1].amount).to.equal(200n * nominal);260 expect(stakes[1].amount).to.equal(200n * nominal);
197 expect(stakes[2].amount).to.equal(300n * nominal);261 expect(stakes[2].amount).to.equal(300n * nominal);
198262
199 // Can unstake multiple stakes263 // Can unstake multiple stakes
200 await helper.staking.unstake(staker);264 testCase.method === 'unstakeAll'
201 pendingUnstake = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});
202 totalPendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});265 ? await helper.staking.unstakeAll(staker)
203 stakes = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
204 expect(totalPendingUnstake).to.be.equal(600n * nominal);266 : await helper.staking.unstakePartial(staker, 600n * nominal);
205 expect(stakes).to.be.deep.equal([]);
206 expect(pendingUnstake[0].amount).to.equal(600n * nominal);
207267
208 expect (await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, feeFrozen: 600n * nominal, miscFrozen: 600n * nominal});268 pendingUnstake = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});
269 totalPendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});
270 stakes = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
271 expect(totalPendingUnstake).to.be.equal(600n * nominal);
272 expect(stakes).to.be.deep.equal([]);
273 expect(pendingUnstake[0].amount).to.equal(600n * nominal);
274
275 expect (await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, feeFrozen: 600n * nominal, miscFrozen: 600n * nominal});
209 expect (await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);276 expect (await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);
210 await helper.wait.forParachainBlockNumber(pendingUnstake[0].block);277 await helper.wait.forParachainBlockNumber(pendingUnstake[0].block);
211 expect (await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, feeFrozen: 0n, miscFrozen: 0n});278 expect (await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, feeFrozen: 0n, miscFrozen: 0n});
212 expect (await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);279 expect (await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);
280 });
213 });281 });
214282
215 itSub('should not have any effects if no active stakes', async ({helper}) => {283 [
284 {method: 'unstakeAll' as const},
285 {method: 'unstakePartial' as const},
286 ].map(testCase => {
287 itSub(`[${testCase.method}] should not have any effects if no active stakes`, async ({helper}) => {
216 const staker = accounts.pop()!;288 const [staker] = getAccount(1);
217289
218 // unstake has no effect if no stakes at all290 // unstake has no effect if no stakes at all
219 await helper.staking.unstake(staker);291 testCase.method === 'unstakeAll'
220 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(0n);292 ? await helper.staking.unstakeAll(staker)
221 expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n); // TODO bigint closeTo helper293 : await expect(helper.staking.unstakePartial(staker, 100n * nominal)).to.be.rejectedWith('appPromotion.InsufficientStakedBalance');
222294
223 // TODO stake() unstake() waitUnstaked() unstake();295 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(0n);
296 expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n); // TODO bigint closeTo helper
224297
225 // can't unstake if there are only pendingUnstakes298 // TODO stake() unstake() waitUnstaked() unstake();
226 await helper.staking.stake(staker, 100n * nominal);
227 await helper.staking.unstake(staker);
228 await helper.staking.unstake(staker);
229299
230 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal);300 // can't unstake if there are only pendingUnstakes
301 await helper.staking.stake(staker, 100n * nominal);
302
303 if (testCase.method === 'unstakeAll') {
304 await helper.staking.unstakeAll(staker);
305 await helper.staking.unstakeAll(staker);
306 } else {
307 await helper.staking.unstakePartial(staker, 100n * nominal);
308 await expect(helper.staking.unstakePartial(staker, 100n * nominal)).to.be.rejectedWith('appPromotion.InsufficientStakedBalance');
309 }
310
311 expect(await helper.staking.getStakesNumber({Substrate: staker.address})).to.eq(0);
312 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal);
231 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);313 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);
314 });
232 });315 });
233316
234 itSub('should keep different unlocking block for each unlocking stake', async ({helper}) => {317 [
318 {method: 'unstakeAll' as const},
319 {method: 'unstakePartial' as const},
320 ].map(testCase => {
321 itSub(`[${testCase.method}] should create different pending-unlock for each unlocking stake`, async ({helper}) => {
235 const staker = accounts.pop()!;322 const [staker] = getAccount(1);
236 await helper.staking.stake(staker, 100n * nominal);323 await helper.staking.stake(staker, 100n * nominal);
237 await helper.staking.unstake(staker);324 testCase.method === 'unstakeAll'
325 ? await helper.staking.unstakeAll(staker)
326 : await helper.staking.unstakePartial(staker, 100n * nominal);
238 await helper.staking.stake(staker, 120n * nominal);327 await helper.staking.stake(staker, 120n * nominal);
239 await helper.staking.unstake(staker);328 testCase.method === 'unstakeAll'
329 ? await helper.staking.unstakeAll(staker)
330 : await helper.staking.unstakePartial(staker, 120n * nominal);
240331
241 const unstakingPerBlock = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});332 const unstakingPerBlock = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});
242 expect(unstakingPerBlock).has.length(2);333 expect(unstakingPerBlock).has.length(2);
243 expect(unstakingPerBlock[0].amount).to.equal(100n * nominal);334 expect(unstakingPerBlock[0].amount).to.equal(100n * nominal);
244 expect(unstakingPerBlock[1].amount).to.equal(120n * nominal);335 expect(unstakingPerBlock[1].amount).to.equal(120n * nominal);
336 expect(await helper.staking.getStakesNumber({Substrate: staker.address})).to.equal(0);
337 });
245 });338 });
246339
247 itSub('should be possible for 3 accounts in one block', async ({helper}) => {340 [
341 {method: 'unstakeAll' as const},
342 {method: 'unstakePartial' as const},
343 ].map(testCase => {
344 itSub(`[${testCase.method}] should be possible for 3 accounts in one block`, async ({helper}) => {
248 const stakers = [accounts.pop()!, accounts.pop()!, accounts.pop()!];345 const stakers = getAccount(3);
249346
250 await Promise.all(stakers.map(staker => helper.staking.stake(staker, 100n * nominal)));347 await Promise.all(stakers.map(staker => helper.staking.stake(staker, 100n * nominal)));
251 await Promise.all(stakers.map(staker => helper.staking.unstake(staker)));348 await Promise.all(stakers.map(staker => {
349 return testCase.method === 'unstakeAll'
350 ? helper.staking.unstakeAll(staker)
351 : helper.staking.unstakePartial(staker, 100n * nominal);
352 }));
252353
253 await Promise.all(stakers.map(async (staker) => {354 await Promise.all(stakers.map(async (staker) => {
254 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal);355 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal);
255 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);356 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);
256 }));357 }));
358 });
257 });359 });
258360
259 itSub('should not be possible for more than 3 accounts in one block', async ({helper}) => {361 itSub('should not be possible for more than 3 accounts in one block', async ({helper}) => {
260 if (!await helper.arrange.isDevNode()) {362 if (!await helper.arrange.isDevNode()) {
261 const stakers = await helper.arrange.createAccounts([200n,200n,200n,200n,200n,200n,200n,200n,200n,200n], donor);363 const stakers = getAccount(10);
262364
263 await Promise.all(stakers.map(staker => helper.staking.stake(staker, 100n * nominal)));365 await Promise.all(stakers.map(staker => helper.staking.stake(staker, 100n * nominal)));
264 const unstakingResults = await Promise.allSettled(stakers.map(staker => helper.staking.unstake(staker)));366 const unstakingResults = await Promise.allSettled(stakers.map((staker, i) => {
367 return i % 2 === 0
368 ? helper.staking.unstakeAll(staker)
369 : helper.staking.unstakePartial(staker, 100n * nominal);
370 }));
265371
266 const successfulUnstakes = unstakingResults.filter(result => result.status === 'fulfilled');372 const successfulUnstakes = unstakingResults.filter(result => result.status === 'fulfilled');
267 expect(successfulUnstakes).to.have.length(3);373 expect(successfulUnstakes).to.have.length(3);
268 }374 }
269 });375 });
376
377 itSub('Cannot partially unstake more than staked', async ({helper}) => {
378 const [staker] = getAccount(1);
379 // Staker stakes 300:
380 await helper.staking.stake(staker, 100n * nominal);
381 await helper.staking.stake(staker, 200n * nominal);
382
383 // cannot usntake 300.00000...1
384 await expect(helper.staking.unstakePartial(staker, 300n * nominal + 1n)).to.be.rejectedWith('appPromotion.InsufficientStakedBalance');
385 expect(await helper.staking.getStakesNumber({Substrate: staker.address})).eq(2);
386
387 await helper.staking.unstakePartial(staker, 150n * nominal);
388 expect(await helper.staking.getStakesNumber({Substrate: staker.address})).eq(1);
389 await expect(helper.staking.unstakePartial(staker, 150n * nominal + 1n)).to.be.rejectedWith('appPromotion.InsufficientStakedBalance');
390 expect(await helper.staking.getStakesNumber({Substrate: staker.address})).eq(1);
391
392 // nothing broken, can unstake full amount:
393 await helper.staking.unstakePartial(staker, 150n * nominal);
394 expect(await helper.staking.getStakesNumber({Substrate: staker.address})).eq(0);
395 });
396
397 itSub('Can partially unstake arbitrary amount', async ({helper}) => {
398 const [staker] = getAccount(1);
399 await helper.staking.stake(staker, 100n * nominal);
400 await helper.staking.stake(staker, 200n * nominal);
401
402 // 0. Staker cannot unstake negative amount
403 await expect(helper.staking.unstakePartial(staker, -1n)).to.be.rejected;
404
405 // 1. Staker can unstake 0 wei
406 await helper.staking.unstakePartial(staker, 0n);
407 expect(await helper.staking.getStakesNumber({Substrate: staker.address})).to.eq(2);
408 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.eq(300n * nominal);
409 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.eq(0n);
410
411 // 2. Staker can unstake 1 wei
412 await helper.staking.unstakePartial(staker, 1n);
413 expect(await helper.staking.getStakesNumber({Substrate: staker.address})).to.eq(2);
414 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.eq(300n * nominal - 1n);
415 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.eq(1n);
416 // 2.1 The oldest stake decreased:
417 let [stake1, stake2] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
418 expect(stake1.amount).to.eq(100n * nominal - 1n);
419 expect(stake2.amount).to.eq(200n * nominal);
420
421 // 3. Staker can unstake all but 1 wei
422 await helper.staking.unstakePartial(staker, 100n * nominal - 2n);
423 expect(await helper.staking.getStakesNumber({Substrate: staker.address})).to.eq(2);
424 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.eq(200n * nominal + 1n);
425 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.eq(100n * nominal - 1n);
426 [stake1, stake2] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
427 expect(stake1.amount).to.eq(1n);
428 expect(stake2.amount).to.eq(200n * nominal);
429 });
430
431 itSub('can mix different type of unstakes', async ({helper}) => {
432 const [staker] = getAccount(1);
433 await helper.staking.stake(staker, 100n * nominal);
434 await helper.staking.stake(staker, 200n * nominal);
435
436 await helper.staking.unstakePartial(staker, 50n * nominal);
437 await helper.staking.unstakeAll(staker);
438 expect(await helper.staking.getStakesNumber({Substrate: staker.address})).to.eq(0);
439 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.eq(0n);
440 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.eq(300n * nominal);
441
442 const [_unstake1, unstake2] = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});
443 await helper.wait.forParachainBlockNumber(unstake2.block);
444
445 expect(await helper.balance.getLocked(staker.address)).to.deep.eq([]);
446 expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, miscFrozen: 0n, feeFrozen: 0n});
447 expect(await helper.balance.getSubstrate(staker.address) / nominal).to.eq(999n);
448 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.eq(0n);
449 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.eq(0n);
450 expect(await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address})).to.deep.eq([]);
451 });
270 });452 });
271453
272 describe('collection sponsoring', () => {454 describe('collection sponsoring', () => {
273 itSub('should actually sponsor transactions', async ({helper}) => {455 itSub('should actually sponsor transactions', async ({helper}) => {
274 const api = helper.getApi();456 const api = helper.getApi();
275 const [collectionOwner, tokenSender, receiver] = [accounts.pop()!, accounts.pop()!, accounts.pop()!];457 const [collectionOwner, tokenSender, receiver] = getAccount(3);
276 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'Name', description: 'Description', tokenPrefix: 'Prefix', limits: {sponsorTransferTimeout: 0}});458 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'Name', description: 'Description', tokenPrefix: 'Prefix', limits: {sponsorTransferTimeout: 0}});
277 const token = await collection.mintToken(collectionOwner, {Substrate: tokenSender.address});459 const token = await collection.mintToken(collectionOwner, {Substrate: tokenSender.address});
278 await helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId));460 await helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId));
289471
290 itSub('can not be set by non admin', async ({helper}) => {472 itSub('can not be set by non admin', async ({helper}) => {
291 const api = helper.getApi();473 const api = helper.getApi();
292 const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!];474 const [collectionOwner, nonAdmin] = getAccount(2);
293475
294 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});476 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});
295477
299481
300 itSub('should set pallet address as confirmed admin', async ({helper}) => {482 itSub('should set pallet address as confirmed admin', async ({helper}) => {
301 const api = helper.getApi();483 const api = helper.getApi();
302 const [collectionOwner, oldSponsor] = [accounts.pop()!, accounts.pop()!];484 const [collectionOwner, oldSponsor] = getAccount(2);
303485
304 // Can set sponsoring for collection without sponsor486 // Can set sponsoring for collection without sponsor
305 const collectionWithoutSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'No-sponsor', description: 'New Collection', tokenPrefix: 'Promotion'});487 const collectionWithoutSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'No-sponsor', description: 'New Collection', tokenPrefix: 'Promotion'});
321503
322 itSub('can be overwritten by collection owner', async ({helper}) => {504 itSub('can be overwritten by collection owner', async ({helper}) => {
323 const api = helper.getApi();505 const api = helper.getApi();
324 const [collectionOwner, newSponsor] = [accounts.pop()!, accounts.pop()!];506 const [collectionOwner, newSponsor] = getAccount(2);
325 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});507 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});
326 const collectionId = collection.collectionId;508 const collectionId = collection.collectionId;
327509
340 itSub('should not overwrite collection limits set by the owner earlier', async ({helper}) => {522 itSub('should not overwrite collection limits set by the owner earlier', async ({helper}) => {
341 const api = helper.getApi();523 const api = helper.getApi();
342 const limits = {ownerCanDestroy: true, ownerCanTransfer: true, sponsorTransferTimeout: 0};524 const limits = {ownerCanDestroy: true, ownerCanTransfer: true, sponsorTransferTimeout: 0};
343 const collectionWithLimits = await helper.nft.mintCollection(accounts.pop()!, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits});525 const collectionWithLimits = await helper.nft.mintCollection(getAccount(1)[0], {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits});
344526
345 await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collectionWithLimits.collectionId))).to.be.fulfilled;527 await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collectionWithLimits.collectionId))).to.be.fulfilled;
346 expect((await collectionWithLimits.getData())?.raw.limits).to.be.deep.contain(limits);528 expect((await collectionWithLimits.getData())?.raw.limits).to.be.deep.contain(limits);
347 });529 });
348530
349 itSub('should reject transaction if collection doesn\'t exist', async ({helper}) => {531 itSub('should reject transaction if collection doesn\'t exist', async ({helper}) => {
350 const api = helper.getApi();532 const api = helper.getApi();
351 const collectionOwner = accounts.pop()!;533 const [collectionOwner] = getAccount(1);
352534
353 // collection has never existed535 // collection has never existed
354 await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(999999999))).to.be.rejected;536 await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(999999999))).to.be.rejected;
363 describe('stopSponsoringCollection', () => {545 describe('stopSponsoringCollection', () => {
364 itSub('can not be called by non-admin', async ({helper}) => {546 itSub('can not be called by non-admin', async ({helper}) => {
365 const api = helper.getApi();547 const api = helper.getApi();
366 const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!];548 const [collectionOwner, nonAdmin] = getAccount(2);
367 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});549 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});
368550
369 await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled;551 await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled;
374556
375 itSub('should set sponsoring as disabled', async ({helper}) => {557 itSub('should set sponsoring as disabled', async ({helper}) => {
376 const api = helper.getApi();558 const api = helper.getApi();
377 const [collectionOwner, recepient] = [accounts.pop()!, accounts.pop()!];559 const [collectionOwner, recepient] = getAccount(2);
378 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits: {sponsorTransferTimeout: 0}});560 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits: {sponsorTransferTimeout: 0}});
379 const token = await collection.mintToken(collectionOwner, {Substrate: collectionOwner.address});561 const token = await collection.mintToken(collectionOwner, {Substrate: collectionOwner.address});
380562
392574
393 itSub('should not affect collection which is not sponsored by pallete', async ({helper}) => {575 itSub('should not affect collection which is not sponsored by pallete', async ({helper}) => {
394 const api = helper.getApi();576 const api = helper.getApi();
395 const collectionOwner = accounts.pop()!;577 const [collectionOwner] = getAccount(1);
396 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: collectionOwner.address});578 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: collectionOwner.address});
397 await collection.confirmSponsorship(collectionOwner);579 await collection.confirmSponsorship(collectionOwner);
398580
402 });584 });
403585
404 itSub('should reject transaction if collection does not exist', async ({helper}) => {586 itSub('should reject transaction if collection does not exist', async ({helper}) => {
405 const collectionOwner = accounts.pop()!;587 const [collectionOwner] = getAccount(1);
406 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});588 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});
407589
408 await collection.burn(collectionOwner);590 await collection.burn(collectionOwner);
476 });658 });
477659
478 itEth('can not be set by non admin', async ({helper}) => {660 itEth('can not be set by non admin', async ({helper}) => {
479 const nonAdmin = accounts.pop()!;661 const [nonAdmin] = getAccount(1);
480 const contractOwner = (await helper.eth.createAccountWithBalance(donor, 1000n)).toLowerCase();662 const contractOwner = (await helper.eth.createAccountWithBalance(donor, 1000n)).toLowerCase();
481 const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);663 const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);
482 const contractHelper = await helper.ethNativeContract.contractHelpers(contractOwner);664 const contractHelper = await helper.ethNativeContract.contractHelpers(contractOwner);
558 });740 });
559741
560 itEth('can not be called by non-admin', async ({helper}) => {742 itEth('can not be called by non-admin', async ({helper}) => {
561 const nonAdmin = accounts.pop()!;743 const [nonAdmin] = getAccount(1);
562 const contractOwner = (await helper.eth.createAccountWithBalance(donor, 1000n)).toLowerCase();744 const contractOwner = (await helper.eth.createAccountWithBalance(donor, 1000n)).toLowerCase();
563 const flipper = await helper.eth.deployFlipper(contractOwner);745 const flipper = await helper.eth.deployFlipper(contractOwner);
564746
568 });750 });
569751
570 itEth('should not affect a contract which is not sponsored by pallete', async ({helper}) => {752 itEth('should not affect a contract which is not sponsored by pallete', async ({helper}) => {
571 const nonAdmin = accounts.pop()!;753 const [nonAdmin] = getAccount(1);
572 const contractOwner = (await helper.eth.createAccountWithBalance(donor, 1000n)).toLowerCase();754 const contractOwner = (await helper.eth.createAccountWithBalance(donor, 1000n)).toLowerCase();
573 const flipper = await helper.eth.deployFlipper(contractOwner);755 const flipper = await helper.eth.deployFlipper(contractOwner);
574 const contractHelper = await helper.ethNativeContract.contractHelpers(contractOwner);756 const contractHelper = await helper.ethNativeContract.contractHelpers(contractOwner);
580762
581 describe('payoutStakers', () => {763 describe('payoutStakers', () => {
582 itSub('can not be called by non admin', async ({helper}) => {764 itSub('can not be called by non admin', async ({helper}) => {
583 const nonAdmin = accounts.pop()!;765 const [nonAdmin] = getAccount(1);
584 await expect(helper.admin.payoutStakers(nonAdmin, 100)).to.be.rejectedWith('appPromotion.NoPermission');766 await expect(helper.admin.payoutStakers(nonAdmin, 100)).to.be.rejectedWith('appPromotion.NoPermission');
585 });767 });
586768
587 itSub('should increase total staked', async ({helper}) => {769 itSub('should increase total staked', async ({helper}) => {
588 const staker = accounts.pop()!;770 const [staker] = getAccount(1);
589 const totalStakedBefore = await helper.staking.getTotalStaked();771 const totalStakedBefore = await helper.staking.getTotalStaked();
590 await helper.staking.stake(staker, 100n * nominal);772 await helper.staking.stake(staker, 100n * nominal);
591773
597 const totalStakedAfter = await helper.staking.getTotalStaked();779 const totalStakedAfter = await helper.staking.getTotalStaked();
598 expect(totalStakedAfter).to.equal(totalStakedBefore + (100n * nominal) + totalPayout);780 expect(totalStakedAfter).to.equal(totalStakedBefore + (100n * nominal) + totalPayout);
599 // staker can unstake781 // staker can unstake
600 await helper.staking.unstake(staker);782 await helper.staking.unstakeAll(staker);
601 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedAfter - calculateIncome(100n * nominal));783 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedAfter - calculateIncome(100n * nominal));
602 });784 });
603785
604 itSub('should credit 0.05% for staking period', async ({helper}) => {786 itSub('should credit 0.05% for staking period', async ({helper}) => {
605 const staker = accounts.pop()!;787 const [staker] = getAccount(1);
606788
607 await waitPromotionPeriodDoesntEnd(helper);789 await waitPromotionPeriodDoesntEnd(helper);
608790
628 });810 });
629811
630 itSub('shoud be paid for more than one period if payments was missed', async ({helper}) => {812 itSub('shoud be paid for more than one period if payments was missed', async ({helper}) => {
631 const staker = accounts.pop()!;813 const [staker] = getAccount(1);
632814
633 await helper.staking.stake(staker, 100n * nominal);815 await helper.staking.stake(staker, 100n * nominal);
634 // wait for two rewards are available:816 // wait for two rewards are available:
647829
648 itSub('should not be credited for pending-unstaked tokens', async ({helper}) => {830 itSub('should not be credited for pending-unstaked tokens', async ({helper}) => {
649 // staker unstakes before rewards been payed831 // staker unstakes before rewards been payed
650 const staker = accounts.pop()!;832 const [staker] = getAccount(1);
651 await helper.staking.stake(staker, 100n * nominal);833 await helper.staking.stake(staker, 100n * nominal);
652 const [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});834 const [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
653 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block) + LOCKING_PERIOD);835 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block) + LOCKING_PERIOD);
654 await helper.staking.unstake(staker);836 await helper.staking.unstakeAll(staker);
655837
656 // so he did not receive any rewards838 // so he did not receive any rewards
657 const totalBalanceBefore = await helper.balance.getSubstrate(staker.address);839 const totalBalanceBefore = await helper.balance.getSubstrate(staker.address);
662 });844 });
663845
664 itSub('should bring compound interest', async ({helper}) => {846 itSub('should bring compound interest', async ({helper}) => {
665 const staker = accounts.pop()!;847 const [staker] = getAccount(1);
666848
667 await helper.staking.stake(staker, 100n * nominal);849 await helper.staking.stake(staker, 100n * nominal);
668850
679 expect(stake.amount).to.equal(calculateIncome(100n * nominal, 2));861 expect(stake.amount).to.equal(calculateIncome(100n * nominal, 2));
680 });862 });
681863
682 itSub.skip('can be paid 1000 rewards in a time', async ({helper}) => {864 itSub('can calculate reward for tiny stake', async ({helper}) => {
683 // all other stakes should be unstaked865 const [staker] = getAccount(1);
866 await helper.staking.stake(staker, 100n * nominal);
684 const oneHundredStakers = await helper.arrange.createCrowd(100, 1050n, donor);867 await helper.staking.stake(staker, 100n * nominal);
868 await helper.staking.unstakePartial(staker, 100n * nominal - 1n);
685869
686 // stakers stakes 10 times each870 const [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
687 for (let i = 0; i < 10; i++) {871 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block));
872
688 await Promise.all(oneHundredStakers.map(staker => helper.staking.stake(staker, 100n * nominal)));873 const payouts = await helper.admin.payoutStakers(palletAdmin, 100);
689 }874 const stakerPayout = payouts.find(p => p.staker === staker.address);
690 await helper.wait.newBlocks(40);
691 await helper.admin.payoutStakers(palletAdmin, 100);875 expect(stakerPayout!.stake).to.eq(100n * nominal + 1n);
692 });876 });
693877
694 itSub.skip('can handle 40.000 rewards', async ({helper}) => {878 itSub('can eventually pay all rewards', async ({helper}) => {
695 const crowdStakes = async () => {879 const stakers = getAccount(30);
696 // each account in the crowd stakes 2 times880 // Create 30 stakes:
697 const crowd = await helper.arrange.createCrowd(500, 300n, donor);881 await Promise.all(stakers.map(staker => helper.staking.stake(staker, 100n * nominal)));
698 await Promise.all(crowd.map(account => helper.staking.stake(account, 100n * nominal)));
699 await Promise.all(crowd.map(account => helper.staking.stake(account, 100n * nominal)));
700 //
701 };
702882
703 for (let i = 0; i < 40; i++) {883 let unstakingTxs = [];
884 for (const staker of stakers) {
704 await crowdStakes();885 if (unstakingTxs.length == 3) {
886 await Promise.all(unstakingTxs);
887 unstakingTxs = [];
888 }
889 unstakingTxs.push(helper.staking.unstakePartial(staker, 100n * nominal - 1n));
705 }890 }
706891
707 // TODO pay rewards for some period892 const [staker] = getAccount(1);
893 await helper.staking.stake(staker, 100n * nominal);
894 const [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
895 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block));
896
897 let payouts;
898 do {
899 payouts = await helper.admin.payoutStakers(palletAdmin, 20);
900 } while (payouts.length !== 0);
708 });901 });
709 });902 });
710});903});
904
711905
712function calculateIncome(base: bigint, iter = 0, calcPeriod: bigint = UNLOCKING_PERIOD): bigint {906function calculateIncome(base: bigint, iter = 0, calcPeriod: bigint = UNLOCKING_PERIOD): bigint {
713 const DAY = 7200n;907 const DAY = 7200n;
modifiedtests/src/util/globalSetup.tsdiffbeforeafterboth
29 const api = helper.getApi();29 const api = helper.getApi();
30 await helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})));30 await helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})));
31 const nominal = helper.balance.getOneTokenNominal();31 const nominal = helper.balance.getOneTokenNominal();
32 await helper.balance.transferToSubstrate(superuser, palletAdmin.address, 1000n * nominal);32 await helper.balance.transferToSubstrate(superuser, palletAdmin.address, 10000n * nominal);
33 await helper.balance.transferToSubstrate(superuser, palletAddress, 1000n * nominal);33 await helper.balance.transferToSubstrate(superuser, palletAddress, 10000n * nominal);
34 await helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [api.tx.configuration34 await helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [api.tx.configuration
35 .setAppPromotionConfigurationOverride({35 .setAppPromotionConfigurationOverride({
36 recalculationInterval: LOCKING_PERIOD,36 recalculationInterval: LOCKING_PERIOD,
modifiedtests/src/util/index.tsdiffbeforeafterboth
94};94};
9595
96export const MINIMUM_DONOR_FUND = 100_000n;96export const MINIMUM_DONOR_FUND = 100_000n;
97export const DONOR_FUNDING = 1_000_000n;97export const DONOR_FUNDING = 2_000_000n;
9898
99// App-promotion periods:99// App-promotion periods:
100export const LOCKING_PERIOD = 12n; // 12 blocks of relay100export const LOCKING_PERIOD = 12n; // 12 blocks of relay
modifiedtests/src/util/playgrounds/types.tsdiffbeforeafterboth
20 event: IEvent;20 event: IEvent;
21 }[];21 }[];
22 },22 },
23 blockHash: string,
23 moduleError?: string;24 moduleError?: string | object;
24}25}
2526
26export interface ISubscribeBlockEventsData {27export interface ISubscribeBlockEventsData {
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
214 accounts.push(recipient);214 accounts.push(recipient);
215 if (balance !== 0n) {215 if (balance !== 0n) {
216 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);216 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);
217 transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));217 transactions.push(this.helper.signTransaction(donor, tx, {nonce, era: 0}, 'account generation'));
218 nonce++;218 nonce++;
219 }219 }
220 }220 }
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
6/* eslint-disable no-prototype-builtins */6/* eslint-disable no-prototype-builtins */
77
8import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';8import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';
9import {SignerOptions} from '@polkadot/api/types/submittable';
9import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types';10import {ApiInterfaceEvents} from '@polkadot/api/types';
10import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm, base58Encode, blake2AsU8a} from '@polkadot/util-crypto';11import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm, base58Encode, blake2AsU8a} from '@polkadot/util-crypto';
11import {IKeyringPair} from '@polkadot/types/types';12import {IKeyringPair} from '@polkadot/types/types';
12import {hexToU8a} from '@polkadot/util/hex';13import {hexToU8a} from '@polkadot/util/hex';
561 if (status === this.transactionStatus.SUCCESS) {562 if (status === this.transactionStatus.SUCCESS) {
562 this.logger.log(`${label} successful`);563 this.logger.log(`${label} successful`);
563 unsub();564 unsub();
564 resolve({result, status});565 resolve({result, status, blockHash: result.status.asInBlock.toHuman()});
565 } else if (status === this.transactionStatus.FAIL) {566 } else if (status === this.transactionStatus.FAIL) {
566 let moduleError = null;567 let moduleError = null;
567568
672 params,673 params,
673 } as IUniqueHelperLog;674 } as IUniqueHelperLog;
675
676 let errorMessage = '';
674677
675 if(result.status !== this.transactionStatus.SUCCESS) {678 if(result.status !== this.transactionStatus.SUCCESS) {
676 if (result.moduleError) log.moduleError = result.moduleError;679 if (result.moduleError) {
680 errorMessage = typeof result.moduleError === 'string'
681 ? result.moduleError
682 : `${Object.keys(result.moduleError)[0]}: ${Object.values(result.moduleError)[0]}`;
683 log.moduleError = errorMessage;
684 }
677 else if (result.result.dispatchError) log.dispatchError = result.result.dispatchError;685 else if (result.result.dispatchError) log.dispatchError = result.result.dispatchError;
678 }686 }
679 if(events.length > 0) log.events = events;687 if(events.length > 0) log.events = events;
680688
681 this.chainLog.push(log);689 this.chainLog.push(log);
682690
683 if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) {691 if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) {
684 if (result.moduleError) throw Error(`${result.moduleError}`);692 if (result.moduleError) throw Error(`${errorMessage}`);
685 else if (result.result.dispatchError) throw Error(JSON.stringify(result.result.dispatchError));693 else if (result.result.dispatchError) throw Error(JSON.stringify(result.result.dispatchError));
686 }694 }
687 return result;695 return result;
2656 return true;2664 return true;
2657 }2665 }
26582666
2659 /**2667 /**
2660 * Unstake tokens for App Promotion2668 * Unstake all staked tokens
2661 * @param signer keyring of signer2669 * @param signer keyring of signer
2662 * @param amountToUnstake amount of tokens to unstake2670 * @param amountToUnstake amount of tokens to unstake
2663 * @param label extra label for log2671 * @param label extra label for log
2664 * @returns block number where balances will be unlocked2672 * @returns block hash where unstake happened
2665 */2673 */
2666 async unstake(signer: TSigner, label?: string): Promise<number> {2674 async unstakeAll(signer: TSigner, label?: string): Promise<string> {
2667 if(typeof label === 'undefined') label = `${signer.address}`;2675 if(typeof label === 'undefined') label = `${signer.address}`;
2668 const _unstakeResult = await this.helper.executeExtrinsic(2676 const unstakeResult = await this.helper.executeExtrinsic(
2669 signer, 'api.tx.appPromotion.unstake',2677 signer, 'api.tx.appPromotion.unstakeAll',
2670 [], true,2678 [], true,
2671 );2679 );
2672 // TODO extract block number fron events
2673 return 1;2680 return unstakeResult.blockHash;
2674 }2681 }
2682
2683 /**
2684 * Unstake the part of a staked tokens
2685 * @param signer keyring of signer
2686 * @param amount amount of tokens to unstake
2687 * @param label extra label for log
2688 * @returns block hash where unstake happened
2689 */
2690 async unstakePartial(signer: TSigner, amount: bigint, label?: string): Promise<string> {
2691 if(typeof label === 'undefined') label = `${signer.address}`;
2692 const unstakeResult = await this.helper.executeExtrinsic(
2693 signer, 'api.tx.appPromotion.unstakePartial',
2694 [amount], true,
2695 );
2696 return unstakeResult.blockHash;
2697 }
2698
2699 /**
2700 * Get total number of active stakes
2701 * @param address substrate address
2702 * @returns {number}
2703 */
2704 async getStakesNumber(address: ICrossAccountId): Promise<number> {
2705 if (address.Ethereum) throw Error('only substrate address');
2706 return (await this.helper.callRpc('api.query.appPromotion.stakesPerAccount', [address.Substrate])).toNumber();
2707 }
26752708
2676 /**2709 /**
2677 * Get total staked amount for address2710 * Get total staked amount for address